163 lines
6.3 KiB
JavaScript
163 lines
6.3 KiB
JavaScript
/**
|
||
* 界面配置 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)
|
||
})
|
||
})
|