// @vitest-environment jsdom // figureLayout.test.js —— 聊天图片布局纯函数(2026-09-01 修订版) // 需求:单图 50% 宽;只要是连续两张图片就并排一行各 50%(不做比例相同限制)。 import { describe, it, expect } from 'vitest'; import { pairFigures, isInlineAdjacent, layoutTextRoot } from '../../../../ui/src/components/chat/figureLayout.js'; const fig = () => document.createElement('figure'); describe('pairFigures 按 2 分块配对', () => { const figs = [fig(), fig(), fig(), fig(), fig()]; it('2 张 → 1 对', () => { const g = pairFigures([figs[0], figs[1]]); expect(g).toHaveLength(1); expect(g[0].row).toBe(true); expect(g[0].figures).toEqual([figs[0], figs[1]]); }); it('3 张 → 1 对 + 1 单(连续即配对,不论比例)', () => { const g = pairFigures([figs[0], figs[1], figs[2]]); expect(g).toHaveLength(2); expect(g[0].row).toBe(true); expect(g[1].row).toBe(false); expect(g[1].figures).toEqual([figs[2]]); }); it('5 张 → 2 对 + 1 单', () => { const g = pairFigures(figs); expect(g).toHaveLength(3); expect(g.map((x) => x.row)).toEqual([true, true, false]); }); it('空列表', () => { expect(pairFigures([])).toEqual([]); }); }); describe('isInlineAdjacent 连续性', () => { it('同一

内仅空白相隔 → 相邻', () => { const p = document.createElement('p'); const a = fig(); const b = fig(); p.append(a, document.createTextNode(' '), b); expect(isInlineAdjacent(a, b)).toBe(true); }); it('相邻兄弟

→ 相邻(markdown-it 逐图成段)', () => { const wrap = document.createElement('div'); const pa = document.createElement('p'); const pb = document.createElement('p'); pa.append(fig()); pb.append(fig()); wrap.append(pa, pb); expect(isInlineAdjacent(pa.firstChild, pb.firstChild)).toBe(true); }); it('HTML 解析器把 figure 提出 p 留下的空

视为空白 → 相邻', () => { const wrap = document.createElement('div'); const a = fig(); const emptyP = document.createElement('p'); const b = fig(); wrap.append(a, emptyP, b); expect(isInlineAdjacent(a, b)).toBe(true); }); it('中间有文字段落 → 不相邻', () => { const wrap = document.createElement('div'); const pa = document.createElement('p'); const pm = document.createElement('p'); const pb = document.createElement('p'); pa.append(fig()); pm.textContent = '说明文字'; pb.append(fig()); wrap.append(pa, pm, pb); expect(isInlineAdjacent(pa.firstChild, pb.firstChild)).toBe(false); }); }); describe('layoutTextRoot 幂等布局', () => { function makeRoot(html) { const root = document.createElement('div'); root.className = 'md-text'; root.innerHTML = html; return root; } it('单图 → half 类,无 row', () => { const root = makeRoot('

'); layoutTextRoot(root); expect(root.querySelectorAll('.figure-row').length).toBe(0); expect(root.querySelector('.md-figure').classList.contains('half')).toBe(true); }); it('连续两张(任意比例)→ 一个 figure-row,无 half', () => { const root = makeRoot( '

' ); layoutTextRoot(root); expect(root.querySelectorAll('.figure-row').length).toBe(1); const row = root.querySelector('.figure-row'); expect(row.children.length).toBe(2); expect(root.querySelectorAll('.md-figure.half').length).toBe(0); }); it('3 张连续 → 1 对 + 1 单', () => { const root = makeRoot( '

' ); layoutTextRoot(root); expect(root.querySelectorAll('.figure-row').length).toBe(1); expect(root.querySelectorAll('.md-figure.half').length).toBe(1); }); it('中间夹文字段落的双图 → 各自 half,无 row', () => { const root = makeRoot( '

分隔文字

' ); layoutTextRoot(root); expect(root.querySelectorAll('.figure-row').length).toBe(0); expect(root.querySelectorAll('.md-figure.half').length).toBe(2); }); it('重复调用幂等(重排前拆旧 row / 清 half)', () => { const root = makeRoot( '

' ); layoutTextRoot(root); expect(root.querySelectorAll('.figure-row').length).toBe(1); layoutTextRoot(root); expect(root.querySelectorAll('.figure-row').length).toBe(1); expect(root.querySelectorAll('.md-figure.half').length).toBe(0); }); it('display:none 的失败图不参与布局', () => { const root = makeRoot( '

' ); layoutTextRoot(root); // 隐藏图被跳过,剩余两张可见图并排 expect(root.querySelectorAll('.figure-row').length).toBe(1); expect(root.querySelectorAll('.md-figure.half').length).toBe(0); }); });