4fb0de2c94
- resourceBookController 重写为真实元素 FLIP:begin 在提交前记录 First,play 测 Last 后给同一节点加反向 transform,sync 按布局签名做可中断重基 - 转场进行中重基用剩余时长(下限 80ms),空闲布局变化用完整 420ms,预览图尺寸晚到的二次布局变化不再瞬移 - 宿主节点几何基准改存 world 局部坐标,平移和缩放不会让上一次的干净矩形过期 - 不可见节点钉在 First 淡出、挂载节点淡入;搜索/排序导致的卸载保留一帧淡出;返回总览时源栏目未回到摞上的卡片按子画布布局淡出 - 新增 buildResourceBookScenePlan 渲染计划,渲染、转场签名与淡出集合共用同一份数据 - 删除 ResourceBookTransitionLayer 与克隆快照逻辑,清理 styles.css 的转场层和 data-book-motion 隐藏规则 - 重写 resourceBookController.test.ts,补 resourceBookLayout/resourceBookModel 用例与 appSurface 同节点断言 - 同步技术方案转场段与 pitfalls 经验条目
163 lines
4.7 KiB
TypeScript
163 lines
4.7 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: 'art',
|
|
token: 2,
|
|
});
|
|
expect(switching).toMatchObject({
|
|
view: 'child',
|
|
category: 'art',
|
|
phase: 'entering',
|
|
});
|
|
expect(resourceBookSceneCategory(switching)).toBe('art');
|
|
});
|
|
|
|
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: 'art',
|
|
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: 'art',
|
|
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, 'art'),
|
|
).toBe('overview');
|
|
});
|
|
|
|
it('keeps other categories mounted while entering so they can fade out', () => {
|
|
const entering = resourceBookReducer(initialResourceBookState, {
|
|
type: 'open-category',
|
|
category: 'art',
|
|
token: 1,
|
|
});
|
|
|
|
expect(resourceBookCategoryCardPresentation(entering, 'art')).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: 'art',
|
|
token: 1,
|
|
}),
|
|
{ type: 'finish-transition', token: 1 },
|
|
);
|
|
|
|
expect(resourceBookCategoryCardPresentation(steady, 'art')).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: 'art',
|
|
token: 1,
|
|
}),
|
|
{ type: 'return-to-main', token: 2 },
|
|
);
|
|
|
|
expect(resourceBookCategoryCardPresentation(returning, 'art')).toBe(
|
|
'overview',
|
|
);
|
|
expect(resourceBookCategoryCardPresentation(returning, 'code')).toBe(
|
|
'overview',
|
|
);
|
|
expect(resourceBookShowsExitingCards(returning)).toBe(true);
|
|
expect(resourceBookShowsExitingCards(initialResourceBookState)).toBe(false);
|
|
});
|
|
});
|