import { describe, expect, it } from 'vitest'; import { collectGameTemplateRuntimes, collectGameTemplateTags, EMPTY_TEMPLATE_LIBRARY_FILTERS, filterGameTemplates, formatGameTemplateSize, type GameTemplateEntry, isTemplateLibraryFiltersEmpty, needsTemplateDownload, templateMatchesQuery, templateRuntimeLabel, toggleGameTemplateTag, } from '../src/features/template-library/templateLibraryModel'; function template( overrides: Partial & Pick, ): GameTemplateEntry { return { title: '未命名模板', summary: '', tags: [], runtime: 'html', engine: 'phaser', engineVersion: '4.2.1', templateVersion: '1.0.0', updatedAt: '2026-09-17T00:00:00Z', entry: 'game/index.html', zipUrl: 'https://agc-dev.oss-rg-china-mainland.aliyuncs.com/templates/v1/demo/template.zip', zipSizeBytes: 2048, zipSha256: 'a'.repeat(64), coverUrl: 'https://agc-dev.oss-rg-china-mainland.aliyuncs.com/templates/v1/demo/cover.png', coverWidth: 960, coverHeight: 540, installed: false, installedVersion: null, installedAtMillis: null, ...overrides, }; } const matchThree = template({ id: 'match-3', title: '三消经营', summary: '三消与模拟经营的融合模板', tags: ['三消', '经营'], engine: 'phaser', installed: true, installedVersion: '1.0.0', }); const pixelFarm = template({ id: 'pixel-farm', title: '像素农场', summary: '像素风种植玩法', tags: ['经营', '像素'], engine: 'godot', runtime: 'godot', templateVersion: '2.0.0', installed: true, installedVersion: '1.0.0', }); const spaceShooter = template({ id: 'space-shooter', title: '太空射击', summary: '纵版弹幕射击', tags: ['射击'], runtime: 'unity', engine: 'unity', installed: false, }); const templates: GameTemplateEntry[] = [matchThree, pixelFarm, spaceShooter]; describe('templateMatchesQuery', () => { it('matches title, summary, tags and engine case-insensitively', () => { expect(templateMatchesQuery(matchThree, '三消')).toBe(true); expect(templateMatchesQuery(matchThree, '经营')).toBe(true); expect(templateMatchesQuery(matchThree, 'PHASER')).toBe(true); expect(templateMatchesQuery(matchThree, '弹幕')).toBe(false); }); it('requires every whitespace separated term to match', () => { expect(templateMatchesQuery(pixelFarm, '像素 种植')).toBe(true); expect(templateMatchesQuery(pixelFarm, '像素 弹幕')).toBe(false); expect(templateMatchesQuery(pixelFarm, ' ')).toBe(true); }); }); describe('filterGameTemplates', () => { it('filters by tag, runtime, query and installed state together', () => { expect( filterGameTemplates(templates, { ...EMPTY_TEMPLATE_LIBRARY_FILTERS, tags: ['经营'], }).map((entry) => entry.id), ).toEqual(['match-3', 'pixel-farm']); expect( filterGameTemplates(templates, { ...EMPTY_TEMPLATE_LIBRARY_FILTERS, runtime: 'godot', }).map((entry) => entry.id), ).toEqual(['pixel-farm']); expect( filterGameTemplates(templates, { ...EMPTY_TEMPLATE_LIBRARY_FILTERS, installedOnly: true, query: '经营', tags: ['像素'], }).map((entry) => entry.id), ).toEqual(['pixel-farm']); }); it('returns everything when no filter is active', () => { expect( filterGameTemplates(templates, EMPTY_TEMPLATE_LIBRARY_FILTERS), ).toHaveLength(3); expect(isTemplateLibraryFiltersEmpty(EMPTY_TEMPLATE_LIBRARY_FILTERS)).toBe( true, ); expect( isTemplateLibraryFiltersEmpty({ ...EMPTY_TEMPLATE_LIBRARY_FILTERS, query: ' x ', }), ).toBe(false); }); }); describe('tag and runtime options', () => { it('orders tags by frequency and drops blanks', () => { const withBlank = [ ...templates, template({ id: 'blank-tag', tags: ['', ' ', '经营'] }), ]; expect(collectGameTemplateTags(withBlank)).toEqual([ '经营', '三消', '射击', '像素', ]); }); it('collects distinct runtimes and labels them', () => { expect(collectGameTemplateRuntimes(templates)).toEqual([ 'godot', 'html', 'unity', ]); expect(templateRuntimeLabel('html')).toBe('网页'); expect(templateRuntimeLabel('cocos')).toBe('Cocos'); expect(templateRuntimeLabel('')).toBe('未标注运行时'); expect(templateRuntimeLabel('custom-engine')).toBe('custom-engine'); }); it('toggles tags without mutating the previous filters', () => { const next = toggleGameTemplateTag(EMPTY_TEMPLATE_LIBRARY_FILTERS, '经营'); expect(next.tags).toEqual(['经营']); expect(toggleGameTemplateTag(next, '经营').tags).toEqual([]); expect(EMPTY_TEMPLATE_LIBRARY_FILTERS.tags).toEqual([]); }); }); describe('needsTemplateDownload', () => { it('requires a download when missing or when the installed version is stale', () => { expect(needsTemplateDownload(matchThree)).toBe(false); expect(needsTemplateDownload(pixelFarm)).toBe(true); expect(needsTemplateDownload(spaceShooter)).toBe(true); }); }); describe('formatGameTemplateSize', () => { it('formats bytes, kilobytes and megabytes', () => { expect(formatGameTemplateSize(0)).toBe('--'); expect(formatGameTemplateSize(512)).toBe('512 B'); expect(formatGameTemplateSize(2048)).toBe('2.0 KB'); expect(formatGameTemplateSize(5 * 1024 * 1024)).toBe('5.0 MB'); }); });