Files
Genarrative/src/components/creation-home/showcaseMasonryLayout.test.ts
T
kdletters 080dca8c4e
Project CI / Repository checks (push) Successful in 46s
Project CI / Frontend tests (push) Successful in 49s
Project CI / Native shell tests (push) Successful in 2m35s
Project CI / Backend tests (push) Successful in 3m7s
实现精选顺序分列瀑布流
按容器宽度循环分配精选卡片到三列、两列或单列
使用 ResizeObserver 动态重排并保留 Grid 安全回退
补充布局临界值、分页追加和样式回归测试
同步创作主页文档与项目共享决策
2026-07-22 20:30:53 +08:00

154 lines
4.4 KiB
TypeScript

/* @vitest-environment jsdom */
import { afterEach, describe, expect, it } from 'vitest';
import {
applySequentialShowcaseMasonry,
createSequentialShowcaseMasonryLayout,
resetSequentialShowcaseMasonry,
resolveShowcaseMasonryColumnCount,
} from './showcaseMasonryLayout';
function createRect(width: number, height: number): DOMRect {
return {
bottom: height,
height,
left: 0,
right: width,
top: 0,
width,
x: 0,
y: 0,
toJSON: () => ({}),
};
}
describe('showcase masonry layout', () => {
afterEach(() => {
document.body.replaceChildren();
});
it('assigns every group to columns in stable round-robin order', () => {
const layout = createSequentialShowcaseMasonryLayout({
containerWidth: 1296,
gap: 16,
itemHeights: [600, 300, 450, 200, 400, 250],
});
expect(layout.columnCount).toBe(3);
expect(layout.items.map((item) => item.columnIndex)).toEqual([
0, 1, 2, 0, 1, 2,
]);
expect(layout.items.map((item) => item.top)).toEqual([
0, 0, 0, 616, 316, 466,
]);
expect(layout.columnHeights).toEqual([816, 716, 716]);
expect(layout.containerHeight).toBe(816);
});
it('derives three, two, or one column from the actual container width', () => {
expect(
resolveShowcaseMasonryColumnCount({
containerWidth: 590.71,
gap: 14.72,
}),
).toBe(1);
expect(
resolveShowcaseMasonryColumnCount({
containerWidth: 590.72,
gap: 14.72,
}),
).toBe(2);
expect(
resolveShowcaseMasonryColumnCount({
containerWidth: 893.43,
gap: 14.72,
}),
).toBe(2);
expect(
resolveShowcaseMasonryColumnCount({
containerWidth: 893.44,
gap: 14.72,
}),
).toBe(3);
expect(
resolveShowcaseMasonryColumnCount({ containerWidth: 1600, gap: 14.72 }),
).toBe(3);
});
it('applies and resets absolute card positions without changing DOM order', () => {
const container = document.createElement('div');
container.className = 'creation-landing__asset-waterfall';
container.style.columnGap = '16px';
container.getBoundingClientRect = () => createRect(900, 0);
const heights = [600, 300, 450, 200, 400, 250];
const cards = heights.map((height, index) => {
const card = document.createElement('article');
card.className = 'creation-landing__asset-card';
card.textContent = `card-${index + 1}`;
card.getBoundingClientRect = () =>
createRect(Number.parseFloat(card.style.width) || 0, height);
container.append(card);
return card;
});
document.body.append(container);
const layout = applySequentialShowcaseMasonry(container);
expect(layout?.columnCount).toBe(3);
expect(container.dataset.masonryColumns).toBe('3');
expect(cards.map((card) => card.dataset.masonryColumn)).toEqual([
'1',
'2',
'3',
'1',
'2',
'3',
]);
expect(cards.map((card) => card.style.top)).toEqual([
'0px',
'0px',
'0px',
'616px',
'316px',
'466px',
]);
expect(
Array.from(container.children).map((card) => card.textContent),
).toEqual(['card-1', 'card-2', 'card-3', 'card-4', 'card-5', 'card-6']);
resetSequentialShowcaseMasonry(container);
expect(container.dataset.masonryColumns).toBeUndefined();
expect(container.style.height).toBe('');
expect(cards.every((card) => card.style.position === '')).toBe(true);
expect(cards.every((card) => card.style.top === '')).toBe(true);
});
it('keeps the flow fallback until every expected card has a valid height', () => {
const container = document.createElement('div');
container.className = 'creation-landing__asset-waterfall';
container.getBoundingClientRect = () => createRect(900, 0);
const card = document.createElement('article');
card.className = 'creation-landing__asset-card';
card.getBoundingClientRect = () => createRect(288, 0);
container.append(card);
document.body.append(container);
expect(
applySequentialShowcaseMasonry(container, { expectedItemCount: 2 }),
).toBeNull();
expect(
container.classList.contains(
'creation-landing__asset-waterfall--masonry',
),
).toBe(false);
expect(
applySequentialShowcaseMasonry(container, { expectedItemCount: 1 }),
).toBeNull();
expect(container.style.height).toBe('');
expect(card.style.position).toBe('');
});
});