Files
Genarrative/apps/ai-game-creator-shell/tests/runtimeInspectResourceMatch.test.ts
T
suzmii 6264fed6b4 运行画面点选结果收敛到 manifest 现有素材 id
- 新增匹配纯函数:显式 id、精确路径后缀、文件名、序列帧地址四档,未知值一律丢弃
- 恰好命中一个素材且标签由文件名派生时,芯片名归一化为资源显示名
- 点选处理改为调用该纯函数,派发载荷字段与顺序不变
- 新增匹配与标签归一化的单元用例
2026-10-04 20:50:26 +08:00

279 lines
8.3 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 type { GameCreationAppAssetManifestEntry } from '../../../packages/shared/src/contracts/gameCreationApp';
import {
matchRuntimeInspectResources,
resolveRuntimeInspectLabel,
} from '../src/features/project-workspace/runtimeInspectResourceMatch';
function asset(
id: string,
localPath: string,
frameSrcs: string[] = [],
): GameCreationAppAssetManifestEntry {
return {
id,
kind: 'character',
mediaType: 'image/png',
localPath,
source: { kind: 'uploaded' },
imageSequenceFrames: frameSrcs.map((imageSrc) => ({
imageSrc,
width: 32,
height: 32,
})),
};
}
const hero = asset('hero', 'assets/hero.png');
const enemy = asset('enemy', 'assets/enemy.png');
const manifest = [hero, enemy];
describe('运行画面点选 → manifest 素材 id', () => {
it('显式 id 优先:登记在 manifest 的按输入顺序保留,未知 id 一律丢弃', () => {
expect(
matchRuntimeInspectResources({
assets: manifest,
resourceIds: ['ghost', 'hero'],
sourcePath: '/assets/enemy.png',
}),
).toEqual({ resourceIds: ['hero', 'enemy'] });
// 全是未知 id:宁可给空,也不能把值透传给 Rust(未知 id 会让整条引用被拒)。
expect(
matchRuntimeInspectResources({
assets: manifest,
resourceIds: ['ghost'],
}),
).toEqual({ resourceIds: [] });
});
it('精确后缀匹配:深层路径以段边界命中短 localPath', () => {
expect(
matchRuntimeInspectResources({
assets: [asset('hero', 'game/public/assets/hero.png')],
sourcePath: '/assets/hero.png',
}),
).toEqual({ resourceIds: ['hero'] });
// `/` 与 `\` 都是分隔符,两侧混用同样按段比较(大小写不敏感)。
expect(
matchRuntimeInspectResources({
assets: [asset('hero', 'game\\public\\assets\\hero.png')],
sourcePath: '/ASSETS/HERO.PNG',
}),
).toEqual({ resourceIds: ['hero'] });
});
it('段边界不误配:后缀档先于文件名档,`minigame/hero.png` 不当作 `game/hero.png` 的后缀', () => {
expect(
matchRuntimeInspectResources({
assets: [
asset('mini', 'minigame/hero.png'),
asset('game', 'game/hero.png'),
],
sourcePath: '/game/hero.png',
}),
).toEqual({ resourceIds: ['game', 'mini'] });
expect(
matchRuntimeInspectResources({
assets: [asset('a', 'a/hero.png'), asset('ab', 'ab/hero.png')],
sourcePath: '/ab/hero.png',
}),
).toEqual({ resourceIds: ['ab', 'a'] });
});
it('重名素材命中多条时全部返回,不静默只取一条', () => {
expect(
matchRuntimeInspectResources({
assets: [
asset('first', 'art/a/hero.png'),
asset('second', 'art/b/hero.png'),
],
sourcePath: '/other/hero.png',
}),
).toEqual({ resourceIds: ['first', 'second'] });
});
it('序列帧 `imageSrc` 与 `localPath` 同权参与后缀与文件名匹配', () => {
const walk = asset('walk', 'assets/hero.png', ['assets/walk/frame_01.png']);
expect(
matchRuntimeInspectResources({
assets: [walk],
sourcePath: '/game/assets/walk/frame_01.png',
}),
).toEqual({ resourceIds: ['walk'] });
expect(
matchRuntimeInspectResources({
assets: [walk],
sourcePath: '/elsewhere/frame_01.png',
}),
).toEqual({ resourceIds: ['walk'] });
// 序列帧不命中时不能靠 localPath 蹭上:这里文件名与帧地址都不同。
expect(
matchRuntimeInspectResources({
assets: [walk],
sourcePath: '/assets/other.png',
}),
).toEqual({ resourceIds: [] });
});
it('无匹配时返回空数组', () => {
expect(
matchRuntimeInspectResources({
assets: manifest,
sourcePath: '/assets/missing.png',
}),
).toEqual({ resourceIds: [] });
expect(matchRuntimeInspectResources({ assets: manifest })).toEqual({
resourceIds: [],
});
expect(
matchRuntimeInspectResources({
assets: [],
sourcePath: '/assets/hero.png',
}),
).toEqual({ resourceIds: [] });
});
it('显式 id 与路径命中撞上同一素材时去重', () => {
expect(
matchRuntimeInspectResources({
assets: manifest,
resourceIds: ['hero', 'hero'],
sourcePath: '/assets/hero.png',
}),
).toEqual({ resourceIds: ['hero'] });
});
});
describe('运行画面点选载荷的 label', () => {
// 引擎档证据:Phaser 4 loader 走 XHR + blob URL,贴图 URL 读不回来,桥的 label 会落到
// `texture.key`(`decoy`)或常量 `'canvas'`。唯一命中素材时这两类都要按 manifest 显示名定名。
it('引擎档(elementTag=canvas)唯一命中素材时,无论 label 是不是文件名派生,都归一化成资源显示名', () => {
expect(
resolveRuntimeInspectLabel({
assets: manifest,
resourceIds: ['enemy'],
sourcePath: '/assets/decoy.png',
label: 'decoy',
elementTag: 'canvas',
}),
).toBe('enemy');
// 贴图 URL 读不回来(sourcePath 缺失)时同样定名。
expect(
resolveRuntimeInspectLabel({
assets: manifest,
resourceIds: ['enemy'],
label: 'decoy',
elementTag: 'canvas',
}),
).toBe('enemy');
expect(
resolveRuntimeInspectLabel({
assets: manifest,
resourceIds: ['hero'],
label: 'canvas',
elementTag: 'canvas',
}),
).toBe('hero');
});
it('label 是文件名派生的词干时归一化(含空白折 `-`、大小写取 manifest 的 localPath)', () => {
expect(
resolveRuntimeInspectLabel({
assets: manifest,
resourceIds: ['hero'],
sourcePath: '/assets/hero.png',
label: 'hero',
elementTag: 'canvas',
}),
).toBe('hero');
expect(
resolveRuntimeInspectLabel({
assets: [asset('hero-v2', 'assets/hero v2.png')],
resourceIds: ['hero-v2'],
sourcePath: '/assets/hero v2.png',
label: 'hero v2',
elementTag: 'canvas',
}),
).toBe('hero-v2');
expect(
resolveRuntimeInspectLabel({
assets: manifest,
resourceIds: ['hero'],
sourcePath: '/assets/Hero.PNG',
label: 'Hero',
elementTag: 'canvas',
}),
).toBe('hero');
// 非 canvas 的选点(DOM 档)恰好在媒体 `src` 上、且 label 逐字等于文件名时,同样归一化。
expect(
resolveRuntimeInspectLabel({
assets: manifest,
resourceIds: ['hero'],
sourcePath: '/assets/Hero.PNG',
label: 'Hero',
elementTag: 'img',
}),
).toBe('hero');
});
it('DOM 档的元素文本 / 标签不是文件名派生时原样保留(DOM 点选行为逐字不变)', () => {
expect(
resolveRuntimeInspectLabel({
assets: manifest,
resourceIds: ['hero'],
sourcePath: '/assets/hero.png',
label: '开始按钮',
elementTag: 'button',
}),
).toBe('开始按钮');
expect(
resolveRuntimeInspectLabel({
assets: manifest,
resourceIds: ['hero'],
sourcePath: '/assets/hero.png',
label: 'img',
elementTag: 'img',
}),
).toBe('img');
expect(
resolveRuntimeInspectLabel({
assets: manifest,
resourceIds: ['hero'],
label: 'HUD 区域',
elementTag: 'div',
}),
).toBe('HUD 区域');
});
it('匹配不到唯一素材时不改 label', () => {
expect(
resolveRuntimeInspectLabel({
assets: [asset('first', 'a/hero.png'), asset('second', 'b/hero.png')],
resourceIds: ['first', 'second'],
sourcePath: '/other/hero.png',
label: 'Hero',
elementTag: 'canvas',
}),
).toBe('Hero');
expect(
resolveRuntimeInspectLabel({
assets: manifest,
resourceIds: [],
sourcePath: '/assets/hero.png',
label: 'hero',
elementTag: 'canvas',
}),
).toBe('hero');
expect(
resolveRuntimeInspectLabel({
assets: manifest,
resourceIds: ['ghost'],
sourcePath: '/assets/hero.png',
label: 'hero',
elementTag: 'canvas',
}),
).toBe('hero');
});
});