Files
workspace/code/fms/fms-vue/tests/unit/schema-render.spec.js
T
2026-09-23 16:54:54 +08:00

203 lines
8.0 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.
/**
* 界面配置 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: 2,
defaultViewCode: 'pending',
views: [
{ code: 'all', label: '全部', order: 20 },
{ code: 'pending', label: '待处理', order: 10, showCount: true,
conditions: [{ field: 'b_status', operator: 'eq', value: '1' }] },
{ code: 'ghost', label: '幽灵页签', order: 30,
conditions: [{ field: 'b_denied', operator: 'eq', value: 'x' }] },
],
keywordSearch: { placeholder: '搜索', trigger: 'enter_or_debounce', fields: ['b_no', 'b_denied'] },
quickFilters: [
{ field: 'b_created', order: 20 },
{ field: 'b_status', order: 10 },
{ field: 'b_denied', order: 30 },
],
filterGroups: [
{ code: 'basic', label: '基础', order: 10, fields: ['b_no', 'b_status', 'b_denied'] },
{ code: 'time', label: '时间', order: 20, fields: ['b_created'] },
],
queryFields: {
b_status: { component: 'select', operator: 'eq' },
b_created: { component: 'datetime', operator: 'between' },
},
}
it('产出页签 / 关键词 / 快捷筛选 / 分组,条目按 order 排序', () => {
const render = buildQueryRender(query, fields)
expect(render.views.map((v) => [v.code, v.showCount])).toEqual([
['pending', true],
['all', false],
['ghost', false],
])
expect(render.defaultViewCode).toBe('pending')
expect(render.quickFilters.map((item) => item.field.b_field)).toEqual(['b_status', 'b_created'])
expect(render.filterGroups.map((g) => g.code)).toEqual(['basic', 'time'])
expect(render.keywordSearch.items.map((item) => item.field.b_field)).toEqual(['b_no'])
})
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 createdItem = render.filterGroups[1].items[0]
expect(createdItem.config.b_operator).toBe('between')
// queryFields 未登记的字段按 b_type 推导:input → like
const noItem = render.filterGroups[0].items[0]
expect(noItem.config.b_operator).toBe('like')
})
it('无权限字段不进任何入口;引用它的页签整条丢弃', () => {
const render = buildQueryRender(query, fields, new Set(['b_no', 'b_status', 'b_created']))
expect(render.views.map((v) => v.code)).toEqual(['pending', 'all'])
expect(render.quickFilters.map((item) => item.field.b_field)).toEqual(['b_status', 'b_created'])
expect(render.keywordSearch).toBeNull()
expect(render.filterGroups[0].items.map((item) => item.field.b_field)).toEqual(['b_no', 'b_status'])
})
it('未配置查询时返回空结构', () => {
const render = buildQueryRender(null, fields)
expect(render.views).toEqual([])
expect(render.defaultViewCode).toBe('')
expect(render.keywordSearch).toBeNull()
expect(render.quickFilters).toEqual([])
expect(render.filterGroups).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: [] })
})
})