- 实测发现同论文连续图(g001/g002)比例差异达 4.5%/62.5%,比例门禁把真实连续对全部拆散 - 布局改为纯 DOM 结构:连续相邻即配对包 figure-row,奇数最后一张落单 - 移除图片尺寸读取与 load 等待,布局时机简化为 mounted/updated - 测试更新:任意连续两张并排、3 张=1对+1单、空 p 兼容、隐藏失败图跳过 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
131 lines
5.3 KiB
JavaScript
131 lines
5.3 KiB
JavaScript
// @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('同一 <p> 内仅空白相隔 → 相邻', () => {
|
||
const p = document.createElement('p');
|
||
const a = fig();
|
||
const b = fig();
|
||
p.append(a, document.createTextNode(' '), b);
|
||
expect(isInlineAdjacent(a, b)).toBe(true);
|
||
});
|
||
it('相邻兄弟 <p> → 相邻(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 留下的空 <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('<p><figure class="md-figure"><img></figure></p>');
|
||
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(
|
||
'<p><figure class="md-figure"><img></figure></p><p><figure class="md-figure"><img></figure></p>'
|
||
);
|
||
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(
|
||
'<p><figure class="md-figure"><img></figure></p><p><figure class="md-figure"><img></figure></p><p><figure class="md-figure"><img></figure></p>'
|
||
);
|
||
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(
|
||
'<p><figure class="md-figure"><img></figure></p><p>分隔文字</p><p><figure class="md-figure"><img></figure></p>'
|
||
);
|
||
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(
|
||
'<p><figure class="md-figure"><img></figure></p><p><figure class="md-figure"><img></figure></p>'
|
||
);
|
||
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(
|
||
'<p><figure class="md-figure" style="display:none"><img></figure></p><p><figure class="md-figure"><img></figure></p><p><figure class="md-figure"><img></figure></p>'
|
||
);
|
||
layoutTextRoot(root);
|
||
// 隐藏图被跳过,剩余两张可见图并排
|
||
expect(root.querySelectorAll('.figure-row').length).toBe(1);
|
||
expect(root.querySelectorAll('.md-figure.half').length).toBe(0);
|
||
});
|
||
});
|