import { beforeEach, describe, expect, it, vi } from 'vitest' const loadDataApi = vi.fn() const saveObjectApi = vi.fn() const authState = { userInfo: { account: 'u001' } } vi.mock('@/services/api', () => ({ loadDataApi: (...args) => loadDataApi(...args), saveObjectApi: (...args) => saveObjectApi(...args), })) vi.mock('@/stores/auth', () => ({ useAuthStore: () => authState, })) const { buildDefaultQueryLayout, currentUserId, isEmptyDelta, layoutDelta, loadFieldPower, loadModulePref, mergeQueryLayout, removeModulePref, saveModulePref, } = await import('@/components/fms-module-list/useModulePref') const items = [ { field: { b_field: 'b_no' }, config: { b_operator: 'like' } }, { field: { b_field: 'b_name' }, config: { b_operator: 'like' } }, { field: { b_field: 'b_bz' }, config: { b_operator: 'like' } }, ] describe('用户模块偏好', () => { beforeEach(() => { loadDataApi.mockReset() saveObjectApi.mockReset() loadDataApi.mockResolvedValue({ data: [] }) saveObjectApi.mockResolvedValue({}) }) it('取当前登录账号作为偏好用户编码', () => { expect(currentUserId()).toBe('u001') }) it('默认布局:quick 列表中的字段进常用区并靠前排序', () => { const layout = buildDefaultQueryLayout(items, ['b_name', 'b_no']) expect(layout.area).toEqual({ b_name: 'quick', b_no: 'quick', b_bz: 'advanced' }) expect(layout.order.b_name).toBeLessThan(layout.order.b_no) }) it('未配置 quick 列表时,配置了的查询条件全部进常用区(配置了就要能看到)', () => { const layout = buildDefaultQueryLayout(items, []) expect(layout.area).toEqual({ b_no: 'quick', b_name: 'quick', b_bz: 'quick' }) }) it('查询 JSON 节点上的 area 显式配置优先', () => { const configured = [ { field: { b_field: 'b_no' }, node: { area: 'hidden' } }, { field: { b_field: 'b_name' }, node: { area: 'advanced' } }, ] const layout = buildDefaultQueryLayout(configured, []) expect(layout.area).toEqual({ b_no: 'hidden', b_name: 'advanced' }) }) it('偏好只覆盖布局,无权限字段不得因偏好复活', () => { const defaultLayout = buildDefaultQueryLayout(items, ['b_no']) const pref = { schemaVersion: 1, area: { b_bz: 'quick', b_status: 'quick' }, order: { b_bz: 1 }, } const merged = mergeQueryLayout(defaultLayout, pref, new Set(['b_no', 'b_name', 'b_bz'])) expect(merged.area.b_bz).toBe('quick') expect(merged.area.b_status).toBeUndefined() expect(merged.order.b_status).toBeUndefined() }) it('三层合并:企业层盖系统默认,个人层再盖企业层', () => { const sysDefault = buildDefaultQueryLayout(items, ['b_no']) // b_no 常用,其余靠后 const globalPref = { schemaVersion: 1, area: { b_name: 'quick' } } const enterprise = mergeQueryLayout(sysDefault, globalPref, new Set(['b_no', 'b_name', 'b_bz'])) expect(enterprise.area.b_name).toBe('quick') // 个人层在企业层之上进一步调整 const personalPref = { schemaVersion: 1, area: { b_name: 'hidden' } } const merged = mergeQueryLayout(enterprise, personalPref, new Set(['b_no', 'b_name', 'b_bz'])) expect(merged.area.b_name).toBe('hidden') // 企业层没动的字段保持系统默认 expect(merged.area.b_bz).toBe(sysDefault.area.b_bz) }) it('增量覆盖:只保留与基线不同的字段', () => { const base = { area: { a: 'quick', b: 'advanced' }, order: { a: 10, b: 20 } } const current = { area: { a: 'quick', b: 'quick' }, order: { a: 30, b: 20 } } const delta = layoutDelta(current, base) expect(delta.area).toEqual({ b: 'quick' }) expect(delta.order).toEqual({ a: 30 }) expect(isEmptyDelta(delta)).toBe(false) expect(isEmptyDelta(layoutDelta(base, base))).toBe(true) }) it('读取个人偏好时按用户/模块/schema 类型过滤,解析失败返回 null', async () => { loadDataApi.mockResolvedValueOnce({ data: [{ b_schema_json: '{ "schemaVersion": 1 }' }] }) await expect(loadModulePref('u001', 'sea')).resolves.toEqual({ schemaVersion: 1 }) expect(loadDataApi.mock.calls[0][0]).toBe('s_user_module_pref') expect(loadDataApi.mock.calls[0][1]).toContain("b_user_id = N'u001'") expect(loadDataApi.mock.calls[0][1]).toContain("b_schema_type = N'query'") loadDataApi.mockResolvedValueOnce({ data: [{ b_schema_json: 'not-json' }] }) await expect(loadModulePref('u001', 'sea')).resolves.toBeNull() }) it('删除个人层覆盖用于恢复默认', async () => { await removeModulePref('u001', 'sea') expect(saveObjectApi.mock.calls[0][0][0].table).toBe('s_user_module_pref') expect(saveObjectApi.mock.calls[0][0][0].deletes).toEqual([ { b_user_id: 'u001', b_module_id: 'sea', b_schema_type: 'query' }, ]) }) it('保存偏好:首次写入走 inserts,主键冲突时重试 updates', async () => { await saveModulePref('u001', 'sea', { schemaVersion: 1 }, false) expect(saveObjectApi.mock.calls[0][0][0].inserts).toHaveLength(1) saveObjectApi.mockRejectedValueOnce(new Error('duplicate key')) await saveModulePref('u001', 'sea', { schemaVersion: 1 }, false) expect(saveObjectApi.mock.calls[2][0][0].updates).toHaveLength(1) }) it('字段查询权限:b_query = 0 的字段被标记为不可查询', async () => { loadDataApi.mockResolvedValueOnce({ data: [ { b_field_id: 'b_no', b_query: 1 }, { b_field_id: 'b_bz', b_query: 0 }, ], }) const power = await loadFieldPower('u001', 'sea') expect(power.get('b_no')).toBe(1) expect(power.get('b_bz')).toBe(0) }) })