2f8753f597
- 契约层把 ProjectResourceCanvasPosition.section 加宽为 PersistedProjectResourceCanvasSection 并删除旧 ProjectResourceCanvasSection 联合 - 七个分区类型消费点改用 ProjectResourceCanvasCategory:投影、布局模型、布局 Hook、分区高度、依赖图层与画布历史 - RESOURCE_CANVAS_SECTION_ORDER 改为 PROJECT_RESOURCE_CANVAS_SECTIONS,删除 RESOURCE_CANVAS_VISIBLE_SECTION_ORDER - reconcileResourceCanvasLayout 接入读时归一化:旧栏目值按资源当前分区改写 section,坐标与手动标记原样保留 - 归并后的布局与原布局逐项不同,由既有协调路径判定 changed 并写回一次新分区,不引入迁移脚本 - 投影层五个资源入表点统一走 projectResourceCanvasCategory:项目版本独立成栏、Agent 回执归文档、其余按资产分类 - 新增 projectResourceDisplayKind 收敛 manifest 资产 subtype 即 kind 的显示类型口径,projectResourceTypeLabel 不再依赖旧栏目 - 卡片预览、编辑分流、媒体工具条、预览文案与画布历史快照改用显示类型与新分区,历史快照入栈时收窄到现行分区 - index.tsx 删除可见栏目导入与 visibleCategoryOrder,栏目标签由资源筛选唯一中文口径派生并补齐 7 栏 - index.tsx 补齐 7 栏默认视口与 7 个栏目图标(新增 LayoutGrid、Users、PackageOpen,移除已无用的 Code2) - index.tsx 删除 canvasResources 的 code 过滤:只登记游戏代码的项目现在落在待归类栏目并正常显示卡片 - 新增只登记游戏代码项目的 UI 回归用例,覆盖栏目大纲、默认落地栏目与代码卡片渲染 - 测试夹具按新分区轴迁移,新增旧栏目 sidecar 读时归并用例与栏目顺序用例 - 共享测试 harness 的资源卡查询正则允许栏目标签自带空格(UI 交互) - 同步 PRD 与共享记忆决策记录的栏目口径描述
165 lines
4.8 KiB
TypeScript
165 lines
4.8 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
initialResourceBookState,
|
|
resourceBookCategoryCardPresentation,
|
|
resourceBookReducer,
|
|
resourceBookSceneCategory,
|
|
resourceBookShowsExitingCards,
|
|
resourceBookShowsOverviewCards,
|
|
} from '../src/view/project-development/resourceBookModel';
|
|
|
|
describe('resource book state machine', () => {
|
|
it('keeps overview titles and preview cards visible on the steady main canvas', () => {
|
|
expect(resourceBookShowsOverviewCards(initialResourceBookState)).toBe(true);
|
|
});
|
|
|
|
it('commits the child target immediately and only keeps the motion phase transient', () => {
|
|
const entering = resourceBookReducer(initialResourceBookState, {
|
|
type: 'open-category',
|
|
category: 'document',
|
|
token: 1,
|
|
});
|
|
expect(entering).toMatchObject({
|
|
view: 'child',
|
|
category: 'document',
|
|
phase: 'entering',
|
|
token: 1,
|
|
});
|
|
expect(resourceBookSceneCategory(entering)).toBe('document');
|
|
});
|
|
|
|
it('retargets directly from one child canvas to another', () => {
|
|
const current = resourceBookReducer(initialResourceBookState, {
|
|
type: 'open-category',
|
|
category: 'document',
|
|
token: 1,
|
|
});
|
|
const switching = resourceBookReducer(current, {
|
|
type: 'open-category',
|
|
category: 'scene',
|
|
token: 2,
|
|
});
|
|
expect(switching).toMatchObject({
|
|
view: 'child',
|
|
category: 'scene',
|
|
phase: 'entering',
|
|
});
|
|
expect(resourceBookSceneCategory(switching)).toBe('scene');
|
|
});
|
|
|
|
it('marks a return to the main canvas separately from a child enter transition', () => {
|
|
const entering = resourceBookReducer(initialResourceBookState, {
|
|
type: 'open-category',
|
|
category: 'document',
|
|
token: 1,
|
|
});
|
|
const returning = resourceBookReducer(entering, {
|
|
type: 'return-to-main',
|
|
token: 2,
|
|
});
|
|
expect(returning).toMatchObject({
|
|
view: 'main',
|
|
category: null,
|
|
phase: 'returning-main',
|
|
});
|
|
});
|
|
|
|
it('lets an explicit collapse cancel an in-flight child switch', () => {
|
|
const entering = resourceBookReducer(initialResourceBookState, {
|
|
type: 'open-category',
|
|
category: 'document',
|
|
token: 1,
|
|
});
|
|
const switching = resourceBookReducer(entering, {
|
|
type: 'open-category',
|
|
category: 'scene',
|
|
token: 2,
|
|
});
|
|
const main = resourceBookReducer(switching, {
|
|
type: 'return-to-main',
|
|
token: 3,
|
|
});
|
|
|
|
expect(main).toMatchObject({
|
|
view: 'main',
|
|
category: null,
|
|
phase: 'returning-main',
|
|
token: 3,
|
|
});
|
|
});
|
|
|
|
it('ignores stale transition callbacks', () => {
|
|
const entering = resourceBookReducer(initialResourceBookState, {
|
|
type: 'open-category',
|
|
category: 'document',
|
|
token: 1,
|
|
});
|
|
const switched = resourceBookReducer(entering, {
|
|
type: 'open-category',
|
|
category: 'scene',
|
|
token: 2,
|
|
});
|
|
expect(
|
|
resourceBookReducer(switched, { type: 'finish-transition', token: 1 }),
|
|
).toEqual(switched);
|
|
});
|
|
});
|
|
|
|
describe('resource book card presentation', () => {
|
|
it('uses the overview stack on the steady main canvas', () => {
|
|
expect(
|
|
resourceBookCategoryCardPresentation(initialResourceBookState, 'scene'),
|
|
).toBe('overview');
|
|
});
|
|
|
|
it('keeps other categories mounted while entering so they can fade out', () => {
|
|
const entering = resourceBookReducer(initialResourceBookState, {
|
|
type: 'open-category',
|
|
category: 'scene',
|
|
token: 1,
|
|
});
|
|
|
|
expect(resourceBookCategoryCardPresentation(entering, 'scene')).toBe(
|
|
'child',
|
|
);
|
|
expect(resourceBookCategoryCardPresentation(entering, 'code')).toBe(
|
|
'overview',
|
|
);
|
|
});
|
|
|
|
it('drops other categories once the child canvas is steady', () => {
|
|
const steady = resourceBookReducer(
|
|
resourceBookReducer(initialResourceBookState, {
|
|
type: 'open-category',
|
|
category: 'scene',
|
|
token: 1,
|
|
}),
|
|
{ type: 'finish-transition', token: 1 },
|
|
);
|
|
|
|
expect(resourceBookCategoryCardPresentation(steady, 'scene')).toBe('child');
|
|
expect(resourceBookCategoryCardPresentation(steady, 'code')).toBeNull();
|
|
});
|
|
|
|
it('returns every category to the stack and exposes the exiting window', () => {
|
|
const returning = resourceBookReducer(
|
|
resourceBookReducer(initialResourceBookState, {
|
|
type: 'open-category',
|
|
category: 'scene',
|
|
token: 1,
|
|
}),
|
|
{ type: 'return-to-main', token: 2 },
|
|
);
|
|
|
|
expect(resourceBookCategoryCardPresentation(returning, 'scene')).toBe(
|
|
'overview',
|
|
);
|
|
expect(resourceBookCategoryCardPresentation(returning, 'code')).toBe(
|
|
'overview',
|
|
);
|
|
expect(resourceBookShowsExitingCards(returning)).toBe(true);
|
|
expect(resourceBookShowsExitingCards(initialResourceBookState)).toBe(false);
|
|
});
|
|
});
|