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

60 lines
2.1 KiB
TypeScript

import { describe, expect, test } from 'vitest';
import {
buildMonotonicWordToTokenMap,
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,
});
const alignmentWords = (words: string[]): TTSSentenceAlignment['words'] =>
words.map((word, index) => ({
text: word,
startSec: index,
endSec: index + 0.5,
charStart: 0,
charEnd: word.length,
}));
describe('EPUB word highlight mapping', () => {
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 },
]);
});
test('maps repeated words monotonically instead of jumping to later duplicates', () => {
const tokens = tokenizeCanonicalSegment(segment('the light and the shadow and the light returned'));
const map = buildMonotonicWordToTokenMap(
alignmentWords(['the', 'light', 'and', 'the', 'shadow', 'and', 'the', 'light', 'returned']),
tokens,
);
expect(map).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8]);
});
test('leaves unmatched alignment words unhighlighted instead of borrowing a neighbor', () => {
const tokens = tokenizeCanonicalSegment(segment('alpha beta gamma'));
const map = buildMonotonicWordToTokenMap(
alignmentWords(['alpha', 'missing', 'gamma']),
tokens,
);
expect(map).toEqual([0, -1, 2]);
});
});