270 lines
8.4 KiB
JavaScript
270 lines
8.4 KiB
JavaScript
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||
import {
|
||
addRowUpdate,
|
||
applyTreePosition,
|
||
buildSortedTree,
|
||
buildTree,
|
||
decorateTreeNodes,
|
||
findNodePosition,
|
||
rebuildTreePositions,
|
||
renumberSiblings,
|
||
sortTree,
|
||
} from '@/utils/tree'
|
||
|
||
describe('utils/tree buildTree', () => {
|
||
it('空数组返回空树', () => {
|
||
expect(buildTree([])).toEqual([])
|
||
})
|
||
|
||
it('无父节点的节点成为根', () => {
|
||
const list = [
|
||
{ b_id: 1, b_parent_id: null },
|
||
{ b_id: 2, b_parent_id: null },
|
||
]
|
||
const tree = buildTree(list)
|
||
expect(tree).toHaveLength(2)
|
||
expect(tree.map((n) => n.b_id)).toEqual([1, 2])
|
||
})
|
||
|
||
it('按 parentKey 挂载子节点', () => {
|
||
const list = [
|
||
{ b_id: 1, b_parent_id: null },
|
||
{ b_id: 2, b_parent_id: 1 },
|
||
{ b_id: 3, b_parent_id: 2 },
|
||
]
|
||
const tree = buildTree(list)
|
||
expect(tree).toHaveLength(1)
|
||
expect(tree[0].b_id).toBe(1)
|
||
expect(tree[0].children).toHaveLength(1)
|
||
expect(tree[0].children[0].b_id).toBe(2)
|
||
expect(tree[0].children[0].children[0].b_id).toBe(3)
|
||
})
|
||
|
||
it('父节点不存在的子节点提升为根(孤儿节点)', () => {
|
||
const list = [
|
||
{ b_id: 2, b_parent_id: 999 },
|
||
{ b_id: 3, b_parent_id: null },
|
||
]
|
||
const tree = buildTree(list)
|
||
expect(tree).toHaveLength(2)
|
||
})
|
||
|
||
it('支持自定义 idKey / parentKey / childrenKey', () => {
|
||
const list = [
|
||
{ id: 'a', pid: null },
|
||
{ id: 'b', pid: 'a' },
|
||
]
|
||
const tree = buildTree(list, 'id', 'pid', 'kids')
|
||
expect(tree[0].kids[0].id).toBe('b')
|
||
})
|
||
|
||
it('副作用说明:向父节点对象注入 children 属性(当前实现行为)', () => {
|
||
const list = [
|
||
{ b_id: 1, b_parent_id: null },
|
||
{ b_id: 2, b_parent_id: 1 },
|
||
]
|
||
buildTree(list)
|
||
// 当前实现直接修改传入的节点对象(parent[childrenKey] = []),
|
||
// 调用方若复用原数组需注意此副作用;若要求纯函数需后续改造
|
||
expect(Array.isArray(list[0].children)).toBe(true)
|
||
expect(list[0].children).toHaveLength(1)
|
||
})
|
||
})
|
||
|
||
describe('utils/tree sortTree', () => {
|
||
it('按 b_xh 升序排序', () => {
|
||
const tree = [{ b_xh: 3 }, { b_xh: 1 }, { b_xh: 2 }]
|
||
sortTree(tree)
|
||
expect(tree.map((n) => n.b_xh)).toEqual([1, 2, 3])
|
||
})
|
||
|
||
it('b_xh 相同按 b_id 排序', () => {
|
||
const tree = [{ b_xh: 1, b_id: 'b' }, { b_xh: 1, b_id: 'a' }]
|
||
sortTree(tree)
|
||
expect(tree.map((n) => n.b_id)).toEqual(['a', 'b'])
|
||
})
|
||
|
||
it('递归排序子节点', () => {
|
||
const tree = [
|
||
{
|
||
b_xh: 1,
|
||
children: [{ b_xh: 2 }, { b_xh: 1 }],
|
||
},
|
||
]
|
||
sortTree(tree)
|
||
expect(tree[0].children.map((n) => n.b_xh)).toEqual([1, 2])
|
||
})
|
||
|
||
it('返回原数组引用', () => {
|
||
const tree = [{ b_xh: 2 }, { b_xh: 1 }]
|
||
expect(sortTree(tree)).toBe(tree)
|
||
})
|
||
})
|
||
|
||
describe('utils/tree 可编辑树:建树与节点装饰', () => {
|
||
it('buildSortedTree 建树并排序,且不污染原始行(重复构建不累积子节点)', () => {
|
||
const rows = [
|
||
{ b_id: 'a', b_parent_id: null, b_xh: 20 },
|
||
{ b_id: 'b', b_parent_id: null, b_xh: 10 },
|
||
{ b_id: 'a1', b_parent_id: 'a', b_xh: 10 },
|
||
]
|
||
|
||
const tree = buildSortedTree(rows)
|
||
|
||
expect(tree.map((node) => node.b_id)).toEqual(['b', 'a'])
|
||
expect(tree[1].children.map((node) => node.b_id)).toEqual(['a1'])
|
||
// children 挂在浅拷贝上,原始行保持干净
|
||
expect(rows[0].children).toBeUndefined()
|
||
expect(buildSortedTree(rows)[1].children).toHaveLength(1)
|
||
})
|
||
|
||
it('decorateTreeNodes 预载类型图标,并强制指定类型非叶子', () => {
|
||
const Folder = () => null
|
||
const tree = [
|
||
{
|
||
b_id: 'a',
|
||
b_module_type: 'module',
|
||
children: [{ b_id: 'a1', b_module_type: 'data', children: [] }],
|
||
},
|
||
]
|
||
|
||
decorateTreeNodes(tree, {
|
||
typeField: 'b_module_type',
|
||
nonLeafValues: ['module'],
|
||
iconMap: { module: Folder },
|
||
})
|
||
|
||
expect(tree[0].isLeaf).toBe(false)
|
||
expect(tree[0].icon).toBe(Folder)
|
||
// 非 module 类型不改 isLeaf,图标表里没有的类型不写 icon
|
||
expect(tree[0].children[0].isLeaf).toBeUndefined()
|
||
expect(tree[0].children[0].icon).toBeUndefined()
|
||
})
|
||
})
|
||
|
||
describe('utils/tree 可编辑树:位置派生与就地变更', () => {
|
||
it('applyTreePosition 根节点的 b_depth 为 0、b_path 为 /编码/', () => {
|
||
const row = { b_id: 'a' }
|
||
applyTreePosition(row, null)
|
||
expect(row).toMatchObject({ b_depth: 0, b_path: '/a/' })
|
||
})
|
||
|
||
it('applyTreePosition 子节点继承父级路径并递增层级', () => {
|
||
const row = { b_id: 'a1' }
|
||
applyTreePosition(row, { b_depth: 2, b_path: '/a/b/' })
|
||
expect(row).toMatchObject({ b_depth: 3, b_path: '/a/b/a1/' })
|
||
})
|
||
|
||
it('findNodePosition 返回所在父数组与索引', () => {
|
||
const tree = [{ b_id: 'a', children: [{ b_id: 'a1' }, { b_id: 'a2' }] }]
|
||
|
||
expect(findNodePosition(tree, 'a')).toMatchObject({ index: 0 })
|
||
const found = findNodePosition(tree, 'a2')
|
||
expect(found.index).toBe(1)
|
||
expect(found.list.map((node) => node.b_id)).toEqual(['a1', 'a2'])
|
||
expect(findNodePosition(tree, 'not-exist')).toBe(null)
|
||
})
|
||
|
||
it('addRowUpdate 按 b_id 合并字段,避免同节点多条 update', () => {
|
||
const updated = []
|
||
addRowUpdate(updated, { b_id: 'a', b_xh: 20 })
|
||
addRowUpdate(updated, { b_id: 'a', b_parent_id: 'p' })
|
||
addRowUpdate(updated, { b_id: 'b', b_xh: 10 })
|
||
|
||
expect(updated).toEqual([
|
||
{ b_id: 'a', b_xh: 20, b_parent_id: 'p' },
|
||
{ b_id: 'b', b_xh: 10 },
|
||
])
|
||
})
|
||
|
||
it('renumberSiblings 按 10 步长重排并只收集变化的行', () => {
|
||
const a = { b_id: 'a', b_xh: 30 }
|
||
const b = { b_id: 'b', b_xh: 10 }
|
||
const updated = []
|
||
|
||
renumberSiblings([a, b], updated)
|
||
|
||
expect([a.b_xh, b.b_xh]).toEqual([10, 20])
|
||
expect(updated).toEqual([
|
||
{ b_id: 'a', b_xh: 10 },
|
||
{ b_id: 'b', b_xh: 20 },
|
||
])
|
||
})
|
||
|
||
it('rebuildTreePositions 整树收敛 b_depth / b_path,未变化的不重复收集', () => {
|
||
const tree = [
|
||
{
|
||
b_id: 'a',
|
||
b_depth: 0,
|
||
b_path: '/a/',
|
||
children: [{ b_id: 'a1', b_depth: 9, b_path: '/stale/', children: [] }],
|
||
},
|
||
]
|
||
const updated = []
|
||
|
||
rebuildTreePositions(tree, updated)
|
||
|
||
expect(tree[0].children[0]).toMatchObject({ b_depth: 1, b_path: '/a/a1/' })
|
||
// 根节点本来就是 0 / '/a/',不该产生一条无意义的 update
|
||
expect(updated).toEqual([{ b_id: 'a1', b_depth: 1, b_path: '/a/a1/' }])
|
||
})
|
||
|
||
it('rebuildTreePositions 根节点路径保留前导斜杠(回归:三份实现曾漂移)', () => {
|
||
const tree = [
|
||
{
|
||
b_id: 'base',
|
||
b_depth: 0,
|
||
b_path: '/base/',
|
||
children: [
|
||
{ b_id: 'base_otherdata', b_depth: 1, b_path: '/base/base_otherdata/', children: [] },
|
||
],
|
||
},
|
||
]
|
||
const updated = []
|
||
|
||
rebuildTreePositions(tree, updated)
|
||
|
||
expect(tree[0].b_path).toBe('/base/')
|
||
expect(tree[0].children[0].b_path).toBe('/base/base_otherdata/')
|
||
// 与库中既有格式一致,整棵树都不该被改动
|
||
expect(updated).toEqual([])
|
||
})
|
||
|
||
it('rebuildTreePositions 修正过期路径时补全前导斜杠', () => {
|
||
const tree = [{ b_id: 'base', b_depth: 3, b_path: 'stale/', children: [] }]
|
||
const updated = []
|
||
|
||
rebuildTreePositions(tree, updated)
|
||
|
||
expect(tree[0]).toMatchObject({ b_depth: 0, b_path: '/base/' })
|
||
expect(updated).toEqual([{ b_id: 'base', b_depth: 0, b_path: '/base/' }])
|
||
})
|
||
})
|
||
|
||
describe('utils/theme', () => {
|
||
beforeEach(() => {
|
||
document.documentElement.style.cssText = ''
|
||
document.documentElement.classList.remove('dark')
|
||
})
|
||
|
||
it('applyPrimaryColor 写入 --fms-primary CSS 变量', async () => {
|
||
const { applyPrimaryColor } = await import('@/theme/index')
|
||
applyPrimaryColor('#ff0000')
|
||
expect(document.documentElement.style.getPropertyValue('--fms-primary')).toBe('#ff0000')
|
||
})
|
||
|
||
it('applyPrimaryColor 空值不写入', async () => {
|
||
const { applyPrimaryColor } = await import('@/theme/index')
|
||
applyPrimaryColor('')
|
||
expect(document.documentElement.style.getPropertyValue('--fms-primary')).toBe('')
|
||
})
|
||
|
||
it('applyDarkMode true 添加 dark class,false 移除', async () => {
|
||
const { applyDarkMode } = await import('@/theme/index')
|
||
applyDarkMode(true)
|
||
expect(document.documentElement.classList.contains('dark')).toBe(true)
|
||
applyDarkMode(false)
|
||
expect(document.documentElement.classList.contains('dark')).toBe(false)
|
||
})
|
||
})
|