99 lines
3.5 KiB
TypeScript
99 lines
3.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
|
||
import { generateBreakpoints } from '../index';
|
||
|
||
function t(text: string, start: number): { text: string; start: number; end: number } {
|
||
return { text, start, end: start + text.length };
|
||
}
|
||
|
||
describe('textWrap breakpoint-candidates', () => {
|
||
it('EN: tokens=[I,am,so,tired] -> pos=[1,2,3](升序)', () => {
|
||
const tokens = [t('I', 0), t('am', 2), t('so', 5), t('tired', 8)];
|
||
const { breakpoints, meta } = generateBreakpoints({
|
||
tokens: tokens as any,
|
||
lang: 'EN',
|
||
maxLines: 2,
|
||
config: { tcMaxCandidateBreaks: 80, tcPunctuations: [',', '。'], balanceRange: 3 },
|
||
});
|
||
|
||
expect(breakpoints.map((b) => b.pos)).toEqual([1, 2, 3]);
|
||
expect(meta.originalCount).toBe(3);
|
||
expect(meta.finalCount).toBe(3);
|
||
});
|
||
|
||
it('TC: 标点后断点(,)应生成 pos=i+1', () => {
|
||
const tokens = [t('我', 0), t('好', 1), t('累', 2), t(',', 3), t('😮💨', 4)];
|
||
const { breakpoints } = generateBreakpoints({
|
||
tokens: tokens as any,
|
||
lang: 'TC',
|
||
maxLines: 2,
|
||
config: { tcMaxCandidateBreaks: 80, tcPunctuations: [','], balanceRange: 0 },
|
||
});
|
||
|
||
expect(breakpoints.some((b) => b.kind === 'PUNCT' && b.pos === 4)).toBe(true);
|
||
});
|
||
|
||
it('TC: forbiddenBreakRanges(闭区间)命中必须剔除(含 start/end)', () => {
|
||
const tokens = [t('我', 0), t('好', 1), t('累', 2), t(',', 3), t('😮💨', 4)];
|
||
const { breakpoints } = generateBreakpoints({
|
||
tokens: tokens as any,
|
||
lang: 'TC',
|
||
maxLines: 2,
|
||
constraints: { forbiddenBreakRanges: [{ start: 4, end: 4 }] },
|
||
config: { tcMaxCandidateBreaks: 80, tcPunctuations: [','], balanceRange: 0 },
|
||
});
|
||
|
||
expect(breakpoints.some((b) => b.pos === 4)).toBe(false);
|
||
});
|
||
|
||
it('TC: 去重规则:同 pos 优先保留 priority 更高者;同 priority 按 kind 序', () => {
|
||
// 构造:同一个 pos=2 同时来自 SPACE(priority=20) 和 BALANCE(priority=5),必须保留 SPACE
|
||
const tokens = [t('我', 0), t(' ', 1), t('好', 2)];
|
||
const { breakpoints } = generateBreakpoints({
|
||
tokens: tokens as any,
|
||
lang: 'TC',
|
||
maxLines: 2,
|
||
config: { tcMaxCandidateBreaks: 80, tcPunctuations: [], balanceRange: 2 },
|
||
});
|
||
|
||
const b2 = breakpoints.find((b) => b.pos === 2);
|
||
expect(b2?.kind).toBe('SPACE');
|
||
expect(b2?.priority).toBe(20);
|
||
});
|
||
|
||
it('TC: 裁剪上限生效且输出按 pos 升序(确定性)', () => {
|
||
const tokens = Array.from({ length: 60 }).map((_, i) => t('哈', i));
|
||
const res = generateBreakpoints({
|
||
tokens: tokens as any,
|
||
lang: 'TC',
|
||
maxLines: 3,
|
||
config: { tcMaxCandidateBreaks: 8, tcPunctuations: [], balanceRange: 6 },
|
||
});
|
||
|
||
expect(res.breakpoints.length).toBe(8);
|
||
// pos 升序
|
||
for (let i = 1; i < res.breakpoints.length; i++) {
|
||
expect(res.breakpoints[i]!.pos).toBeGreaterThanOrEqual(res.breakpoints[i - 1]!.pos);
|
||
}
|
||
// 确定性
|
||
const res2 = generateBreakpoints({
|
||
tokens: tokens as any,
|
||
lang: 'TC',
|
||
maxLines: 3,
|
||
config: { tcMaxCandidateBreaks: 8, tcPunctuations: [], balanceRange: 6 },
|
||
});
|
||
expect(res).toEqual(res2);
|
||
});
|
||
|
||
it('边界:N<=1 时不输出任何候选断点', () => {
|
||
const { breakpoints } = generateBreakpoints({
|
||
tokens: [t('我', 0)] as any,
|
||
lang: 'TC',
|
||
maxLines: 2,
|
||
config: { tcMaxCandidateBreaks: 80, tcPunctuations: [','], balanceRange: 3 },
|
||
});
|
||
expect(breakpoints).toEqual([]);
|
||
});
|
||
});
|
||
|