forked from akai/readest
0e516f6e56
Add ~290 new unit tests covering utils (lru, diff, css, a11y, walk, usage, txt-worker), services (EdgeTTSClient, transformers), and suppress noisy console output across all test files. Rename 27 camelCase test files to dash-case for consistency. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
244 lines
7.1 KiB
TypeScript
244 lines
7.1 KiB
TypeScript
import { describe, test, expect } from 'vitest';
|
||
import {
|
||
isCJK,
|
||
containsCJK,
|
||
isCJKPunctuation,
|
||
getSegmenterLocale,
|
||
segmentCJKText,
|
||
splitTextIntoWords,
|
||
} from '@/services/rsvp/utils';
|
||
|
||
describe('rsvp/utils', () => {
|
||
describe('isCJK', () => {
|
||
test('returns true for CJK Unified Ideographs', () => {
|
||
expect(isCJK('\u4e00')).toBe(true); // first CJK character
|
||
expect(isCJK('\u9fff')).toBe(true); // last CJK character
|
||
expect(isCJK('\u5f00')).toBe(true); // 开
|
||
});
|
||
|
||
test('returns true for Hiragana', () => {
|
||
expect(isCJK('\u3042')).toBe(true); // あ
|
||
});
|
||
|
||
test('returns true for Katakana', () => {
|
||
expect(isCJK('\u30A2')).toBe(true); // ア
|
||
});
|
||
|
||
test('returns true for Hangul', () => {
|
||
expect(isCJK('\uAC00')).toBe(true); // 가
|
||
});
|
||
|
||
test('returns true for CJK Extension A', () => {
|
||
expect(isCJK('\u3400')).toBe(true);
|
||
});
|
||
|
||
test('returns true for CJK Compatibility Ideographs', () => {
|
||
expect(isCJK('\uF900')).toBe(true);
|
||
});
|
||
|
||
test('returns false for Latin characters', () => {
|
||
expect(isCJK('a')).toBe(false);
|
||
expect(isCJK('Z')).toBe(false);
|
||
});
|
||
|
||
test('returns false for digits', () => {
|
||
expect(isCJK('1')).toBe(false);
|
||
});
|
||
|
||
test('returns false for spaces', () => {
|
||
expect(isCJK(' ')).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe('containsCJK', () => {
|
||
test('returns true for text with CJK characters', () => {
|
||
expect(containsCJK('Hello 你好')).toBe(true);
|
||
});
|
||
|
||
test('returns true for pure CJK text', () => {
|
||
expect(containsCJK('你好世界')).toBe(true);
|
||
});
|
||
|
||
test('returns false for pure Latin text', () => {
|
||
expect(containsCJK('Hello World')).toBe(false);
|
||
});
|
||
|
||
test('returns false for empty string', () => {
|
||
expect(containsCJK('')).toBe(false);
|
||
});
|
||
|
||
test('returns true for Japanese hiragana', () => {
|
||
expect(containsCJK('こんにちは')).toBe(true);
|
||
});
|
||
|
||
test('returns true for Korean', () => {
|
||
expect(containsCJK('안녕하세요')).toBe(true);
|
||
});
|
||
});
|
||
|
||
describe('isCJKPunctuation', () => {
|
||
test('returns true for Chinese period', () => {
|
||
expect(isCJKPunctuation('。')).toBe(true);
|
||
});
|
||
|
||
test('returns true for Chinese comma', () => {
|
||
expect(isCJKPunctuation(',')).toBe(true);
|
||
});
|
||
|
||
test('returns true for full-width exclamation', () => {
|
||
expect(isCJKPunctuation('!')).toBe(true);
|
||
});
|
||
|
||
test('returns true for full-width question mark', () => {
|
||
expect(isCJKPunctuation('?')).toBe(true);
|
||
});
|
||
|
||
test('returns true for brackets', () => {
|
||
expect(isCJKPunctuation('【')).toBe(true);
|
||
expect(isCJKPunctuation('】')).toBe(true);
|
||
expect(isCJKPunctuation('「')).toBe(true);
|
||
expect(isCJKPunctuation('」')).toBe(true);
|
||
});
|
||
|
||
test('returns true for ellipsis', () => {
|
||
expect(isCJKPunctuation('…')).toBe(true);
|
||
});
|
||
|
||
test('returns false for standard Latin comma', () => {
|
||
expect(isCJKPunctuation(',')).toBe(false);
|
||
});
|
||
|
||
test('returns false for Latin period', () => {
|
||
expect(isCJKPunctuation('.')).toBe(false);
|
||
});
|
||
|
||
test('returns false for a letter', () => {
|
||
expect(isCJKPunctuation('A')).toBe(false);
|
||
});
|
||
});
|
||
|
||
describe('getSegmenterLocale', () => {
|
||
test('returns ja for Japanese hiragana text', () => {
|
||
expect(getSegmenterLocale('こんにちは')).toBe('ja');
|
||
});
|
||
|
||
test('returns ja for Katakana text', () => {
|
||
expect(getSegmenterLocale('アイウ')).toBe('ja');
|
||
});
|
||
|
||
test('returns ko for Korean text', () => {
|
||
expect(getSegmenterLocale('안녕하세요')).toBe('ko');
|
||
});
|
||
|
||
test('returns zh for Chinese text', () => {
|
||
expect(getSegmenterLocale('你好世界')).toBe('zh');
|
||
});
|
||
|
||
test('returns null for pure Latin text', () => {
|
||
expect(getSegmenterLocale('Hello World')).toBeNull();
|
||
});
|
||
|
||
test('returns null for empty string', () => {
|
||
expect(getSegmenterLocale('')).toBeNull();
|
||
});
|
||
|
||
test('detects first CJK script in mixed text', () => {
|
||
// Japanese hiragana appears first
|
||
expect(getSegmenterLocale('あ你好')).toBe('ja');
|
||
});
|
||
});
|
||
|
||
describe('segmentCJKText', () => {
|
||
test('segments Chinese text into words', () => {
|
||
const words = segmentCJKText('你好世界');
|
||
expect(words.length).toBeGreaterThan(0);
|
||
expect(words.join('')).toContain('你好');
|
||
});
|
||
|
||
test('segments Japanese text', () => {
|
||
const words = segmentCJKText('こんにちは');
|
||
expect(words.length).toBeGreaterThan(0);
|
||
});
|
||
|
||
test('handles text with punctuation', () => {
|
||
const words = segmentCJKText('你好。世界!');
|
||
expect(words.length).toBeGreaterThan(0);
|
||
});
|
||
|
||
test('handles empty text', () => {
|
||
const words = segmentCJKText('');
|
||
expect(words).toEqual([]);
|
||
});
|
||
|
||
test('handles single character', () => {
|
||
const words = segmentCJKText('你');
|
||
expect(words.length).toBeGreaterThanOrEqual(1);
|
||
});
|
||
|
||
test('attaches trailing CJK punctuation', () => {
|
||
const words = segmentCJKText('你好!');
|
||
// The punctuation should be attached to a word
|
||
const hasWordWithPunct = words.some((w) => w.includes('!'));
|
||
expect(hasWordWithPunct).toBe(true);
|
||
});
|
||
});
|
||
|
||
describe('splitTextIntoWords', () => {
|
||
test('splits English text by spaces', () => {
|
||
const words = splitTextIntoWords('Hello World');
|
||
expect(words).toEqual(['Hello', 'World']);
|
||
});
|
||
|
||
test('splits multi-word English text', () => {
|
||
const words = splitTextIntoWords('The quick brown fox');
|
||
expect(words).toEqual(['The', 'quick', 'brown', 'fox']);
|
||
});
|
||
|
||
test('filters empty words', () => {
|
||
const words = splitTextIntoWords(' Hello World ');
|
||
expect(words.every((w) => w.trim().length > 0)).toBe(true);
|
||
});
|
||
|
||
test('handles CJK text', () => {
|
||
const words = splitTextIntoWords('你好世界');
|
||
expect(words.length).toBeGreaterThan(0);
|
||
});
|
||
|
||
test('handles mixed CJK and Latin text', () => {
|
||
const words = splitTextIntoWords('Hello 你好 World');
|
||
expect(words.length).toBeGreaterThan(0);
|
||
// Should contain both CJK and Latin segments
|
||
});
|
||
|
||
test('handles empty string', () => {
|
||
const words = splitTextIntoWords('');
|
||
expect(words).toEqual([]);
|
||
});
|
||
|
||
test('handles CJK text with punctuation', () => {
|
||
const words = splitTextIntoWords('你好。世界!');
|
||
expect(words.length).toBeGreaterThan(0);
|
||
});
|
||
|
||
test('handles CJK followed immediately by non-CJK', () => {
|
||
const words = splitTextIntoWords('你好Hello');
|
||
expect(words.length).toBeGreaterThanOrEqual(1);
|
||
});
|
||
|
||
test('handles standalone CJK punctuation at start', () => {
|
||
const words = splitTextIntoWords('。Hello');
|
||
expect(words.length).toBeGreaterThan(0);
|
||
});
|
||
|
||
test('handles non-CJK text followed by CJK punctuation', () => {
|
||
const words = splitTextIntoWords('Hello。');
|
||
expect(words.length).toBeGreaterThan(0);
|
||
});
|
||
|
||
test('handles whitespace between CJK segments', () => {
|
||
const words = splitTextIntoWords('你好 世界');
|
||
expect(words.length).toBeGreaterThan(0);
|
||
});
|
||
});
|
||
});
|