/** * 回归用例:菜单管理 - 新建节点(未保存)→ 切到旧节点 → 切回新节点。 * 曾因 loadMenuConfig 用临时 id 查库返回空 → maindata 为 null → * 模板以 null menu 渲染 → render 抛错 → 在布局 下崩溃 * (表现为 "Component inside renders non-element root node" + * "Cannot set properties of null (setting '__vnode')")。 * 修复:watch(menuSelectedId) 对未落库节点不走 loadMenuConfig,从树节点重建草稿。 * * 临时 id 现由 createTempId() 生成本地负数(不再取号),断言按实际值 -1 校验。 */ import { describe, it, expect, beforeEach, vi } from 'vitest' import { flushPromises } from '@vue/test-utils' import MenuManagement from '@/views/module/menu-management/index.vue' import { mountPage } from '../helpers/app.js' vi.mock('@/services/api', () => ({ loadDataApi: vi.fn(), saveObjectApi: vi.fn(() => Promise.resolve({ code: 0, success: true })), nextIdApi: vi.fn(() => Promise.resolve({ data: 9001, code: 0, success: true })), describeApi: vi.fn(() => Promise.resolve({ data: [], code: 0, success: true })), })) vi.mock('@/components/ui/message/message-manager.js', () => { const m = { success: vi.fn(), error: vi.fn(), warning: vi.fn(), info: vi.fn() } return { Message: m, default: m } }) import { loadDataApi } from '@/services/api' const savedMenu = { b_id: '1', b_code: 'home', b_parent_id: null, b_depth: 0, b_path: '/1/', b_name: '首页', b_menu_type: 'page', b_route: '/dashboard', b_xh: 10, b_canuse: 1, } beforeEach(() => { loadDataApi.mockReset() loadDataApi.mockImplementation((view, where) => { if (view === 's_menu' && where === '1 = 1') return Promise.resolve({ data: [savedMenu] }) // 加载单行用 sqlText 拼的 N'1' 字面量 if (view === 's_menu' && where === "b_id = N'1'") return Promise.resolve({ data: [savedMenu] }) if (view === 's_menu') return Promise.resolve({ data: [] }) // 含本地临时 id(负数):查无此行 return Promise.resolve({ data: [] }) }) }) describe('菜单管理:未保存新节点来回切换', () => { it('切回未保存的新节点时,从树节点重建草稿,不查库、渲染不报错', async () => { const { wrapper } = await mountPage(MenuManagement) await flushPromises() // 1. 新建节点(未保存) await wrapper.vm.addMenu(null, 'page') await flushPromises() expect(String(wrapper.vm.menuSelectedId)).toBe('-1') expect(wrapper.vm.maindata?.b_menu_type).toBe('page') // 2. 切到旧节点(已保存) wrapper.vm.menuSelectedId = '1' await flushPromises() expect(wrapper.vm.maindata?.b_id).toBe('1') // 3. 切回新节点:不应对临时 id 发起查库请求 loadDataApi.mockClear() wrapper.vm.menuSelectedId = '-1' await flushPromises() await flushPromises() expect(loadDataApi).not.toHaveBeenCalled() expect(wrapper.vm.maindata).not.toBeNull() expect(String(wrapper.vm.maindata?.b_id)).toBe('-1') expect(wrapper.vm.maindata?.b_menu_type).toBe('page') expect(String(wrapper.vm.loadedMenuId)).toBe('-1') }) })