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); // 1200px:4 列((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([]); }); });