Files
Genarrative/apps/ai-game-creator-shell/tests/templateLibraryGrid.test.ts
T
kdletters 066d0fe6f9
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 6m16s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m56s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 5m17s
Project CI / Backend tests (pull_request) Failing after 9s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Failing after 5m33s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 5m29s
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m40s
Project CI / AI game creator shell web tests (pull_request) Failing after 4m2s
Project CI / Frontend tests (pull_request) Failing after 4m19s
Project CI / Native shell tests (pull_request) Successful in 8m46s
模板库卡片列表接入虚拟滚动
- 用 workspace 已有的 react-window@1.8.11 的 FixedSizeGrid:只渲染可视行 + 2 行 overscan,1000 条不再一次性挂 1000 个卡片节点
- 新增 templateLibraryGrid.ts 纯函数布局契约(列数/列宽/行高/行数/按行切分),单测覆盖
- 卡片抽成 memo 化的 TemplateCard;筛选条件变化时把滚动位置复位到顶部
- 筛选区(运行时/标签)改为可独立滚动区块,标签膨胀不再挤压卡片区
- 页面回归改用固定视口:断言 1000 条只渲染 ≤40 张卡、滚动高度按 250 行计算
- 技术方案补虚拟滚动契约说明
2026-09-17 16:36:04 +08:00

109 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest';
import {
buildTemplateRows,
computeTemplateGridColumns,
computeTemplateGridLayout,
computeTemplateRowHeight,
TEMPLATE_CARD_GAP,
TEMPLATE_CARD_MIN_WIDTH,
TEMPLATE_CARD_TEXT_HEIGHT,
} from '../src/features/template-library/templateLibraryGrid';
import type { GameTemplateEntry } from '../src/features/template-library/templateLibraryModel';
function entry(id: string): GameTemplateEntry {
return {
id,
title: id,
summary: '',
tags: [],
runtime: 'html',
engine: 'phaser',
engineVersion: '4.2.1',
templateVersion: '0.1.0',
updatedAt: '2026-09-17T00:00:00Z',
entry: 'game/index.html',
zipUrl: `https://oss.example/templates/v1/${id}/template.zip`,
zipSizeBytes: 1024,
zipSha256: 'a'.repeat(64),
coverUrl: `https://oss.example/templates/v1/${id}/cover.svg`,
coverWidth: 960,
coverHeight: 540,
installed: false,
installedVersion: null,
installedAtMillis: null,
};
}
describe('computeTemplateGridColumns', () => {
it('fits as many min-width columns as the container allows', () => {
expect(computeTemplateGridColumns(0)).toBe(1);
expect(computeTemplateGridColumns(200)).toBe(1);
expect(computeTemplateGridColumns(TEMPLATE_CARD_MIN_WIDTH)).toBe(1);
// 两列边界:2*250 + 14 = 514
const twoColumnWidth = 2 * TEMPLATE_CARD_MIN_WIDTH + TEMPLATE_CARD_GAP;
expect(computeTemplateGridColumns(twoColumnWidth)).toBe(2);
expect(computeTemplateGridColumns(twoColumnWidth - 1)).toBe(1);
// 1200px4 列((1200+14)/(250+14) = 4.59
expect(computeTemplateGridColumns(1200)).toBe(4);
});
});
describe('computeTemplateRowHeight', () => {
it('keeps cover ratio + fixed text block', () => {
// 列宽 300 → 卡片 286 → 封面 286*9/16 = 160.875 → 161
expect(computeTemplateRowHeight(300)).toBe(
161 + TEMPLATE_CARD_TEXT_HEIGHT + TEMPLATE_CARD_GAP,
);
// 极窄时按最小卡片宽度兜底,避免行高被压成 0
expect(computeTemplateRowHeight(10)).toBeGreaterThan(
TEMPLATE_CARD_TEXT_HEIGHT,
);
});
});
describe('computeTemplateGridLayout', () => {
it('derives columns, row height and row count for a big library', () => {
const layout = computeTemplateGridLayout({
containerWidth: 1200,
itemCount: 1000,
});
expect(layout.columnCount).toBe(4);
expect(layout.columnWidth).toBe(300);
expect(layout.rowHeight).toBe(
161 + TEMPLATE_CARD_TEXT_HEIGHT + TEMPLATE_CARD_GAP,
);
expect(layout.rowCount).toBe(250);
// 虚拟列表只渲染可视行,滚动高度仍由总行数决定
expect(layout.rowCount * layout.rowHeight).toBeGreaterThan(80000);
});
it('handles inline and empty libraries', () => {
expect(
computeTemplateGridLayout({ containerWidth: 0, itemCount: 5 }),
).toEqual({
columnCount: 1,
columnWidth: 1,
rowHeight: computeTemplateRowHeight(1),
rowCount: 5,
});
expect(
computeTemplateGridLayout({ containerWidth: 1200, itemCount: 0 })
.rowCount,
).toBe(0);
});
});
describe('buildTemplateRows', () => {
it('chunks entries per row and pads the tail with nulls', () => {
const rows = buildTemplateRows([entry('a'), entry('b'), entry('c')], 2);
expect(rows).toHaveLength(2);
expect(rows[0]?.map((item) => item?.id)).toEqual(['a', 'b']);
expect(rows[1]?.map((item) => item?.id ?? null)).toEqual(['c', null]);
});
it('returns no rows for an invalid column count', () => {
expect(buildTemplateRows([entry('a')], 0)).toEqual([]);
});
});