1. 排行榜悬浮窗关闭后小按钮消失问题 - 修复面板居中模式关闭时按钮位置错误 - 保存并恢复按钮的居中状态 2. 首页tag按钮按素材数量排序 - 添加按素材数量降序排序逻辑 3. 上传弹窗输入框浅色模式文字不可见 - 添加 text-slate-900 确保浅色模式下文字可见 - 添加 placeholder 样式确保占位符正确显示
317 lines
9.9 KiB
TypeScript
317 lines
9.9 KiB
TypeScript
/**
|
||
* 模板列表 API 测试
|
||
*
|
||
* 测试 app/api/templates/route.ts 的核心功能
|
||
*
|
||
* 主要测试点:
|
||
* 1. 内置模板生成逻辑
|
||
* 2. 自定义模板查询(排除内置模板)
|
||
* 3. 模板合并和排序
|
||
*/
|
||
|
||
// 内置模板配置(与 route.ts 保持一致)
|
||
const BUILTIN_TEMPLATE_KEYS = [
|
||
'3dmax-material',
|
||
'psd-material',
|
||
'audio-material',
|
||
'video-material',
|
||
'model-material',
|
||
'other-material',
|
||
]
|
||
|
||
const BUILTIN_TEMPLATE_NAMES: Record<string, string> = {
|
||
'3dmax-material': '3DMAX 素材模板',
|
||
'psd-material': 'PSD 素材模板',
|
||
'audio-material': '音频素材模板',
|
||
'video-material': '视频素材模板',
|
||
'model-material': '模特素材模板',
|
||
'other-material': '其他素材模板',
|
||
}
|
||
|
||
function createBuiltinTemplate(key: string) {
|
||
return {
|
||
id: `builtin-${key}`,
|
||
name: BUILTIN_TEMPLATE_NAMES[key] || key,
|
||
isBuiltin: true,
|
||
}
|
||
}
|
||
|
||
describe('模板列表 API - 内置模板配置', () => {
|
||
describe('BUILTIN_TEMPLATE_KEYS', () => {
|
||
it('应该包含6个内置模板key', () => {
|
||
expect(BUILTIN_TEMPLATE_KEYS).toHaveLength(6)
|
||
})
|
||
|
||
it('应该包含所有预期的模板key', () => {
|
||
const expectedKeys = [
|
||
'3dmax-material',
|
||
'psd-material',
|
||
'audio-material',
|
||
'video-material',
|
||
'model-material',
|
||
'other-material',
|
||
]
|
||
expect(BUILTIN_TEMPLATE_KEYS).toEqual(expectedKeys)
|
||
})
|
||
})
|
||
|
||
describe('BUILTIN_TEMPLATE_NAMES', () => {
|
||
it('每个模板key都应该有对应的名称', () => {
|
||
for (const key of BUILTIN_TEMPLATE_KEYS) {
|
||
expect(BUILTIN_TEMPLATE_NAMES[key]).toBeDefined()
|
||
expect(typeof BUILTIN_TEMPLATE_NAMES[key]).toBe('string')
|
||
expect(BUILTIN_TEMPLATE_NAMES[key].length).toBeGreaterThan(0)
|
||
}
|
||
})
|
||
})
|
||
|
||
describe('createBuiltinTemplate', () => {
|
||
it('应该生成正确格式的内置模板对象', () => {
|
||
const template = createBuiltinTemplate('3dmax-material')
|
||
|
||
expect(template).toEqual({
|
||
id: 'builtin-3dmax-material',
|
||
name: '3DMAX 素材模板',
|
||
isBuiltin: true,
|
||
})
|
||
})
|
||
|
||
it('内置模板ID应该以builtin-开头', () => {
|
||
for (const key of BUILTIN_TEMPLATE_KEYS) {
|
||
const template = createBuiltinTemplate(key)
|
||
expect(template.id).toMatch(/^builtin-/)
|
||
expect(template.id).toBe(`builtin-${key}`)
|
||
}
|
||
})
|
||
|
||
it('所有内置模板应该标记isBuiltin为true', () => {
|
||
for (const key of BUILTIN_TEMPLATE_KEYS) {
|
||
const template = createBuiltinTemplate(key)
|
||
expect(template.isBuiltin).toBe(true)
|
||
}
|
||
})
|
||
|
||
it('未知key应该使用key作为名称', () => {
|
||
const template = createBuiltinTemplate('unknown-key')
|
||
expect(template.name).toBe('unknown-key')
|
||
})
|
||
})
|
||
})
|
||
|
||
describe('模板列表 API - 模板过滤逻辑', () => {
|
||
/**
|
||
* 测试内置模板过滤逻辑
|
||
* 验证:当 enabledBuiltinTemplates 为空数组时,应该返回所有内置模板
|
||
*/
|
||
describe('内置模板启用过滤', () => {
|
||
it('enabledBuiltinTemplates为空数组时应返回所有内置模板', () => {
|
||
const enabledBuiltinTemplates: string[] = []
|
||
|
||
const builtinTemplates = BUILTIN_TEMPLATE_KEYS
|
||
.filter(key => enabledBuiltinTemplates.length === 0 || enabledBuiltinTemplates.includes(key))
|
||
.map(key => createBuiltinTemplate(key))
|
||
|
||
expect(builtinTemplates).toHaveLength(6)
|
||
})
|
||
|
||
it('enabledBuiltinTemplates有值时应只返回启用的模板', () => {
|
||
const enabledBuiltinTemplates = ['3dmax-material', 'psd-material']
|
||
|
||
const builtinTemplates = BUILTIN_TEMPLATE_KEYS
|
||
.filter(key => enabledBuiltinTemplates.length === 0 || enabledBuiltinTemplates.includes(key))
|
||
.map(key => createBuiltinTemplate(key))
|
||
|
||
expect(builtinTemplates).toHaveLength(2)
|
||
expect(builtinTemplates.map(t => t.id)).toEqual([
|
||
'builtin-3dmax-material',
|
||
'builtin-psd-material',
|
||
])
|
||
})
|
||
|
||
it('enabledBuiltinTemplates只包含一个时应只返回该模板', () => {
|
||
const enabledBuiltinTemplates = ['video-material']
|
||
|
||
const builtinTemplates = BUILTIN_TEMPLATE_KEYS
|
||
.filter(key => enabledBuiltinTemplates.length === 0 || enabledBuiltinTemplates.includes(key))
|
||
.map(key => createBuiltinTemplate(key))
|
||
|
||
expect(builtinTemplates).toHaveLength(1)
|
||
expect(builtinTemplates[0].id).toBe('builtin-video-material')
|
||
})
|
||
})
|
||
|
||
/**
|
||
* 测试自定义模板排除内置模板的逻辑
|
||
* 这是本次修改的核心功能
|
||
*/
|
||
describe('自定义模板排除内置模板', () => {
|
||
it('应该正确识别内置模板ID格式', () => {
|
||
const builtinIds = [
|
||
'builtin-3dmax-material',
|
||
'builtin-psd-material',
|
||
'builtin-audio-material',
|
||
]
|
||
|
||
for (const id of builtinIds) {
|
||
expect(id.startsWith('builtin-')).toBe(true)
|
||
}
|
||
})
|
||
|
||
it('自定义模板ID不应该以builtin-开头', () => {
|
||
const customIds = [
|
||
'custom-template-1',
|
||
'my-template',
|
||
'user-created-template',
|
||
]
|
||
|
||
for (const id of customIds) {
|
||
expect(id.startsWith('builtin-')).toBe(false)
|
||
}
|
||
})
|
||
|
||
it('模拟过滤逻辑:应该排除builtin-开头的模板', () => {
|
||
// 模拟数据库中的模板数据
|
||
const allDbTemplates = [
|
||
{ id: 'builtin-3dmax-material', name: '3DMAX 素材模板', status: 'active' },
|
||
{ id: 'builtin-psd-material', name: 'PSD 素材模板', status: 'active' },
|
||
{ id: 'custom-template-1', name: '自定义模板1', status: 'active' },
|
||
{ id: 'my-template', name: '我的模板', status: 'active' },
|
||
]
|
||
|
||
// 模拟 Prisma 查询的 NOT startsWith 逻辑
|
||
const customTemplates = allDbTemplates.filter(t =>
|
||
t.status === 'active' && !t.id.startsWith('builtin-')
|
||
)
|
||
|
||
expect(customTemplates).toHaveLength(2)
|
||
expect(customTemplates.map(t => t.id)).toEqual([
|
||
'custom-template-1',
|
||
'my-template',
|
||
])
|
||
})
|
||
|
||
it('当数据库中没有自定义模板时应返回空数组', () => {
|
||
const allDbTemplates = [
|
||
{ id: 'builtin-3dmax-material', name: '3DMAX 素材模板', status: 'active' },
|
||
{ id: 'builtin-psd-material', name: 'PSD 素材模板', status: 'active' },
|
||
]
|
||
|
||
const customTemplates = allDbTemplates.filter(t =>
|
||
t.status === 'active' && !t.id.startsWith('builtin-')
|
||
)
|
||
|
||
expect(customTemplates).toHaveLength(0)
|
||
})
|
||
|
||
it('应该排除非active状态的模板', () => {
|
||
const allDbTemplates = [
|
||
{ id: 'custom-template-1', name: '自定义模板1', status: 'active' },
|
||
{ id: 'custom-template-2', name: '自定义模板2', status: 'draft' },
|
||
{ id: 'custom-template-3', name: '自定义模板3', status: 'archived' },
|
||
]
|
||
|
||
const customTemplates = allDbTemplates.filter(t =>
|
||
t.status === 'active' && !t.id.startsWith('builtin-')
|
||
)
|
||
|
||
expect(customTemplates).toHaveLength(1)
|
||
expect(customTemplates[0].id).toBe('custom-template-1')
|
||
})
|
||
})
|
||
|
||
/**
|
||
* 测试模板合并逻辑
|
||
*/
|
||
describe('模板合并', () => {
|
||
it('合并后内置模板应该在前面', () => {
|
||
const builtinTemplates = [
|
||
{ id: 'builtin-3dmax-material', name: '3DMAX', isBuiltin: true },
|
||
]
|
||
const customTemplates = [
|
||
{ id: 'custom-1', name: '自定义1' },
|
||
]
|
||
|
||
const allTemplates = [
|
||
...builtinTemplates,
|
||
...customTemplates.map(t => ({ ...t, isBuiltin: false })),
|
||
]
|
||
|
||
expect(allTemplates[0].isBuiltin).toBe(true)
|
||
expect(allTemplates[1].isBuiltin).toBe(false)
|
||
})
|
||
|
||
it('自定义模板应该标记isBuiltin为false', () => {
|
||
const customTemplates = [
|
||
{ id: 'custom-1', name: '自定义1' },
|
||
{ id: 'custom-2', name: '自定义2' },
|
||
]
|
||
|
||
const merged = customTemplates.map(t => ({ ...t, isBuiltin: false }))
|
||
|
||
for (const t of merged) {
|
||
expect(t.isBuiltin).toBe(false)
|
||
}
|
||
})
|
||
|
||
it('合并后不应该有重复的模板', () => {
|
||
const builtinTemplates = BUILTIN_TEMPLATE_KEYS.map(key => createBuiltinTemplate(key))
|
||
const customTemplates = [
|
||
{ id: 'custom-1', name: '自定义1', isBuiltin: false },
|
||
]
|
||
|
||
const allTemplates = [...builtinTemplates, ...customTemplates]
|
||
const ids = allTemplates.map(t => t.id)
|
||
const uniqueIds = new Set(ids)
|
||
|
||
expect(uniqueIds.size).toBe(ids.length)
|
||
})
|
||
})
|
||
})
|
||
|
||
describe('模板列表 API - 边界情况', () => {
|
||
it('空的enabledBuiltinTemplates数组应该表示全部启用', () => {
|
||
const enabledBuiltinTemplates: string[] = []
|
||
const isAllEnabled = enabledBuiltinTemplates.length === 0
|
||
expect(isAllEnabled).toBe(true)
|
||
})
|
||
|
||
it('null或undefined的enabledBuiltinTemplates应该被处理为全部启用', () => {
|
||
// 模拟从数据库获取的值
|
||
const settingsValue: string | null = null
|
||
|
||
let enabledBuiltinTemplates: string[] = []
|
||
if (settingsValue) {
|
||
try {
|
||
const parsed = JSON.parse(settingsValue)
|
||
if (Array.isArray(parsed) && parsed.length > 0) {
|
||
enabledBuiltinTemplates = parsed
|
||
}
|
||
} catch {
|
||
// 解析失败,保持空数组
|
||
}
|
||
}
|
||
|
||
// 空数组表示全部启用
|
||
const builtinTemplates = BUILTIN_TEMPLATE_KEYS
|
||
.filter(key => enabledBuiltinTemplates.length === 0 || enabledBuiltinTemplates.includes(key))
|
||
|
||
expect(builtinTemplates).toHaveLength(6)
|
||
})
|
||
|
||
it('无效的JSON应该被处理为全部启用', () => {
|
||
const invalidJson = 'not-valid-json'
|
||
|
||
let enabledBuiltinTemplates: string[] = []
|
||
try {
|
||
const parsed = JSON.parse(invalidJson)
|
||
if (Array.isArray(parsed) && parsed.length > 0) {
|
||
enabledBuiltinTemplates = parsed
|
||
}
|
||
} catch {
|
||
// 解析失败,保持空数组
|
||
}
|
||
|
||
expect(enabledBuiltinTemplates).toHaveLength(0)
|
||
})
|
||
})
|