249 lines
9.2 KiB
JavaScript
249 lines
9.2 KiB
JavaScript
/**
|
||
* 界面配置 JSON → 运行时渲染配置 适配层测试(fms-module-common/schemaRender)。
|
||
* 覆盖 view→列表列、edit→分组/编辑行、query→查询入口,以及栏位设置回写 JSON。
|
||
*/
|
||
import { describe, expect, it } from 'vitest'
|
||
import {
|
||
buildListConfig,
|
||
buildEditRender,
|
||
buildQueryRender,
|
||
applyColumnSettings,
|
||
} from '@/components/fms-module-common/schemaRender'
|
||
import { parseOptions } from '@/components/fms-module-common/fieldUtils'
|
||
|
||
describe('buildListConfig(view JSON → 列表列)', () => {
|
||
it('分组/嵌套分组按文档顺序展开为扁平列,保留宽度与可见性', () => {
|
||
const view = {
|
||
schemaVersion: 1,
|
||
columns: [
|
||
{ field: 'name', width: 120 },
|
||
{
|
||
type: 'group',
|
||
title: '基本信息',
|
||
children: [
|
||
{ field: 'age', width: 100 },
|
||
{ type: 'group', title: '深层', children: [{ field: 'remark', width: 200 }] },
|
||
],
|
||
},
|
||
{ field: 'hidden', visible: false },
|
||
],
|
||
}
|
||
const rows = buildListConfig(view)
|
||
expect(rows.map((r) => r.b_field)).toEqual(['name', 'age', 'remark', 'hidden'])
|
||
expect(rows.map((r) => r.b_xh)).toEqual([10, 20, 30, 40])
|
||
expect(rows[0]).toMatchObject({ b_field: 'name', b_width: 120, b_visible: 1 })
|
||
expect(rows[3].b_visible).toBe(0)
|
||
})
|
||
|
||
it('无配置时返回空数组', () => {
|
||
expect(buildListConfig({ schemaVersion: 1, columns: [] })).toEqual([])
|
||
expect(buildListConfig(null)).toEqual([])
|
||
})
|
||
})
|
||
|
||
describe('buildEditRender(edit JSON → 分组 + 编辑行)', () => {
|
||
it('还原分组与行属性,嵌套分组生成独立分组', () => {
|
||
const edit = {
|
||
schemaVersion: 1,
|
||
children: [
|
||
{ field: 'code', span: 24, required: true },
|
||
{
|
||
type: 'group',
|
||
title: '基础',
|
||
children: [
|
||
{ field: 'name', span: 12, readonly: true, defaultValue: 'X' },
|
||
{ type: 'group', title: '扩展', children: [{ field: 'remark', span: 24 }] },
|
||
],
|
||
},
|
||
],
|
||
}
|
||
const { groups, rows } = buildEditRender(edit)
|
||
expect(rows.map((r) => r.b_field)).toEqual(['code', 'name', 'remark'])
|
||
const outer = groups.find((g) => g.b_title === '基础')
|
||
const inner = groups.find((g) => g.b_title === '扩展')
|
||
expect(outer).toBeTruthy()
|
||
expect(inner.b_parent_id).toBe(outer.b_id)
|
||
expect(rows[0]).toMatchObject({ b_group_id: null, b_required: 1, b_colspan: 24 })
|
||
expect(rows[1]).toMatchObject({
|
||
b_group_id: outer.b_id,
|
||
b_colspan: 12,
|
||
b_readonly: 1,
|
||
b_default_value: 'X',
|
||
})
|
||
expect(rows[2].b_group_id).toBe(inner.b_id)
|
||
})
|
||
})
|
||
|
||
describe('buildQueryRender(query JSON → 查询入口)', () => {
|
||
const fields = [
|
||
{ b_field: 'b_no', b_name: '单号', b_type: 'input' },
|
||
{ b_field: 'b_status', b_name: '状态', b_type: 'select' },
|
||
{ b_field: 'b_created', b_name: '创建时间', b_type: 'datetime' },
|
||
{ b_field: 'b_denied', b_name: '无权限字段', b_type: 'input' },
|
||
]
|
||
const query = {
|
||
schemaVersion: 3,
|
||
views: [
|
||
{ code: 'all', label: '全部', order: 20 },
|
||
{
|
||
code: 'pending',
|
||
label: '待处理',
|
||
order: 10,
|
||
showCount: true,
|
||
group: '状态',
|
||
isDefault: true,
|
||
},
|
||
{
|
||
code: 'audited',
|
||
label: '已审核',
|
||
order: 15,
|
||
group: '审核状态',
|
||
conditionSql: "b_status = N'2'",
|
||
},
|
||
{
|
||
code: 'ghost',
|
||
label: '幽灵页签',
|
||
order: 30,
|
||
conditions: [{ field: 'b_denied', operator: 'eq', value: 'x' }],
|
||
},
|
||
],
|
||
keywordSearch: { fields: ['b_no', 'b_denied'] },
|
||
quickFilters: [
|
||
{ field: 'b_created', order: 20 },
|
||
{ field: 'b_status', order: 10 },
|
||
{ field: 'b_denied', order: 30 },
|
||
],
|
||
queryFields: {
|
||
b_status: { component: 'select', operator: 'eq' },
|
||
b_created: { component: 'datetime', operator: 'between' },
|
||
b_denied: { component: 'input', operator: 'eq' },
|
||
},
|
||
}
|
||
|
||
it('产出分组页签 / 关键词 / 快捷筛选 / 更多筛选,条目按 order 排序', () => {
|
||
const render = buildQueryRender(query, fields)
|
||
expect(render.views.map((v) => [v.code, v.group, v.showCount, v.conditionSql])).toEqual([
|
||
['pending', '状态', true, ''],
|
||
['audited', '审核状态', false, "b_status = N'2'"],
|
||
['all', '', false, ''],
|
||
['ghost', '', false, ''],
|
||
])
|
||
expect(render.defaultViewCodes).toEqual({ 状态: 'pending' })
|
||
expect(render.quickFilters.map((item) => item.field.b_field)).toEqual(['b_status', 'b_created'])
|
||
// 更多筛选 = 字段查询元数据全集去掉已进快捷筛选的字段(顺序即配置顺序)
|
||
expect(render.moreFilters.map((item) => item.field.b_field)).toEqual(['b_no', 'b_denied'])
|
||
expect(render.keywordSearch.items.map((item) => item.field.b_field)).toEqual([
|
||
'b_no',
|
||
'b_denied',
|
||
])
|
||
})
|
||
|
||
it('字段项统一为 FmsQueryControl 消费的形状,算子缺省按字段类型推导', () => {
|
||
const render = buildQueryRender(query, fields)
|
||
const statusItem = render.quickFilters[0]
|
||
expect(statusItem.config).toMatchObject({
|
||
b_field: 'b_status',
|
||
b_component: 'select',
|
||
b_operator: 'eq',
|
||
})
|
||
// 已登记的字段沿用配置算子
|
||
const deniedItem = render.moreFilters.find((item) => item.field.b_field === 'b_denied')
|
||
expect(deniedItem.config.b_operator).toBe('eq')
|
||
// queryFields 未登记的字段按 b_type 推导:input → like
|
||
const noItem = render.moreFilters.find((item) => item.field.b_field === 'b_no')
|
||
expect(noItem.config.b_operator).toBe('like')
|
||
})
|
||
|
||
it('无权限字段不进任何入口;旧结构页签引用它则整条丢弃(SQL 条件无法解析,原样保留)', () => {
|
||
const render = buildQueryRender(query, fields, new Set(['b_no', 'b_status', 'b_created']))
|
||
expect(render.views.map((v) => v.code)).toEqual(['pending', 'audited', 'all'])
|
||
expect(render.quickFilters.map((item) => item.field.b_field)).toEqual(['b_status', 'b_created'])
|
||
expect(render.keywordSearch.items.map((item) => item.field.b_field)).toEqual(['b_no'])
|
||
expect(render.moreFilters.map((item) => item.field.b_field)).toEqual(['b_no'])
|
||
})
|
||
|
||
it('老数据兼容:顶层 defaultViewCode 落到所属分组,结构化条件原样保留', () => {
|
||
const legacy = {
|
||
schemaVersion: 2,
|
||
defaultViewCode: 'pending',
|
||
views: [
|
||
{
|
||
code: 'pending',
|
||
label: '待处理',
|
||
order: 10,
|
||
conditions: [{ field: 'b_status', operator: 'eq', value: '1' }],
|
||
},
|
||
{ code: 'audited', label: '已审核', order: 20, group: '审核状态' },
|
||
],
|
||
queryFields: {},
|
||
}
|
||
const render = buildQueryRender(legacy, fields)
|
||
expect(render.defaultViewCodes).toEqual({ '': 'pending' })
|
||
expect(render.views[0].conditionSql).toBe('')
|
||
expect(render.views[0].conditions).toEqual([
|
||
{ field: 'b_status', operator: 'eq', value: '1' },
|
||
])
|
||
})
|
||
|
||
it('未配置查询时返回空结构', () => {
|
||
const render = buildQueryRender(null, fields)
|
||
expect(render.views).toEqual([])
|
||
expect(render.defaultViewCodes).toEqual({})
|
||
expect(render.keywordSearch).toBeNull()
|
||
expect(render.quickFilters).toEqual([])
|
||
expect(render.moreFilters).toEqual([])
|
||
})
|
||
})
|
||
|
||
describe('applyColumnSettings(栏位设置回写 JSON)', () => {
|
||
it('同层按设置顺序重排并更新宽度,保留分组结构', () => {
|
||
const view = {
|
||
schemaVersion: 1,
|
||
columns: [
|
||
{ field: 'a', width: 100 },
|
||
{ type: 'group', title: 'G', children: [{ field: 'b', width: 110 }, { field: 'c', width: 120 }] },
|
||
{ field: 'd', width: 130 },
|
||
],
|
||
}
|
||
const settings = [
|
||
{ b_field: 'd', b_width: 300 },
|
||
{ b_field: 'b', b_width: 200 },
|
||
{ b_field: 'a', b_width: 150 },
|
||
{ b_field: 'c', b_width: 120 },
|
||
]
|
||
const next = applyColumnSettings('view', view, settings)
|
||
expect(next.columns[0].field).toBe('d')
|
||
expect(next.columns[0].width).toBe(300)
|
||
expect(next.columns[1].type).toBe('group')
|
||
expect(next.columns[1].children.map((n) => n.field)).toEqual(['b', 'c'])
|
||
expect(next.columns[1].children[0].width).toBe(200)
|
||
expect(next.columns[2].field).toBe('a')
|
||
expect(next.columns[2].width).toBe(150)
|
||
})
|
||
})
|
||
|
||
describe('parseOptions(选项来源)', () => {
|
||
it('读取界面配置节点的 options 数组', () => {
|
||
const { options } = parseOptions({ options: [{ value: '1', label: '是' }, { value: '0', label: '否' }] })
|
||
expect(options.map((o) => o.value)).toEqual(['1', '0'])
|
||
})
|
||
|
||
it('读取 { options: [] } 形态并解析 optionConfig', () => {
|
||
const { options, optionConfig } = parseOptions({
|
||
options: { precision: 3, options: [{ value: 1, label: '一' }] },
|
||
})
|
||
expect(optionConfig.precision).toBe(3)
|
||
expect(options[0]).toMatchObject({ value: 1, label: '一' })
|
||
})
|
||
|
||
it('兼容旧字段行 b_options JSON 字符串', () => {
|
||
const { options } = parseOptions({ b_options: JSON.stringify(['A', 'B']) })
|
||
expect(options.map((o) => o.value)).toEqual(['A', 'B'])
|
||
})
|
||
|
||
it('无选项来源时返回空', () => {
|
||
expect(parseOptions({ b_type: 'input' })).toEqual({ optionConfig: {}, options: [] })
|
||
expect(parseOptions(null)).toEqual({ optionConfig: {}, options: [] })
|
||
})
|
||
})
|