Files
workspace/code/fms/fms-vue/tests/unit/schema-json.spec.js
T
2026-09-20 22:05:20 +08:00

163 lines
6.3 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 公共工具测试(schemaJson)。
* 覆盖分组节点 id 的收集、取号与归一化:归一化必须是确定性纯函数(同一份 JSON 每次结果一致、
* 可重复执行),否则加载时的「归一化后与基线比较」会把补号误判成用户改动。
*/
import { describe, expect, it } from 'vitest'
import {
GROUP_CODE_MAX_LENGTH,
collectGroupIds,
nextGroupId,
normalizeGroupIds,
parseSchema,
schemaToJsonText,
validateGroupCode,
} from '@/utils/schemaJson'
const group = (over = {}) => ({ type: 'group', title: '分组', children: [], ...over })
describe('collectGroupIds / nextGroupId', () => {
it('收集分组 id 并去重(view 取 columns,edit 取 children)', () => {
const schema = {
schemaVersion: 1,
columns: [group({ id: 'group1' }), group({ id: 'group1' }), group({ id: 'basic' })],
}
expect([...collectGroupIds('view', schema)].toSorted()).toEqual(['basic', 'group1'])
expect(collectGroupIds('edit', schema).size).toBe(0)
})
it('nextGroupId 取已用编号里最小的未用正整数', () => {
expect(nextGroupId([])).toBe('group1')
expect(nextGroupId(['group1', 'group3'])).toBe('group2')
expect(nextGroupId(['group1', 'group2'])).toBe('group3')
// 非 group{n} 的自定义 id 不占编号
expect(nextGroupId(['basic'])).toBe('group1')
})
})
describe('normalizeGroupIds', () => {
it('缺 id 的分组按先序补号,嵌套分组一并补齐', () => {
const schema = {
schemaVersion: 1,
children: [
{ field: 'a' },
group({ title: '外层', children: [group({ title: '内层' }), { field: 'b' }] }),
group({ title: '另一个' }),
],
}
const next = normalizeGroupIds('edit', schema)
expect(next.children[0]).toEqual({ field: 'a' })
expect(next.children[1].id).toBe('group1')
expect(next.children[1].children[0].id).toBe('group2')
expect(next.children[2].id).toBe('group3')
// 原对象不被修改
expect(schema.children[1].id).toBeUndefined()
})
it('已有 id 保留、不重排:删除产生的空洞不回收', () => {
const schema = {
schemaVersion: 1,
columns: [group({ id: 'group2' }), group({})],
}
const next = normalizeGroupIds('view', schema)
expect(next.columns.map((n) => n.id)).toEqual(['group2', 'group1'])
})
it('重复 id 的后来者重新发号,自定义 id 原样保留', () => {
const schema = {
schemaVersion: 1,
columns: [group({ id: 'group1' }), group({ id: 'group1' }), group({ id: 'basic' })],
}
const next = normalizeGroupIds('view', schema)
expect(next.columns.map((n) => n.id)).toEqual(['group1', 'group2', 'basic'])
})
it('确定性且幂等:重复归一化结果一致,无改动时返回原对象', () => {
const schema = { schemaVersion: 1, columns: [group({}), group({})] }
const once = normalizeGroupIds('view', schema)
const twice = normalizeGroupIds('view', once)
expect(schemaToJsonText('view', once)).toBe(schemaToJsonText('view', twice))
expect(twice).toBe(once)
})
it('无分组节点时返回原对象,query 配置不受影响', () => {
const view = { schemaVersion: 1, columns: [{ field: 'a' }] }
expect(normalizeGroupIds('view', view)).toBe(view)
const query = { schemaVersion: 1, quick: [], groups: [{ logic: 'and', conditions: [] }] }
expect(normalizeGroupIds('query', query)).toBe(query)
})
it('解析库中 JSON 后可归一化(缺 columns 的畸形数据不报错)', () => {
expect(() => normalizeGroupIds('view', parseSchema('view', '{bad json'))).not.toThrow()
})
})
describe('validateGroupCode', () => {
const REQUIRED = '分组编码不能为空'
const FORMAT = '分组编码须以小写字母开头,且只能包含小写字母、数字和下划线'
const LENGTH = `分组编码长度需为 2-${GROUP_CODE_MAX_LENGTH} 个字符`
it('空值报不能为空', () => {
expect(validateGroupCode('')).toBe(REQUIRED)
expect(validateGroupCode(' ')).toBe(REQUIRED)
expect(validateGroupCode(null)).toBe(REQUIRED)
expect(validateGroupCode(undefined)).toBe(REQUIRED)
})
it('长度需落在 2 到上限之间(含边界)', () => {
expect(validateGroupCode('a')).toBe(LENGTH)
expect(validateGroupCode('a'.repeat(GROUP_CODE_MAX_LENGTH + 1))).toBe(LENGTH)
expect(validateGroupCode('ab')).toBe(true)
expect(validateGroupCode('a'.repeat(GROUP_CODE_MAX_LENGTH))).toBe(true)
})
it('格式与模块编码同构:小写字母开头,只含小写字母、数字与下划线', () => {
for (const code of ['1abc', 'Abc', 'a-b', 'a b', '分组']) {
expect(validateGroupCode(code)).toBe(FORMAT)
}
})
it('与本配置已有编码冲突时提示已被使用', () => {
const message = validateGroupCode('basic', { existingIds: ['basic'] })
expect(message).toContain('basic')
expect(message).toContain('已被本配置的其他分组使用')
expect(message).not.toContain('多语言')
})
it('与多语言资源里残留的编码冲突时给出可区分的提示', () => {
const message = validateGroupCode('group2', { reservedIds: ['group2'] })
expect(message).toContain('多语言')
})
it('合法自定义编码通过', () => {
expect(validateGroupCode('basic_info')).toBe(true)
expect(validateGroupCode('cw_fee_detail')).toBe(true)
})
it('group{n} 形式不冲突即允许', () => {
expect(validateGroupCode('group7', { existingIds: ['group1'], reservedIds: ['group2'] })).toBe(
true,
)
})
it('编码集合同时兼容数组与 Set', () => {
expect(validateGroupCode('basic', { existingIds: new Set(['basic']) })).toBe(
validateGroupCode('basic', { existingIds: ['basic'] }),
)
})
it('输入首尾空白归一后再比对与判定', () => {
expect(validateGroupCode(' basic ', { existingIds: ['basic'] })).toContain('basic')
expect(validateGroupCode(' ok_code ')).toBe(true)
})
it('保留字 ungrouped 不可占用(结构树虚拟未分组节点用 g:ungrouped)', () => {
expect(validateGroupCode('ungrouped')).toContain('系统保留字')
})
it('长度上限可覆盖', () => {
expect(validateGroupCode('a'.repeat(11), { maxLength: 10 })).toContain('2-10')
expect(validateGroupCode('a'.repeat(10), { maxLength: 10 })).toBe(true)
})
})