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); } }); });