import { describe, expect, it, vi } from 'vitest'; import type { MeasureWidthImpl } from '../../measure/types'; import { applyOverflowFallback } from '../fallback'; function t(text: string, start: number) { return { text, start, end: start + text.length }; } describe('textWrap overflow-fallback', () => { it('SYSTEM_DEFAULT:仍返回单行,但 meta 标记 SYSTEM_DEFAULT', async () => { const res = await applyOverflowFallback({ tokens: [t('I', 0), t('am', 2), t('tired', 5)], lang: 'EN', context: 'APP', availableWidth: 10, maxLines: 2, overflowMode: 'SYSTEM_DEFAULT', ellipsisToken: '…', reason: 'NO_CANDIDATE', partialLayout: null, }); expect(res.lines.length).toBe(1); expect(res.meta.fallback_type).toBe('SYSTEM_DEFAULT'); expect(res.meta.overflow_type).toBe('NONE'); }); it('CLIP:截断到 maxLines', async () => { const res = await applyOverflowFallback({ tokens: [t('a', 0), t('b', 2), t('c', 4)], lang: 'EN', context: 'APP', availableWidth: 10, maxLines: 2, overflowMode: 'CLIP', ellipsisToken: '…', reason: 'TOO_LONG', partialLayout: { breaks: [1, 2], lines: [ { start: 0, end: 1, text: 'a' }, { start: 1, end: 2, text: 'b' }, // 造一个“未覆盖到 N”的 partial,触发 overflow { start: 2, end: 2, text: 'c' }, ], }, }); expect(res.lines).toEqual(['a', 'b']); expect(res.meta.overflow_type).toBe('CLIP'); }); it('ELLIPSIS:清理末尾空白与 TC 末尾标点(不输出 \" …\" / \",…\")', async () => { const res = await applyOverflowFallback({ tokens: [t('我', 0), t('好', 1), t('累', 2), t(',', 3)], lang: 'TC', context: 'WIDGET', availableWidth: 10, maxLines: 1, overflowMode: 'ELLIPSIS', ellipsisToken: '…', reason: 'NO_CANDIDATE', partialLayout: { breaks: [], // 造一个“未覆盖到 N”的 partial,触发 overflow lines: [{ start: 0, end: 3, text: '我好累, ' }], }, tcPunctuations: [','], }); expect(res.lines[0]).toBe('我好累…'); expect(res.meta.overflow_type).toBe('ELLIPSIS'); }); it('ELLIPSIS:可测量时,超宽则按 token 回退直到不超宽', async () => { // mock:含省略号时宽度=100,否则宽度=文本长度 const impl: MeasureWidthImpl = vi.fn(async ({ text }) => (text.includes('…') ? 100 : text.length)); const res = await applyOverflowFallback({ tokens: [t('I', 0), t('am', 2), t('so', 5), t('tired', 8)], lang: 'EN', context: 'APP', availableWidth: 10, maxLines: 1, overflowMode: 'ELLIPSIS', ellipsisToken: '…', reason: 'NO_CANDIDATE', partialLayout: { breaks: [], // 造一个“未覆盖到 N”的 partial,触发 overflow lines: [{ start: 0, end: 3, text: 'I am so' }], }, measure: { context: 'APP', contextProfile: 'APP|ios|ellipsis', fontSpec: { fontFamily: 'PingFangSC-Regular', fontWeight: '400', fontSize: 16 }, measureWidthImpl: impl, }, }); // 因为任何带 ellipsis 的测量都超宽,这里会一路回退到空串 + ellipsis expect(res.lines[0]).toBe('…'); expect(res.meta.overflow_type).toBe('ELLIPSIS'); }); });