71 lines
2.5 KiB
TypeScript
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 },
|
|
]);
|
|
});
|
|
|
|
});
|