import { describe, it, expect, vi, beforeEach } from 'vitest' import { nextTick, ref } from 'vue' import { mount } from '@vue/test-utils' import { createPinia } from 'pinia' import MenuDetailPanel from '@/views/system/module-management/components/MenuDetailPanel.vue' const moduleRow = { b_id: '1', b_code: 'test', b_name: '测试模块', b_module_type: 'data', b_parent_id: null, b_canuse: 1, b_xh: 10, } vi.mock('@/services/api', () => ({ loadDataApi: vi.fn(async (table) => { if (table === 's_module') return { data: [moduleRow] } return { data: [] } }), saveObjectApi: vi.fn(async () => ({})), nextIdApi: vi.fn(async () => ({ data: '999' })), describeApi: vi.fn(async () => ({ data: [] })), })) import ModuleManagement from '@/views/system/module-management/index.vue' describe('模块管理 tab 切换复现', () => { let wrapper beforeEach(async () => { wrapper = mount(ModuleManagement, { global: { plugins: [createPinia()], stubs: { ModuleBasicPanel: { template: '
' }, ModuleFieldsPanel: { template: '
' }, ModuleListConfigPanel: { template: '
' }, ModuleEditConfigPanel: { template: '
' }, ModuleQueryConfigPanel: { template: '
' }, ModuleAutoCodePanel: { template: '
' }, ModulePowersPanel: { template: '
' }, ModuleI18nPanel: { template: '
' }, MenuDetailPanel: { template: '
' }, Modal: { template: '
' }, Transition: { template: '' }, }, }, }) // 等待树加载 await nextTick() await nextTick() }) it('选中模块后 section Tabs 仅一个 tab 激活', async () => { // 点击树节点选中模块 const node = wrapper.find('.tree-node-content') expect(node.exists()).toBe(true) await node.trigger('click') await nextTick() await nextTick() // 等待 loadModuleConfig 完成 await new Promise((r) => setTimeout(r, 50)) await nextTick() const sectionTabs = wrapper.find('.mm-section-tabs') expect(sectionTabs.exists()).toBe(true) const activeTabs = sectionTabs.findAll('.tabs-tab-active') expect(activeTabs).toHaveLength(1) // 点击"字段定义" const fieldsTab = sectionTabs .findAll('.tabs-tab') .find((t) => t.text().includes('字段定义')) expect(fieldsTab).toBeTruthy() await fieldsTab.trigger('click') await nextTick() const activeTabs2 = sectionTabs.findAll('.tabs-tab-active') expect(activeTabs2).toHaveLength(1) expect(activeTabs2[0].text()).toContain('字段定义') }) }) describe('MenuDetailPanel 暴露的保存校验 API', () => { it('validate / saveI18n / markI18nSaved 同时暴露(defineExpose 未被覆盖)', async () => { const menu = ref({ b_id: '1', b_code: '', b_name: '', b_menu_type: 'directory', b_canuse: 1, }) const wrapper = mount(MenuDetailPanel, { props: { menu: menu.value, parentName: '', moduleOptions: [], powers: [], loading: false, }, global: { stubs: { IconSelect: true, ModulePowersPanel: true, ModuleI18nPanel: true, I18nQuickSetModal: true, Tabs: { template: '
' }, TabPane: { template: '
' }, }, }, }) await nextTick() expect(typeof wrapper.vm.validate).toBe('function') expect(typeof wrapper.vm.saveI18n).toBe('function') expect(typeof wrapper.vm.markI18nSaved).toBe('function') }) it('validate 对空必填返回错误字段,且校验时切回基本信息 tab', async () => { const menu = ref({ b_id: '1', b_code: '', b_name: '', b_menu_type: 'page', b_route: '', b_canuse: 1, }) const wrapper = mount(MenuDetailPanel, { props: { menu: menu.value, parentName: '', moduleOptions: [], powers: [], loading: false, }, global: { stubs: { IconSelect: true, ModulePowersPanel: true, ModuleI18nPanel: true, I18nQuickSetModal: true, Tabs: { template: '
' }, TabPane: { template: '
' }, }, }, }) await nextTick() const errors = await wrapper.vm.validate() expect(errors.length).toBeGreaterThan(0) const names = errors.map((e) => e.name) expect(names).toContain('b_code') expect(names).toContain('b_name') }) })