import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { resetStore } from '../test/storeTestUtils'; import { useStore } from '../store/useStore'; import { TemplateRegistry } from './TemplateRegistry'; const apiMock = vi.hoisted(() => ({ getTemplates: vi.fn(), createTemplate: vi.fn(), updateTemplate: vi.fn(), deleteTemplate: vi.fn(), })); vi.mock('../lib/api', () => ({ getTemplates: apiMock.getTemplates, createTemplate: apiMock.createTemplate, updateTemplate: apiMock.updateTemplate, deleteTemplate: apiMock.deleteTemplate, })); describe('TemplateRegistry', () => { beforeEach(() => { resetStore(); vi.clearAllMocks(); }); it('loads and displays templates with unpacked classes', async () => { apiMock.getTemplates.mockResolvedValueOnce([ { id: 't1', name: '腹腔镜胆囊切除术', description: 'desc', classes: [{ id: 'c1', name: '胆囊', color: '#ff0000', zIndex: 10, category: '器官' }], rules: [], }, ]); render(); expect(await screen.findAllByText('腹腔镜胆囊切除术')).toHaveLength(2); expect(screen.getByText('胆囊')).toBeInTheDocument(); }); it('creates a template and stores it globally', async () => { apiMock.getTemplates.mockResolvedValueOnce([]); apiMock.createTemplate.mockResolvedValueOnce({ id: 't2', name: 'New Template', description: 'desc', classes: [], rules: [], }); render(); fireEvent.click(screen.getByText('新建方案')); fireEvent.change(screen.getAllByRole('textbox')[0], { target: { value: 'New Template' } }); fireEvent.change(screen.getAllByRole('textbox')[1], { target: { value: 'desc' } }); fireEvent.click(screen.getByRole('button', { name: '保存' })); await waitFor(() => expect(apiMock.createTemplate).toHaveBeenCalledWith(expect.objectContaining({ name: 'New Template', description: 'desc', classes: [], rules: [], color: '#06b6d4', z_index: 0, }))); expect(useStore.getState().templates[0]).toEqual(expect.objectContaining({ id: 't2' })); }); it('imports JSON classes into the edit modal before saving', async () => { apiMock.getTemplates.mockResolvedValueOnce([]); render(); fireEvent.click(screen.getByText('新建方案')); fireEvent.change(screen.getAllByRole('textbox')[0], { target: { value: 'With Classes' } }); fireEvent.click(screen.getByText('批量导入')); fireEvent.change(screen.getByPlaceholderText('[[[255,0,0], [0,255,0]], ["分类A", "分类B"]]'), { target: { value: '{"colors":[[255,0,0]],"names":["分类A"]}' }, }); fireEvent.click(screen.getByRole('button', { name: '导入' })); expect(screen.getByText('分类A')).toBeInTheDocument(); }); it('edits an existing template through the backend and store', async () => { apiMock.getTemplates.mockResolvedValueOnce([ { id: 't1', name: '旧模板', description: 'old desc', classes: [{ id: 'c1', name: '胆囊', color: '#ff0000', zIndex: 10, category: '器官' }], rules: [], color: '#06b6d4', z_index: 3, }, ]); apiMock.updateTemplate.mockResolvedValueOnce({ id: 't1', name: '新模板', description: 'new desc', classes: [{ id: 'c1', name: '胆囊', color: '#ff0000', zIndex: 10, category: '器官' }], rules: [], }); render(); fireEvent.click(await screen.findByRole('button', { name: /修改库视图结构/ })); fireEvent.change(screen.getAllByRole('textbox')[0], { target: { value: '新模板' } }); fireEvent.change(screen.getAllByRole('textbox')[1], { target: { value: 'new desc' } }); fireEvent.click(screen.getByRole('button', { name: '保存' })); await waitFor(() => expect(apiMock.updateTemplate).toHaveBeenCalledWith('t1', expect.objectContaining({ name: '新模板', description: 'new desc', classes: [expect.objectContaining({ id: 'c1', name: '胆囊' })], rules: [], color: '#06b6d4', z_index: 3, }))); expect(useStore.getState().templates[0]).toEqual(expect.objectContaining({ id: 't1', name: '新模板', })); }); it('deletes an existing template after confirmation', async () => { apiMock.getTemplates.mockResolvedValueOnce([ { id: 't1', name: '待删除模板', description: 'desc', classes: [], rules: [], }, ]); apiMock.deleteTemplate.mockResolvedValueOnce(undefined); const { container } = render(); await screen.findAllByText('待删除模板'); const buttons = Array.from(container.querySelectorAll('button')); fireEvent.click(buttons[2]); await waitFor(() => expect(apiMock.deleteTemplate).toHaveBeenCalledWith('t1')); expect(useStore.getState().templates).toEqual([]); }); });