项目文件夹

文件
wehub-resource-sync c48612c494
CI / E2E Tests (push) Has been cancelled
CI / Lint, Typecheck & Unit Tests (push) Has been cancelled
Docs Build / Build docs site (push) Has been cancelled
Publish @openmaic packages / Build, validate & publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:23 +08:00

84 行
3.2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
splitLongSpeechText,
splitLongSpeechActions,
TTS_MAX_TEXT_LENGTH,
} from '@/lib/audio/tts-utils';
import type { Action, SpeechAction } from '@/lib/types/action';
describe('splitLongSpeechText', () => {
it('returns the trimmed text unchanged when within the limit', () => {
expect(splitLongSpeechText(' hello world ', 100)).toEqual(['hello world']);
});
it('splits at sentence boundaries, keeping the punctuation', () => {
expect(splitLongSpeechText('句子一。句子二!句子三?', 6)).toEqual([
'句子一。',
'句子二!',
'句子三?',
]);
});
it('packs consecutive short sentences up to the limit', () => {
const out = splitLongSpeechText('aa。bb。cc。dd。', 6);
expect(out).toEqual(['aa。bb。', 'cc。dd。']);
expect(out.join('')).toBe('aa。bb。cc。dd。');
});
it('falls back to clause punctuation for an over-long sentence', () => {
const out = splitLongSpeechText('一,二,三,四,五', 4);
expect(out.every((c) => c.length <= 4)).toBe(true);
expect(out.join('').replace(/,/g, '')).toContain('一');
});
it('hard-splits a long run with no punctuation', () => {
expect(splitLongSpeechText('x'.repeat(25), 10)).toEqual(['xxxxxxxxxx', 'xxxxxxxxxx', 'xxxxx']);
});
it('never emits a chunk longer than maxLength, nor an empty chunk (invariant)', () => {
const text = '这是一个很长的句子,包含很多子句、标点。'.repeat(50) + 'z'.repeat(120);
for (const max of [8, 16, 64, 200]) {
const out = splitLongSpeechText(text, max);
expect(
out.every((c) => c.length <= max),
`max=${max}`,
).toBe(true);
expect(
out.some((c) => c.length === 0),
`max=${max}`,
).toBe(false);
}
});
});
describe('splitLongSpeechActions', () => {
const speech = (id: string, text: string): SpeechAction =>
({ id, type: 'speech', text }) as SpeechAction;
it('returns actions untouched for a provider with no length limit', () => {
const actions: Action[] = [speech('a', 'x'.repeat(5000))];
expect(splitLongSpeechActions(actions, 'openai-tts')).toBe(actions);
});
it('leaves short speech and non-speech actions unchanged', () => {
const actions: Action[] = [speech('s', 'short'), { id: 'sp', type: 'spotlight' } as Action];
expect(splitLongSpeechActions(actions, 'glm-tts')).toBe(actions);
});
it('splits an over-limit speech action into chunked sub-actions', () => {
const max = TTS_MAX_TEXT_LENGTH['glm-tts']!; // 1024
const long = '句子。'.repeat(400); // 1200 chars > 1024
const out = splitLongSpeechActions([speech('a', long)], 'glm-tts') as SpeechAction[];
expect(out.length).toBeGreaterThan(1);
expect(out.every((a) => a.type === 'speech' && a.text.length <= max)).toBe(true);
// deterministic sub-action ids…
expect(out.map((a) => a.id)).toEqual(out.map((_, i) => `a_tts_${i + 1}`));
// …each gets its own audio (the parent audioId is dropped)…
expect(out.every((a) => a.audioId === undefined)).toBe(true);
// …and the text is preserved across the split.
expect(out.map((a) => a.text).join('')).toBe(long);
});
});