8701f43f75
- 删除渲染层传输层:services/clientApi.ts、clientHttp 的 fetchClientHttp 与超时封装整体退场,账户、认证、素材直传、错误上报与游戏分发发布改走 Rust typed command。 - 新增宿主封装:accountHost、editorAssetHost、platformSessionRetry、agentRuntimeErrorDetail 与 features/app-shell/workspacePreferences;渲染层不再读写 access token / refresh cookie,localStorage 只剩测试态旧键迁移 shim。 - 修复 DirectProject 失败文案 v2 映射与「查看详情」按需读取,画布标签面板高度夹紧、框选每帧重算、文档预览入口与场景标题栏 key、资源筛选面板样式。 - 补齐与调整回归:accountHost、agentRuntimeErrorDetail、clientAuthHost、clientHttp、platformSession、platformSessionRetry、resourceFilterPanelStyle、directThreadChatSubscription 等定向用例,并清理测试 harness 中无调用方的残留。
264 lines
8.2 KiB
TypeScript
264 lines
8.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
initialResourceBookState,
|
|
isResourceBookAllTarget,
|
|
RESOURCE_BOOK_ALL_TARGET,
|
|
resourceBookCategoryCardPresentation,
|
|
resourceBookReducer,
|
|
resourceBookSceneCategory,
|
|
resourceBookShowsAllCategories,
|
|
resourceBookShowsExitingCards,
|
|
resourceBookShowsOverviewCards,
|
|
sameResourceIdSelection,
|
|
} from '../src/view/project-development/resourceBookModel';
|
|
|
|
describe('框选选中集合的等价判据', () => {
|
|
it('忽略顺序比较,集合相同即为等价', () => {
|
|
expect(
|
|
sameResourceIdSelection(['asset-a', 'asset-b'], ['asset-b', 'asset-a']),
|
|
).toBe(true);
|
|
expect(sameResourceIdSelection([], [])).toBe(true);
|
|
expect(sameResourceIdSelection(['asset-a'], ['asset-a'])).toBe(true);
|
|
});
|
|
|
|
it('集合不同、长度不同或存在重复项时都不等价', () => {
|
|
expect(sameResourceIdSelection(['asset-a'], ['asset-b'])).toBe(false);
|
|
expect(sameResourceIdSelection(['asset-a'], ['asset-a', 'asset-b'])).toBe(
|
|
false,
|
|
);
|
|
expect(
|
|
sameResourceIdSelection(['asset-a', 'asset-a'], ['asset-a', 'asset-b']),
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
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);
|
|
});
|
|
});
|
|
|
|
describe('resource book all-resources target', () => {
|
|
it('takes the overview stack as the first tile on the main canvas', () => {
|
|
expect(
|
|
resourceBookCategoryCardPresentation(
|
|
initialResourceBookState,
|
|
RESOURCE_BOOK_ALL_TARGET,
|
|
),
|
|
).toBe('overview');
|
|
});
|
|
|
|
it('spreads every real category on the canvas and keeps the all tile as a titlebar', () => {
|
|
const all = resourceBookReducer(
|
|
resourceBookReducer(initialResourceBookState, {
|
|
type: 'open-category',
|
|
category: RESOURCE_BOOK_ALL_TARGET,
|
|
token: 1,
|
|
}),
|
|
{ type: 'finish-transition', token: 1 },
|
|
);
|
|
|
|
expect(all).toMatchObject({
|
|
view: 'child',
|
|
category: RESOURCE_BOOK_ALL_TARGET,
|
|
phase: 'idle',
|
|
});
|
|
expect(resourceBookSceneCategory(all)).toBe(RESOURCE_BOOK_ALL_TARGET);
|
|
expect(resourceBookShowsAllCategories(all)).toBe(true);
|
|
expect(resourceBookShowsAllCategories(initialResourceBookState)).toBe(
|
|
false,
|
|
);
|
|
// 展开态把每个真实栏目铺在自己的分带上(卡片仍由画本场景渲染,每张卡只有一个宿主:
|
|
// 资源只属于一个栏目,所以 `card:*` 键不会撞车)。
|
|
for (const category of [
|
|
'ui-interaction',
|
|
'character',
|
|
'scene',
|
|
'audio',
|
|
'document',
|
|
'unclassified',
|
|
'version',
|
|
] as const) {
|
|
expect(resourceBookCategoryCardPresentation(all, category)).toBe('child');
|
|
}
|
|
// 「所有资源」自己只出钉住的标题栏与全量计数,不再复制一份卡片:否则同一张资源卡会
|
|
// 在里面那一组和各栏目组各挂一次(DOM 查询与 FLIP 键都会撞车)。
|
|
expect(
|
|
resourceBookCategoryCardPresentation(all, RESOURCE_BOOK_ALL_TARGET),
|
|
).toBeNull();
|
|
});
|
|
|
|
it('keeps a plain category page on the original single-column presentation', () => {
|
|
const child = resourceBookReducer(
|
|
resourceBookReducer(initialResourceBookState, {
|
|
type: 'open-category',
|
|
category: 'document',
|
|
token: 1,
|
|
}),
|
|
{ type: 'finish-transition', token: 1 },
|
|
);
|
|
|
|
expect(resourceBookShowsAllCategories(child)).toBe(false);
|
|
expect(resourceBookCategoryCardPresentation(child, 'document')).toBe(
|
|
'child',
|
|
);
|
|
// 静止态的非活动栏目一张都不挂。
|
|
expect(resourceBookCategoryCardPresentation(child, 'audio')).toBeNull();
|
|
});
|
|
|
|
it('identifies the render-layer sentinel without widening the section contract', () => {
|
|
expect(isResourceBookAllTarget(RESOURCE_BOOK_ALL_TARGET)).toBe(true);
|
|
expect(isResourceBookAllTarget('unclassified')).toBe(false);
|
|
expect(isResourceBookAllTarget(null)).toBe(false);
|
|
});
|
|
});
|