import { describe, expect, test } from 'vitest'; import { buildAlignmentTokenRanges, findBestHighlightTokenMatch, } from '../../src/lib/client/highlight-token-alignment'; import { segmentWords } from '../../src/lib/shared/language'; import type { TTSSentenceAlignment } from '../../src/types/tts'; const alignmentWords = ( words: string[], ): TTSSentenceAlignment['words'] => words.map((word, index) => ({ text: word, startSec: index, endSec: index + 0.5, charStart: 0, charEnd: word.length, })); describe('shared viewer highlight token alignment', () => { test('finds an exact sentence in a large document without entering fuzzy comparison', () => { const targets = Array.from({ length: 20_000 }, (_, index) => `token-${index}`); targets.splice(15_000, 4, 'the', 'exact', 'sentence', 'here'); expect(findBestHighlightTokenMatch( ['the', 'exact', 'sentence', 'here'], targets, )).toEqual({ start: 15_000, end: 15_003, rating: 1, lengthDiff: 0, }); }); test('matches a complete Japanese sentence using locale-aware token count', () => { const sentence = 'これは日本語です。'; const patternTokens = segmentWords(sentence, 'ja').map((token) => token.text); const tokenTexts = ['前文', ...patternTokens, '次文']; expect(findBestHighlightTokenMatch(patternTokens, tokenTexts)).toMatchObject({ start: 1, end: patternTokens.length, lengthDiff: 0, }); }); test('matches spaced Latin text without relying on whitespace tokens', () => { expect(findBestHighlightTokenMatch( ['hello', 'world'], ['before', 'hello', 'world', 'after'], )).toMatchObject({ start: 1, end: 2, lengthDiff: 0, }); }); test('maps a Japanese timed chunk across every visible token it spans', () => { expect(buildAlignmentTokenRanges( alignmentWords(['これは', '日本語', 'です']), ['これ', 'は', '日本語', 'です'], )).toEqual([ { start: 0, end: 1 }, { start: 2, end: 2 }, { start: 3, end: 3 }, ]); }); test('maps visible chunks back to a larger timed Japanese token', () => { expect(buildAlignmentTokenRanges( alignmentWords(['これ', 'は', '日本語', 'です']), ['これは', '日本語', 'です'], )).toEqual([ { start: 0, end: 0 }, { start: 0, end: 0 }, { start: 1, end: 1 }, { start: 2, end: 2 }, ]); }); test('maps repeated words monotonically', () => { expect(buildAlignmentTokenRanges( alignmentWords(['the', 'light', 'and', 'the', 'light']), ['the', 'light', 'and', 'the', 'light'], )).toEqual([ { start: 0, end: 0 }, { start: 1, end: 1 }, { start: 2, end: 2 }, { start: 3, end: 3 }, { start: 4, end: 4 }, ]); }); test('can leave unrelated fallback tokens unmapped for strict viewers', () => { expect(buildAlignmentTokenRanges( alignmentWords(['alpha', 'missing', 'gamma']), ['alpha', 'beta', 'gamma'], { minimumSimilarity: 0.8 }, )).toEqual([ { start: 0, end: 0 }, null, { start: 2, end: 2 }, ]); }); });