Files
workspace/code/fms/fms-vue/tests/views/menu-module-binding.spec.js
T
2026-09-14 22:30:13 +08:00

196 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 回归用例:菜单管理 - 绑定模块(s_menu_module)的加载与保存。
*
* s_menu 表没有模块列:绑定关系存 s_menu_module,行身份 = (b_menu_id, b_module_id),
* 只登记 data / virtual 模块(module 只是模块树上的分类,不参与权限)。
* 保存时与加载基线做行级 diff,只提交增删行;目录 / 外链菜单不绑定模块。
*/
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, saveObjectApi } from '@/services/api'
const directoryMenu = {
b_id: 'base',
b_parent_id: null,
b_depth: 0,
b_path: '/base/',
b_name: '基础资料',
b_i18n: 'menu.base',
b_menu_type: 'directory',
b_route: null,
b_icon: null,
b_xh: 10,
b_canuse: 1,
}
const pageMenu = {
b_id: 'otherdata',
b_parent_id: 'base',
b_depth: 1,
b_path: '/base/otherdata/',
b_name: '其他数据',
b_i18n: 'menu.otherdata',
b_menu_type: 'page',
b_route: '/base/otherdata',
b_icon: null,
b_xh: 10,
b_canuse: 1,
}
/** 模块清单:base / base_otherdata 为分类节点,b_port / b_money 为数据模块 */
const modules = [
{
b_id: 'base',
b_parent_id: null,
b_depth: 0,
b_path: '/base/',
b_module_type: 'module',
b_name: '基础资料',
b_canuse: 1,
b_xh: 10,
},
{
b_id: 'base_otherdata',
b_parent_id: 'base',
b_depth: 1,
b_path: '/base/base_otherdata/',
b_module_type: 'module',
b_name: '其他数据',
b_canuse: 1,
b_xh: 30,
},
{
b_id: 'b_port',
b_parent_id: 'base_otherdata',
b_depth: 2,
b_path: '/base/base_otherdata/b_port/',
b_module_type: 'data',
b_name: '港口',
b_canuse: 1,
b_xh: 10,
},
{
b_id: 'b_money',
b_parent_id: 'base_otherdata',
b_depth: 2,
b_path: '/base/base_otherdata/b_money/',
b_module_type: 'data',
b_name: '币种',
b_canuse: 1,
b_xh: 20,
},
]
/** 库中 s_menu_module 的现有行(每个用例前重置) */
let boundRows = []
/** 从查询条件里取 N'xxx' 字面量(mock 按 id 过滤用;'1 = 1' 等条件返回 null) */
function literalOf(condition) {
return /N'([^']*)'/.exec(String(condition || ''))?.[1] ?? null
}
beforeEach(() => {
boundRows = [{ b_menu_id: 'otherdata', b_module_id: 'b_port', b_xh: 10, b_canuse: 1 }]
loadDataApi.mockReset()
saveObjectApi.mockClear()
loadDataApi.mockImplementation((view, condition) => {
if (view === 's_menu') {
const id = literalOf(condition)
const rows = [directoryMenu, pageMenu].filter((row) => !id || row.b_id === id)
return Promise.resolve({ data: rows })
}
if (view === 's_module') return Promise.resolve({ data: modules })
if (view === 's_menu_module') {
const id = literalOf(condition)
return Promise.resolve({
data: boundRows.filter((row) => !id || row.b_menu_id === id),
})
}
return Promise.resolve({ data: [] })
})
})
/** 取出最近一次保存请求里的指定表请求 */
function lastRequestOf(table) {
const requests = saveObjectApi.mock.calls.at(-1)[0]
return requests.find((request) => request.table === table)
}
describe('菜单管理:绑定模块(s_menu_module)', () => {
it('选中菜单时按 b_menu_id 加载绑定模块', async () => {
const { wrapper } = await mountPage(MenuManagement)
await flushPromises()
wrapper.vm.menuSelectedId = 'otherdata'
await flushPromises()
expect(loadDataApi).toHaveBeenCalledWith(
's_menu_module',
"b_menu_id = N'otherdata'",
'b_xh ASC, b_module_id ASC',
)
expect(wrapper.vm.menuModules).toEqual(['b_port'])
})
it('新增绑定:只提交 s_menu_module 的 insert 行', async () => {
const { wrapper } = await mountPage(MenuManagement)
await flushPromises()
wrapper.vm.menuSelectedId = 'otherdata'
await flushPromises()
wrapper.vm.menuModules = ['b_port', 'b_money']
await wrapper.vm.handleSave()
const request = lastRequestOf('s_menu_module')
expect(request.key_field).toBe('b_menu_id,b_module_id')
expect(request.inserts).toEqual([
{ b_menu_id: 'otherdata', b_module_id: 'b_money', b_xh: 20, b_canuse: 1 },
])
expect(request.deletes).toBeUndefined()
})
it('移除绑定:只提交 s_menu_module 的 delete 主键行', async () => {
const { wrapper } = await mountPage(MenuManagement)
await flushPromises()
wrapper.vm.menuSelectedId = 'otherdata'
await flushPromises()
wrapper.vm.menuModules = []
await wrapper.vm.handleSave()
const request = lastRequestOf('s_menu_module')
expect(request.deletes).toEqual([{ b_menu_id: 'otherdata', b_module_id: 'b_port' }])
expect(request.inserts).toBeUndefined()
})
it('目录菜单不绑定模块:保存时清空并删除既有关系行', async () => {
boundRows = [{ b_menu_id: 'base', b_module_id: 'b_port', b_xh: 10, b_canuse: 1 }]
const { wrapper } = await mountPage(MenuManagement)
await flushPromises()
wrapper.vm.menuSelectedId = 'base'
await flushPromises()
expect(wrapper.vm.menuModules).toEqual(['b_port'])
await wrapper.vm.handleSave()
const request = lastRequestOf('s_menu_module')
expect(request.deletes).toEqual([{ b_menu_id: 'base', b_module_id: 'b_port' }])
})
})