openreader/tests/unit/epub-word-highlight.vitest.spec.ts

71 lines
2.5 KiB
TypeScript

import { describe, expect, test } from 'vitest';
import {
resolveAlignmentWordSourceRange,
tokenizeCanonicalSegment,
} from '../../src/lib/client/epub/epub-word-highlight';
import type { CanonicalTtsSegment } from '../../src/lib/shared/tts-segment-plan';
import type { TTSSentenceAlignment } from '../../src/types/tts';
const segment = (text: string, offset = 0): CanonicalTtsSegment => ({
key: `segment:${offset}:${text}`,
ordinal: 0,
text,
ownerSourceKey: 'str:epubcfi(/6/2)',
ownerLocator: { location: 'epubcfi(/6/2)', readerType: 'epub' },
startAnchor: { sourceKey: 'str:epubcfi(/6/2)', offset },
endAnchor: { sourceKey: 'str:epubcfi(/6/2)', offset: offset + text.length },
spansSourceBoundary: false,
});
describe('EPUB word highlight mapping', () => {
test('tokenizes Japanese and Chinese using locale-aware word boundaries', () => {
const japanese = tokenizeCanonicalSegment(segment('これは日本語です。', 5), 'ja');
expect(japanese.length).toBeGreaterThan(1);
expect(japanese.every((token) => token.norm.length > 0)).toBe(true);
const chinese = tokenizeCanonicalSegment(segment('这是中文。', 10), 'zh');
expect(chinese.length).toBeGreaterThan(1);
expect(chinese.map((token) => token.norm).join('')).toBe('这是中文');
});
test('resolves Japanese alignment chunks directly from character offsets', () => {
const japanese = segment('これは日本語です。', 25);
const word: TTSSentenceAlignment['words'][number] = {
text: 'これは',
startSec: 0,
endSec: 0.5,
charStart: 0,
charEnd: 3,
};
expect(resolveAlignmentWordSourceRange(japanese, word)).toEqual({
sourceStart: 25,
sourceEnd: 28,
});
});
test('rejects invalid alignment character offsets so token mapping can be used', () => {
const japanese = segment('これは日本語です。', 25);
const word: TTSSentenceAlignment['words'][number] = {
text: '範囲外',
startSec: 0,
endSec: 0.5,
charStart: 20,
charEnd: 23,
};
expect(resolveAlignmentWordSourceRange(japanese, word)).toBeNull();
});
test('tokenizes canonical segment words with source offsets', () => {
const tokens = tokenizeCanonicalSegment(segment('"Hello," she said.', 12));
expect(tokens).toEqual([
{ norm: 'hello', sourceStart: 13, sourceEnd: 18 },
{ norm: 'she', sourceStart: 21, sourceEnd: 24 },
{ norm: 'said', sourceStart: 25, sourceEnd: 29 },
]);
});
});