资源 kind 用例与 fixture 跟随严格口径(非 canonical 值一律停在待归类)
- packages/shared/src/contracts/gameCreationApp.test.ts:新增严格解析用例(认不出的原值 console.warn 留痕、返回 unknown),分类映射与自愈用例改用 canonical ui-design / icon,并断言 kind:'ui' 收口成 unknown 后落 unclassified。 - tests/resourceTypePanel.test.tsx / tests/resourceClassificationPanel.test.tsx:自愈用例的 fixture kind 由 ui 改为 ui-design,让「显示口径 ≠ 落盘口径」这条判据继续真的被验证。 - tests/projectResourceLiveIntegration.test.tsx:fixture kind 改为 ui-design;用例按 canonical UI 设计图带「(待视觉验收)」的标签定位按钮,并把用例名与注释改回真实口径。 - tests/resourceCardPreviewRealManifest.test.ts:真机 61 条资产的取证结论改为严格口径——57 条 ui 与 game-entry 停在待归类,明确栏目只由落盘 category 决定;自愈断言改用 ui-design。 - tests/appSurface/home.suite.ts / project-development.suite.ts:fixture 里的 art-spritesheet / game-background / animation / ui-prototype / ui 改为 canonical icon-spritesheet / scene / character-animation / ui-design / icon。 - tests/projectResourceProjectionModel.test.ts / projectResourceLiveUpdateModel.test.ts:fixture kind 由 game-entry / asset 改为 code / image。
This commit is contained in:
@@ -227,7 +227,7 @@ export function registerClientHomeTests() {
|
||||
assets: [
|
||||
{
|
||||
id: 'live-hero',
|
||||
kind: 'art-spritesheet',
|
||||
kind: 'icon-spritesheet',
|
||||
mediaType: 'image/png',
|
||||
localPath: 'assets/live-hero.png',
|
||||
source: { kind: 'generated' as const, taskId: 'art-asset-plan' },
|
||||
@@ -375,7 +375,7 @@ export function registerClientHomeTests() {
|
||||
assets: [
|
||||
{
|
||||
id: 'runtime-live-hero',
|
||||
kind: 'art-spritesheet',
|
||||
kind: 'icon-spritesheet',
|
||||
mediaType: 'image/png',
|
||||
localPath: 'assets/runtime-live-hero.png',
|
||||
source: {
|
||||
@@ -538,7 +538,7 @@ export function registerClientHomeTests() {
|
||||
assets: [
|
||||
{
|
||||
id: 'stale-first-asset',
|
||||
kind: 'art-spritesheet',
|
||||
kind: 'icon-spritesheet',
|
||||
mediaType: 'image/png',
|
||||
localPath: 'assets/stale-first.png',
|
||||
source: { kind: 'generated' as const },
|
||||
@@ -552,7 +552,7 @@ export function registerClientHomeTests() {
|
||||
secondManifest.assets = [
|
||||
{
|
||||
id: 'second-asset',
|
||||
kind: 'art-spritesheet',
|
||||
kind: 'icon-spritesheet',
|
||||
mediaType: 'image/png',
|
||||
localPath: 'assets/second.png',
|
||||
source: { kind: 'generated' },
|
||||
@@ -2346,7 +2346,7 @@ export function registerHomeProjectCreationTests() {
|
||||
},
|
||||
{
|
||||
id: 'direct-game-background',
|
||||
kind: 'game-background',
|
||||
kind: 'scene',
|
||||
mediaType: 'image/png',
|
||||
localPath: 'assets/direct-game-background.png',
|
||||
source: {
|
||||
@@ -2356,7 +2356,7 @@ export function registerHomeProjectCreationTests() {
|
||||
},
|
||||
{
|
||||
id: 'direct-art-spritesheet',
|
||||
kind: 'art-spritesheet',
|
||||
kind: 'icon-spritesheet',
|
||||
mediaType: 'image/png',
|
||||
localPath: 'assets/art-spritesheet.png',
|
||||
source: {
|
||||
|
||||
@@ -2728,7 +2728,9 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
assets: [
|
||||
{
|
||||
id: 'imported-image',
|
||||
kind: 'ui',
|
||||
// canonical `icon` → 「UI 交互」:本用例钉的是"同数量 manifest 更新后栏目跟着变",
|
||||
// 不掺 UI 设计图「(待视觉验收)」标签那套,所以选一个不会带来额外后缀的 kind。
|
||||
kind: 'icon',
|
||||
mediaType: 'image/png',
|
||||
localPath: 'assets/uploads/local-imported-image.png',
|
||||
source: {
|
||||
@@ -3913,7 +3915,7 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
},
|
||||
{
|
||||
id: 'art-video',
|
||||
kind: 'animation',
|
||||
kind: 'character-animation',
|
||||
mediaType: 'video/mp4',
|
||||
localPath: 'assets/intro.mp4',
|
||||
source: { kind: 'generated' },
|
||||
@@ -4056,8 +4058,8 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
// 视频(animation → character-animation)落在「角色与对象」,
|
||||
// 音频(bgm 不在 canonical 目录里)落在「待归类」。
|
||||
// 视频(canonical `character-animation`)落在「角色与对象」,
|
||||
// 音频(`bgm` 不是 canonical kind)落在「待归类」。
|
||||
await openResourceBookCategory('角色与对象');
|
||||
fireEvent.click(
|
||||
screen.getByRole('button', {
|
||||
@@ -5668,17 +5670,17 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
);
|
||||
manifest.assets.push(
|
||||
{
|
||||
id: 'ui-prototype-first-by-label',
|
||||
kind: 'ui-prototype',
|
||||
id: 'ui-design-last-by-label',
|
||||
kind: 'ui-design',
|
||||
mediaType: 'image/png',
|
||||
localPath: 'assets/a-ui-prototype.png',
|
||||
localPath: 'assets/a-ui-design.png',
|
||||
source: { kind: 'generated', taskId: 'design-foundation' },
|
||||
},
|
||||
{
|
||||
id: 'art-spritesheet-last-by-label',
|
||||
kind: 'art-spritesheet',
|
||||
id: 'icon-spritesheet-first-by-label',
|
||||
kind: 'icon-spritesheet',
|
||||
mediaType: 'image/png',
|
||||
localPath: 'assets/z-art-spritesheet.png',
|
||||
localPath: 'assets/z-icon-spritesheet.png',
|
||||
source: { kind: 'generated', taskId: 'art-asset-plan' },
|
||||
},
|
||||
);
|
||||
@@ -5749,12 +5751,12 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
expect(typePositions).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
resourceId: 'asset:art-spritesheet-last-by-label',
|
||||
resourceId: 'asset:icon-spritesheet-first-by-label',
|
||||
x: 0,
|
||||
y: 0,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
resourceId: 'asset:ui-prototype-first-by-label',
|
||||
resourceId: 'asset:ui-design-last-by-label',
|
||||
x: RESOURCE_CANVAS_CARD_WIDTH + RESOURCE_CANVAS_COLUMN_GAP,
|
||||
y: 0,
|
||||
}),
|
||||
|
||||
@@ -226,9 +226,9 @@ function ClassificationWorkbench() {
|
||||
},
|
||||
{
|
||||
id: 'asset-ui',
|
||||
// `ui` 不在 canonical 目录里:`kind` 派生值经别名落到 `ui-interaction`,
|
||||
// 而落盘值仍是 `unclassified`。用来钉住"角标与栏目都走读显示口径"。
|
||||
kind: 'ui',
|
||||
// canonical `ui-design` 派生 `ui-interaction`,而落盘值仍是 `unclassified`:
|
||||
// 用来钉住"角标与栏目都走读显示口径"(写 `ui` 只会被收口成 `unknown`,不再派生)。
|
||||
kind: 'ui-design',
|
||||
category: 'unclassified',
|
||||
mediaType: 'image/png',
|
||||
localPath: 'assets/panel.png',
|
||||
@@ -1874,8 +1874,10 @@ describe('project resource live canvas integration', () => {
|
||||
*
|
||||
* 两条判据缺一不可:
|
||||
* - `asset-hero`(落盘 `character`):角标「角色与对象」,不可能是媒体类型「图片」;
|
||||
* - `asset-ui`(落盘 `unclassified` + `kind:"ui"`):读显示口径自愈成 `ui-interaction`,
|
||||
* 所以角标与栏目都是「UI 交互」——不是落盘值推出的「待归类」。
|
||||
* - `asset-ui`(落盘 `unclassified` + canonical `kind:"ui-design"`):读显示口径自愈成
|
||||
* `ui-interaction`,所以角标与栏目都是「UI 交互」——不是落盘值推出的「待归类」。
|
||||
* 它的 `kind` 是 canonical UI 设计图,`design-foundation` 未完成时卡片标签带
|
||||
* 「(待视觉验收)」,定位按钮时要带上这一截。
|
||||
*
|
||||
* 变异验证:把角标改回媒体类型口径(`projectResourceTypeLabel(resource)`),
|
||||
* 两条都会变红(分别是「图片」与「图片」)。
|
||||
@@ -1888,7 +1890,7 @@ describe('project resource live canvas integration', () => {
|
||||
expect(await cardBadgeText('hero.png')).toBe('角色与对象');
|
||||
|
||||
await openResourceBookCategory('UI 交互');
|
||||
expect(await cardBadgeText('panel.png')).toBe('UI 交互');
|
||||
expect(await cardBadgeText('panel.png(待视觉验收)')).toBe('UI 交互');
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -2025,20 +2027,21 @@ describe('project resource live canvas integration', () => {
|
||||
});
|
||||
|
||||
/**
|
||||
* 只改标签不许动分类(验收判据里的"逐字不变"):落盘 `unclassified` + `kind:"ui"` 的资产
|
||||
* 在读显示口径下自愈成「UI 交互」,用户在标签面板里加一个标签后,manifest 上的 `category`
|
||||
* 必须还是 `unclassified` —— 真机上这条资产正因回写自愈值同时出现过两种落盘值。
|
||||
* 只改标签不许动分类(验收判据里的"逐字不变"):落盘 `unclassified` + canonical
|
||||
* `kind:"ui-design"` 的资产在读显示口径下自愈成「UI 交互」,用户在标签面板里加一个标签后,
|
||||
* manifest 上的 `category` 必须还是 `unclassified` —— 真机上这条资产正因回写自愈值同时
|
||||
* 出现过两种落盘值。
|
||||
*
|
||||
* 变异验证(已实测):把标签面板的载荷改回读显示口径
|
||||
* (`gameCreationAppAssetCategory(asset)`),manifest 上的值会变成 `ui-interaction`,
|
||||
* 本用例必须失败。
|
||||
*/
|
||||
it('只改标签后 manifest 的 category 逐字不变(落盘 unclassified + kind ui 的资产)', async () => {
|
||||
it('只改标签后 manifest 的 category 逐字不变(落盘 unclassified 的 UI 设计图资产)', async () => {
|
||||
const { invoke } = installTauri();
|
||||
render(<ClassificationWorkbench />);
|
||||
|
||||
await openResourceBookCategory('UI 交互');
|
||||
fireEvent.click(await findResourceSelectButton('panel.png'));
|
||||
fireEvent.click(await findResourceSelectButton('panel.png(待视觉验收)'));
|
||||
const toolbar = await screen.findByRole('toolbar', { name: '图片工具栏' });
|
||||
fireEvent.click(within(toolbar).getByRole('button', { name: '编辑标签' }));
|
||||
const dialog = await screen.findByRole('dialog', { name: '编辑素材标签' });
|
||||
|
||||
@@ -24,7 +24,7 @@ function manifest(projectId: string, assetIds: string[] = []) {
|
||||
tasks: [],
|
||||
assets: assetIds.map((id) => ({
|
||||
id,
|
||||
kind: 'asset',
|
||||
kind: 'image',
|
||||
mediaType: 'image/png',
|
||||
localPath: `assets/${id}.png`,
|
||||
source: { kind: 'canvas' as const, taskId: null },
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('项目资源投影', () => {
|
||||
manifest.assets = [
|
||||
{
|
||||
id: 'game-entry',
|
||||
kind: 'game-entry',
|
||||
kind: 'code',
|
||||
mediaType: 'text/html',
|
||||
localPath: 'game/index.html',
|
||||
source: { kind: 'generated' },
|
||||
|
||||
@@ -118,7 +118,7 @@ describe('真机 manifest 取证:占位卡片计数与栏目分布', () => {
|
||||
expect(resources).toHaveLength(61);
|
||||
});
|
||||
|
||||
test('存量 57 条 ui 的落盘 unclassified 被读时重派生自愈', () => {
|
||||
test('存量 57 条 ui 的落盘 unclassified 停在「待归类」:kind 只接受 canonical,不归一也不迁移', () => {
|
||||
const assets = realManifest().assets;
|
||||
const uiAssets = assets.filter((asset) => asset.kind === 'ui');
|
||||
|
||||
@@ -127,26 +127,47 @@ describe('真机 manifest 取证:占位卡片计数与栏目分布', () => {
|
||||
expect(uiAssets.every((asset) => asset.category === 'unclassified')).toBe(
|
||||
true,
|
||||
);
|
||||
// 读取侧已经把它重派生成 ui-interaction:读时自愈,不回写 manifest。
|
||||
// `ui` 不是 canonical kind → 收口成 `unknown` → 派生分类仍是 unclassified:
|
||||
// 读时自愈对这类存量**不会触发**,它们继续留在「待归类」,由 `app_log!` 的
|
||||
// 原始串留痕去定位写入方(无别名表、无迁移)。
|
||||
expect(
|
||||
uiAssets.every(
|
||||
(asset) => gameCreationAppAssetCategory(asset) === 'ui-interaction',
|
||||
(asset) => gameCreationAppAssetCategory(asset) === 'unclassified',
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
// 直接钉住「读时重派生」这一行为本身:落盘 unclassified + kind 能派生明确分类
|
||||
// → 用派生值;这正是修复前那 57 条留在「待归类」的原因。
|
||||
// 读时自愈本身仍然成立,但只对 canonical kind 生效:落盘 unclassified +
|
||||
// canonical kind 能派生明确分类 → 用派生值。
|
||||
expect(
|
||||
gameCreationAppAssetCategory({ kind: 'ui', category: 'unclassified' }),
|
||||
gameCreationAppAssetCategory({
|
||||
kind: 'ui-design',
|
||||
category: 'unclassified',
|
||||
}),
|
||||
).toBe('ui-interaction');
|
||||
// 落盘值本身就是明确分类时仍然信任落盘值(用户手动设置的权威性)。
|
||||
expect(
|
||||
gameCreationAppAssetCategory({ kind: 'ui', category: 'scene' }),
|
||||
gameCreationAppAssetCategory({ kind: 'ui-design', category: 'scene' }),
|
||||
).toBe('scene');
|
||||
// 落盘值缺失时按 kind 派生(历史 manifest 兼容)。
|
||||
// 落盘值缺失时按 canonical kind 派生。
|
||||
expect(
|
||||
gameCreationAppAssetCategory({ kind: 'ui', category: undefined }),
|
||||
gameCreationAppAssetCategory({
|
||||
kind: 'ui-design',
|
||||
category: undefined,
|
||||
}),
|
||||
).toBe('ui-interaction');
|
||||
// 非 canonical 原值不查别名表:一律 unknown → unclassified。
|
||||
for (const raw of [
|
||||
'ui',
|
||||
'UI',
|
||||
'ui-prototype',
|
||||
'art-spritesheet',
|
||||
'game-background',
|
||||
'game-entry',
|
||||
]) {
|
||||
expect(
|
||||
gameCreationAppAssetCategory({ kind: raw, category: 'unclassified' }),
|
||||
).toBe('unclassified');
|
||||
}
|
||||
});
|
||||
|
||||
test('落盘 unclassified 且 kind 派生也是 unclassified 时不受影响', () => {
|
||||
@@ -159,16 +180,18 @@ describe('真机 manifest 取证:占位卡片计数与栏目分布', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('读时重派生后:57 条 ui 归位 UI 交互,待归类只剩 code 类 game-entry', () => {
|
||||
test('严格口径下:58 条非 canonical kind 停在待归类,明确栏目只由落盘 category 决定', () => {
|
||||
const resources = projectResourcesFromReadModels(realManifest(), [], []);
|
||||
const byCategory = countBy(resources, (resource) =>
|
||||
projectResourceCanvasCategory(resource),
|
||||
);
|
||||
|
||||
// 存量自愈后的真实分布:57 条 ui 归位到 UI 交互,只剩 game-entry(code)在待归类。
|
||||
// ui ×57、game-entry ×1 的 kind 都认不出 → unknown → unclassified;
|
||||
// art-spritesheet / game-background 虽然 kind 认不出,但落盘 category 是明确值,
|
||||
// 读显示口径信任落盘值,因此仍留在各自栏目。
|
||||
expect(byCategory).toEqual({
|
||||
unclassified: 1,
|
||||
'ui-interaction': 59,
|
||||
unclassified: 58,
|
||||
'ui-interaction': 2,
|
||||
scene: 1,
|
||||
});
|
||||
const uiResources = resources.filter(
|
||||
@@ -178,18 +201,9 @@ describe('真机 manifest 取证:占位卡片计数与栏目分布', () => {
|
||||
expect(
|
||||
uiResources.every(
|
||||
(resource) =>
|
||||
projectResourceCanvasCategory(resource) === 'ui-interaction',
|
||||
projectResourceCanvasCategory(resource) === 'unclassified',
|
||||
),
|
||||
).toBe(true);
|
||||
// 唯一留在待归类的就是 game-entry:code → unclassified 是分类表的设计口径。
|
||||
expect(
|
||||
resources
|
||||
.filter(
|
||||
(resource) =>
|
||||
projectResourceCanvasCategory(resource) === 'unclassified',
|
||||
)
|
||||
.map((resource) => resource.subtype),
|
||||
).toEqual(['game-entry']);
|
||||
});
|
||||
|
||||
test('按预览分支计数:52 张 PNG 走图片分支,8 条 UI 规格走文档分支,无一落占位', () => {
|
||||
@@ -234,15 +248,15 @@ describe('真机 manifest 取证:占位卡片计数与栏目分布', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('按画布栏目计数:待归类从 58 降到 1,UI 交互升到 59', () => {
|
||||
test('按画布栏目计数:非 canonical kind 全部停在待归类(待归类 58 / UI 交互 2 / 场景 1)', () => {
|
||||
const resources = projectResourcesFromReadModels(realManifest(), [], []);
|
||||
const byCategory = countBy(resources, (resource) =>
|
||||
projectResourceCanvasCategory(resource),
|
||||
);
|
||||
|
||||
expect(byCategory).toEqual({
|
||||
unclassified: 1,
|
||||
'ui-interaction': 59,
|
||||
unclassified: 58,
|
||||
'ui-interaction': 2,
|
||||
scene: 1,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -250,14 +250,14 @@ describe('ResourceClassificationPanel 编辑素材标签', () => {
|
||||
return { revision: 7 };
|
||||
}
|
||||
return {
|
||||
asset: { ...asset, kind: 'ui', category: 'unclassified' },
|
||||
asset: { ...asset, kind: 'ui-design', category: 'unclassified' },
|
||||
committedProjectRevision: 8,
|
||||
};
|
||||
});
|
||||
renderPanel({
|
||||
asset: {
|
||||
...asset,
|
||||
kind: 'ui',
|
||||
kind: 'ui-design',
|
||||
category: 'unclassified',
|
||||
tags: [],
|
||||
},
|
||||
@@ -476,13 +476,14 @@ describe('ResourceClassificationPanel 编辑素材标签', () => {
|
||||
*/
|
||||
describe('ResourceClassificationPanel 不再编辑素材类型', () => {
|
||||
/**
|
||||
* 落盘 `unclassified` 而 `kind:"ui"` 能派生出 `ui-interaction`:这是读时自愈的触发条件,
|
||||
* 也正是"显示值 ≠ 落盘值"的现场。真机 57 条 `kind:"ui"` 资产就是这个形状。
|
||||
* 落盘 `unclassified` 而 canonical `kind:"ui-design"` 能派生出 `ui-interaction`:这是读时
|
||||
* 自愈的触发条件,也正是"显示值 ≠ 落盘值"的现场。kind 只接受 canonical 值——写 `"ui"`
|
||||
* 会被收口成 `unknown`,派生仍是 `unclassified`,自愈不触发。
|
||||
*/
|
||||
const selfHealingAsset: GameCreationAppAssetManifestEntry = {
|
||||
...asset,
|
||||
id: 'asset-ui',
|
||||
kind: 'ui',
|
||||
kind: 'ui-design',
|
||||
category: 'unclassified',
|
||||
tags: [],
|
||||
};
|
||||
|
||||
@@ -31,13 +31,16 @@ const asset: GameCreationAppAssetManifestEntry = {
|
||||
};
|
||||
|
||||
/**
|
||||
* 落盘 `unclassified` 而 `kind:"ui"` 能派生出 `ui-interaction`:这是读时自愈的触发条件,
|
||||
* 也正是"选择器显示值 ≠ 落盘值"的现场(真机 57 条 `kind:"ui"` 资产就是这个形状)。
|
||||
* 落盘 `unclassified` 而 canonical `kind:"ui-design"` 能派生出 `ui-interaction`:这是读时
|
||||
* 自愈的触发条件,也正是"选择器显示值 ≠ 落盘值"的现场。
|
||||
*
|
||||
* kind 只接受 canonical 值:写成 `"ui"` / `"UI"` 只会被收口成 `unknown`(派生仍是
|
||||
* `unclassified`),自愈不触发,用例就失去要钉的行为。
|
||||
*/
|
||||
const selfHealingAsset: GameCreationAppAssetManifestEntry = {
|
||||
...asset,
|
||||
id: 'asset-ui',
|
||||
kind: 'ui',
|
||||
kind: 'ui-design',
|
||||
category: 'unclassified',
|
||||
tags: [],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user