This commit is contained in:
2026-04-08 21:26:18 +08:00
commit 8fdc7ac0c3
401 changed files with 53093 additions and 0 deletions

View File

@@ -0,0 +1,229 @@
import type { Layer } from '@/components/editor/canvas/types'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { useComponentTemplates } from '@/composables/useComponentTemplates'
const RE_TPL_PREFIX = /^tpl_/
const RE_DEFAULT_NAME = /^模板 \d+$/
// 模拟 @vueuse/core 的 useLocalStorage
// useComponentTemplates 通过 useLocalStorage 管理持久化,
// 测试中用 ref 替代以隔离 localStorage 副作用
vi.mock('@vueuse/core', async () => {
const { ref } = await import('vue')
return {
useLocalStorage: (_key: string, defaultValue: any) => ref(defaultValue),
}
})
function createMockLayer(overrides: Partial<Layer> = {}): Layer {
return {
id: `layer_${Math.random().toString(36).slice(2, 6)}`,
type: 'rect',
x: 0,
y: 0,
width: 100,
height: 50,
...overrides,
}
}
describe('useComponentTemplates', () => {
let tpl: ReturnType<typeof useComponentTemplates>
beforeEach(() => {
vi.useFakeTimers()
tpl = useComponentTemplates()
})
afterEach(() => {
vi.useRealTimers()
})
// ──── saveTemplate() ────
describe('saveTemplate() 保存模板', () => {
it('应保存模板并返回模板对象', () => {
const layers = [createMockLayer()]
const result = tpl.saveTemplate('测试模板', layers)
expect(result).not.toBeNull()
expect(result!.name).toBe('测试模板')
expect(result!.layers).toHaveLength(1)
expect(result!.id).toMatch(RE_TPL_PREFIX)
expect(result!.createdAt).toBeTypeOf('number')
})
it('空图层数组时应返回 null', () => {
const result = tpl.saveTemplate('空模板', [])
expect(result).toBeNull()
})
it('名称为空字符串时应使用默认名称', () => {
const layers = [createMockLayer()]
const result = tpl.saveTemplate('', layers)
expect(result).not.toBeNull()
expect(result!.name).toMatch(RE_DEFAULT_NAME)
})
it('名称含前后空格时应自动 trim', () => {
const layers = [createMockLayer()]
const result = tpl.saveTemplate(' 模板名称 ', layers)
expect(result!.name).toBe('模板名称')
})
it('应深拷贝图层数据,修改原图层不影响模板', () => {
const layer = createMockLayer({ x: 10 })
const layers = [layer]
const result = tpl.saveTemplate('深拷贝测试', layers)
// 修改原图层
layer.x = 999
expect(result!.layers[0].x).toBe(10)
})
it('多次保存应生成不同的 id', () => {
const layers = [createMockLayer()]
vi.setSystemTime(new Date('2026-01-01T00:00:00Z'))
const t1 = tpl.saveTemplate('模板1', layers)
vi.setSystemTime(new Date('2026-01-01T00:00:01Z'))
const t2 = tpl.saveTemplate('模板2', layers)
expect(t1!.id).not.toBe(t2!.id)
})
})
// ──── templatessortedTemplates────
describe('templates 排序', () => {
it('空状态返回空数组', () => {
expect(tpl.templates.value).toEqual([])
})
it('应按 createdAt 降序排列(新模板在前)', () => {
const layers = [createMockLayer()]
vi.setSystemTime(new Date('2026-01-01T00:00:00Z'))
tpl.saveTemplate('旧模板', layers)
vi.setSystemTime(new Date('2026-01-02T00:00:00Z'))
tpl.saveTemplate('新模板', layers)
expect(tpl.templates.value).toHaveLength(2)
expect(tpl.templates.value[0].name).toBe('新模板')
expect(tpl.templates.value[1].name).toBe('旧模板')
})
})
// ──── getTemplate() ────
describe('getTemplate() 按 id 获取模板', () => {
it('存在时返回模板对象', () => {
const layers = [createMockLayer()]
const saved = tpl.saveTemplate('查找测试', layers)!
const found = tpl.getTemplate(saved.id)
expect(found).not.toBeNull()
expect(found!.id).toBe(saved.id)
expect(found!.name).toBe('查找测试')
})
it('不存在时返回 null', () => {
expect(tpl.getTemplate('nonexistent_id')).toBeNull()
})
})
// ──── removeTemplate() ────
describe('removeTemplate() 删除模板', () => {
it('应按 id 删除指定模板', () => {
const layers = [createMockLayer()]
const t1 = tpl.saveTemplate('模板1', layers)!
const t2 = tpl.saveTemplate('模板2', layers)!
tpl.removeTemplate(t1.id)
expect(tpl.templates.value).toHaveLength(1)
expect(tpl.templates.value[0].id).toBe(t2.id)
})
it('删除不存在的 id 不应报错', () => {
const layers = [createMockLayer()]
tpl.saveTemplate('模板', layers)
expect(() => tpl.removeTemplate('nonexistent')).not.toThrow()
expect(tpl.templates.value).toHaveLength(1)
})
})
// ──── renameTemplate() ────
describe('renameTemplate() 重命名模板', () => {
it('应更新指定模板的名称', () => {
const layers = [createMockLayer()]
const saved = tpl.saveTemplate('原始名称', layers)!
tpl.renameTemplate(saved.id, '新名称')
const updated = tpl.getTemplate(saved.id)
expect(updated!.name).toBe('新名称')
})
it('名称含空格时应自动 trim', () => {
const layers = [createMockLayer()]
const saved = tpl.saveTemplate('原始', layers)!
tpl.renameTemplate(saved.id, ' 修改后 ')
expect(tpl.getTemplate(saved.id)!.name).toBe('修改后')
})
it('重命名不存在的 id 不应报错', () => {
expect(() => tpl.renameTemplate('nonexistent', '新名称')).not.toThrow()
})
it('重命名不应影响其他字段', () => {
const layers = [createMockLayer({ x: 42 })]
const saved = tpl.saveTemplate('原始', layers)!
const originalCreatedAt = saved.createdAt
tpl.renameTemplate(saved.id, '新名称')
const updated = tpl.getTemplate(saved.id)!
expect(updated.createdAt).toBe(originalCreatedAt)
expect(updated.layers[0].x).toBe(42)
})
})
// ──── 边界情况 ────
describe('边界情况', () => {
it('保存含多个图层的模板', () => {
const layers = [
createMockLayer({ x: 0, y: 0 }),
createMockLayer({ x: 100, y: 100 }),
createMockLayer({ x: 200, y: 200 }),
]
const result = tpl.saveTemplate('多图层模板', layers)
expect(result!.layers).toHaveLength(3)
})
it('图层含嵌套 config 对象时应深拷贝', () => {
const config = { fill: 'red', nested: { value: 1 } }
const layer = createMockLayer({ config })
const result = tpl.saveTemplate('嵌套配置', [layer])
// 修改原始 config
config.nested.value = 999
expect(result!.layers[0].config!.nested.value).toBe(1)
})
})
})