46d395763d
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 6m50s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 6m2s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m36s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 6m7s
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 4m56s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m46s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Frontend tests (pull_request) Failing after 4m41s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m48s
Project CI / Native shell tests (pull_request) Successful in 9m39s
- 前端单测迁到 apps/ai-game-creator-shell/tests/,与根 vitest include 一致;新增模板库页面与首页推荐位 8 项渲染/交互测试(卡片封面、标签、已下载徽标、搜索、筛选、下载与使用模板、空态与错误态) - 模板库的运行时与标签筛选补 aria-label,避免同名按钮歧义 - 真连检查扩展为「读线上清单 → 下载安装线上模板 → 据此建项目」的完整链路 - 同步技术方案、里程碑与实施计划的验证命令与验收口径
184 lines
5.4 KiB
TypeScript
184 lines
5.4 KiB
TypeScript
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<GameTemplateEntry> & Pick<GameTemplateEntry, 'id'>,
|
|
): 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');
|
|
});
|
|
});
|