补充前端交互状态机文档和Esc测试

- 新增前端交互状态机文档,梳理全局状态、工作区工具、语义分类树、键盘快捷键、范围选择、AI页、模板确认和导入导出交互。

- 明确记录 Esc 行为:只取消当前 mask 选区和临时绘制状态,不删除已有 mask,也不清空 active class。

- 补充 CanvasArea 测试,覆盖 Esc 取消选中 mask 但保留 mask/active class,以及 Esc 取消进行中的多边形绘制。

- 更新文档索引、AGENTS 和测试计划,把前端交互状态机纳入事实文档和 R13 文档测试覆盖。
This commit is contained in:
2026-05-04 03:55:39 +08:00
parent b1bb792b71
commit 56665283aa
5 changed files with 161 additions and 2 deletions

View File

@@ -90,6 +90,38 @@ describe('CanvasArea', () => {
expect(maskGroup()).toHaveAttribute('data-opacity', '0.3');
});
it('clears only the selected mask state with Escape', async () => {
useStore.setState({
activeTemplateId: '2',
activeClass: { id: 'c1', name: '胆囊', color: '#ff0000', zIndex: 20, maskId: 1 },
activeClassId: 'c1',
selectedMaskIds: ['m1'],
masks: [
{
id: 'm1',
frameId: 'frame-1',
pathData: 'M 10 10 L 80 10 L 80 80 L 10 80 Z',
label: '胆囊',
color: '#ff0000',
classId: 'c1',
segmentation: [[10, 10, 80, 10, 80, 80, 10, 80]],
},
],
});
render(<CanvasArea activeTool="move" frame={frame} />);
fireEvent.keyDown(window, { key: 'Escape' });
await waitFor(() => expect(useStore.getState().selectedMaskIds).toEqual([]));
expect(useStore.getState().masks).toHaveLength(1);
expect(useStore.getState().masks[0]).toEqual(expect.objectContaining({
id: 'm1',
label: '胆囊',
color: '#ff0000',
}));
expect(useStore.getState().activeClassId).toBe('c1');
});
it('refines one SAM2 candidate mask from an initial box with positive and negative points', async () => {
apiMock.predictMask
.mockResolvedValueOnce({
@@ -1817,6 +1849,21 @@ describe('CanvasArea', () => {
expect(useStore.getState().selectedMaskIds).toEqual([useStore.getState().masks[0].id]);
});
it('cancels in-progress polygon creation with Escape', () => {
render(<CanvasArea activeTool="create_polygon" frame={frame} />);
const stage = screen.getByTestId('konva-stage');
fireEvent.click(stage, { clientX: 120, clientY: 80 });
fireEvent.click(stage, { clientX: 220, clientY: 80 });
expect(screen.getAllByTestId('konva-circle')).toHaveLength(2);
fireEvent.keyDown(window, { key: 'Escape' });
expect(useStore.getState().masks).toEqual([]);
expect(useStore.getState().selectedMaskIds).toEqual([]);
expect(screen.queryAllByTestId('konva-circle')).toHaveLength(0);
expect(screen.getByText(/点击画布添加顶点/)).toBeInTheDocument();
});
it('closes a clicked polygon by clicking the first node again', () => {
render(<CanvasArea activeTool="create_polygon" frame={frame} />);
const stage = screen.getByTestId('konva-stage');