0ef1a4ec15
- index.tsx:新增 resourceFilterOpen 与 resourceFilterTags 两个 state,区域不新增 state(唯一新增的筛选相关 state 是已选标签) - index.tsx:可见资源由内联关键词过滤改为调用 filterCanvasResources,维度为区域 / 标签 / 关键词;区域由 resourceBookState 派生(不在分页栏目里时为「全部区域」),因此用户用滚轮 / 总览卡片 / 下一页换栏目时区域自动跟随,无需双向同步 - index.tsx:标签库按「区域 + 关键词」派生并故意不含已选标签——若拿已按标签收窄的集合派生,选了一个标签后列表只剩该标签,用户再也选不出第二个标签,AND 多选会形同废掉 - index.tsx:Dock 上紧邻搜索按钮新增筛选按钮,复用 game-resource-book-zoom-button 与 is-active 口径;两个浮层共用同一个右下角锚点,因此开筛选即收起搜索、开搜索即收起筛选 - index.tsx:挂载 ResourceFilterPanel,区域变更转交既有 openResourceBookChild(与栏目切换同一条路径),关键词与搜索浮层共用同一份 searchText - resourceCanvasFilterModel.ts:resourceFilterIsActive 只看关键词与标签、不含区域——区域就是画布当前栏目,用户换栏时它照样在变,算进高亮会让按钮在「只是换了一栏看」时也常亮 - 新增 resourceFloatingPanelModel.ts 与其测试:资源卡旁「快速编辑」与「生成动画」的互斥规则收在一处并被两个开启入口共用,修掉「先开生成动画再点快速编辑两个浮层并存」的缺口;关闭仍只关自己,生成收尾不会吞掉用户刚打开的另一个面板 - 断言:新增 5 条互斥用例(含两个方向各自一条与来回点击序列),加上既有筛选用例;变异验证实测——把互斥模型的 closed 写成恒为 quick-edit(等价只修一个方向)→ 4 failed,还原后 5 passed
125 lines
4.1 KiB
TypeScript
125 lines
4.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
openResourceCardFloatingPanelState,
|
|
opposingResourceCardFloatingPanel,
|
|
type ResourceCardFloatingPanelKind,
|
|
} from '../src/view/project-development/resourceFloatingPanelModel';
|
|
|
|
/**
|
|
* 用一个真实的两布尔状态跑「开启某个浮层」这一步,模拟宿主里两个 state 的实际结果,
|
|
* 这样断言的是「最终会不会两个都开着」,而不是只看模型返回值的形状。
|
|
*/
|
|
function openPanel(
|
|
state: { quickEditOpen: boolean; characterAnimationOpen: boolean },
|
|
kind: ResourceCardFloatingPanelKind,
|
|
) {
|
|
const { open, closed } = openResourceCardFloatingPanelState(kind);
|
|
const next = { ...state };
|
|
if (open === 'quick-edit') next.quickEditOpen = true;
|
|
else next.characterAnimationOpen = true;
|
|
if (closed === 'quick-edit') next.quickEditOpen = false;
|
|
else next.characterAnimationOpen = false;
|
|
return next;
|
|
}
|
|
|
|
const BOTH_CLOSED = { quickEditOpen: false, characterAnimationOpen: false };
|
|
|
|
describe('资源卡旁浮层的互斥开关', () => {
|
|
it('互相是对方的对端,且对端是自身的逆', () => {
|
|
expect(opposingResourceCardFloatingPanel('quick-edit')).toBe(
|
|
'character-animation',
|
|
);
|
|
expect(opposingResourceCardFloatingPanel('character-animation')).toBe(
|
|
'quick-edit',
|
|
);
|
|
for (const kind of [
|
|
'quick-edit',
|
|
'character-animation',
|
|
] as const satisfies readonly ResourceCardFloatingPanelKind[]) {
|
|
expect(
|
|
opposingResourceCardFloatingPanel(
|
|
opposingResourceCardFloatingPanel(kind),
|
|
),
|
|
).toBe(kind);
|
|
}
|
|
});
|
|
|
|
it('开一个必然关另一个:返回的 open 与 closed 永远不同', () => {
|
|
for (const kind of [
|
|
'quick-edit',
|
|
'character-animation',
|
|
] as const satisfies readonly ResourceCardFloatingPanelKind[]) {
|
|
const { open, closed } = openResourceCardFloatingPanelState(kind);
|
|
expect(open).toBe(kind);
|
|
expect(closed).not.toBe(open);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 变异验证①:把 `openResourceCardFloatingPanelState` 的 `closed` 写成恒为
|
|
* `'quick-edit'`(或删掉对端推导),下面这一条必须变红。
|
|
*
|
|
* 方向一:先开「生成动画」,再点「快速编辑」→ 动画必须被收起。
|
|
*/
|
|
it('开动画后再点快速编辑,动画被收起', () => {
|
|
const afterAnimation = openPanel(BOTH_CLOSED, 'character-animation');
|
|
expect(afterAnimation).toEqual({
|
|
quickEditOpen: false,
|
|
characterAnimationOpen: true,
|
|
});
|
|
|
|
const afterQuickEdit = openPanel(afterAnimation, 'quick-edit');
|
|
expect(afterQuickEdit).toEqual({
|
|
quickEditOpen: true,
|
|
characterAnimationOpen: false,
|
|
});
|
|
});
|
|
|
|
/**
|
|
* 变异验证②:同一条规则的另一半。
|
|
*
|
|
* 方向二:先开「快速编辑」,再点「生成动画」→ 快速编辑必须被收起。
|
|
* 只修一个方向时这条会红。
|
|
*/
|
|
it('开快速编辑后再点动画,快速编辑被收起', () => {
|
|
const afterQuickEdit = openPanel(BOTH_CLOSED, 'quick-edit');
|
|
expect(afterQuickEdit).toEqual({
|
|
quickEditOpen: true,
|
|
characterAnimationOpen: false,
|
|
});
|
|
|
|
const afterAnimation = openPanel(afterQuickEdit, 'character-animation');
|
|
expect(afterAnimation).toEqual({
|
|
quickEditOpen: false,
|
|
characterAnimationOpen: true,
|
|
});
|
|
});
|
|
|
|
it('反复来回点也永远不会两个同时开着', () => {
|
|
let state = BOTH_CLOSED;
|
|
const sequence: ResourceCardFloatingPanelKind[] = [
|
|
'quick-edit',
|
|
'character-animation',
|
|
'character-animation',
|
|
'quick-edit',
|
|
'quick-edit',
|
|
'character-animation',
|
|
];
|
|
for (const kind of sequence) {
|
|
state = openPanel(state, kind);
|
|
expect(state.quickEditOpen && state.characterAnimationOpen).toBe(false);
|
|
expect(
|
|
state.quickEditOpen || state.characterAnimationOpen,
|
|
`${kind} 之后至少应有一个开着`,
|
|
).toBe(true);
|
|
// 被点开的那个必须是当前开着的那个。
|
|
expect(
|
|
kind === 'quick-edit'
|
|
? state.quickEditOpen
|
|
: state.characterAnimationOpen,
|
|
).toBe(true);
|
|
}
|
|
});
|
|
});
|