Files
2026-09-27 21:59:13 +08:00

278 lines
10 KiB
JavaScript
Raw Permalink 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.
/**
* 界面配置 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('行节点 → lines,行属性与嵌套分组保留', () => {
const edit = {
schemaVersion: 2,
children: [
{ type: 'row', children: [{ field: 'code', span: 24, required: true }] },
{
type: 'group',
title: '基础',
children: [
{
type: 'row',
children: [
{ field: 'name', span: 12, readonly: true, defaultValue: 'X' },
{ field: 'age', span: 12 },
],
},
{ type: 'group', title: '扩展', children: [{ type: 'row', children: [{ field: 'remark', span: 24 }] }] },
],
},
],
}
const { groups, rows, lines } = buildEditRender(edit)
expect(rows.map((r) => r.b_field)).toEqual(['code', 'name', 'age', '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)
// 行是显式结构:同一行 b_line 相同、跨行不同,lines 按行聚合
expect(rows[1].b_line).toBe(rows[2].b_line)
expect(rows[0].b_line).not.toBe(rows[1].b_line)
expect(lines.map((l) => l.rows.map((r) => r.b_field))).toEqual([
['code'],
['name', 'age'],
['remark'],
])
expect(lines.every((l) => l.rows.every((r) => r.b_line === l.key))).toBe(true)
})
it('老结构(平铺 + newline)容错折行,只读不回写', () => {
const edit = {
schemaVersion: 1,
children: [
{ field: 'a', span: 8 },
{ field: 'b', span: 8 },
{ field: 'c', span: 8, newline: true },
{ field: 'd', span: 24 },
],
}
const { lines } = buildEditRender(edit)
expect(lines.map((l) => l.rows.map((r) => r.b_field))).toEqual([['a', 'b'], ['c'], ['d']])
})
})
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: [] })
})
})