flnxi-Wu/lib/__tests__/category-batch-operations.test.ts
WuYongJie 63ec6b7dfb fix: 修复排行榜按钮消失、tag排序和输入框浅色模式问题
1. 排行榜悬浮窗关闭后小按钮消失问题
   - 修复面板居中模式关闭时按钮位置错误
   - 保存并恢复按钮的居中状态

2. 首页tag按钮按素材数量排序
   - 添加按素材数量降序排序逻辑

3. 上传弹窗输入框浅色模式文字不可见
   - 添加 text-slate-900 确保浅色模式下文字可见
   - 添加 placeholder 样式确保占位符正确显示
2025-12-14 21:41:29 +08:00

500 lines
14 KiB
TypeScript
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.

/**
* 分类管理批量操作工具测试
*
* 测试覆盖:
* - CSV解析功能
* - JSON解析功能
* - CSV导出功能
* - JSON导出功能
*
* 注意:数据库相关的函数需要集成测试或mock
*
* @jest-environment node
* @created 2024-12-13
*/
import {
parseCSVOptions,
parseJSONOptions,
exportToCSV,
exportToJSON,
ImportedOption,
ExportedField,
} from '../category-batch-operations'
describe('category-batch-operations', () => {
describe('parseCSVOptions', () => {
it('should parse simple CSV without header', () => {
const csv = `categoryName,店铺A
categoryName,店铺B
sceneName,场景1`
const result = parseCSVOptions(csv)
expect(result).toHaveLength(3)
expect(result[0]).toEqual({ fieldKey: 'categoryName', value: '店铺A', label: undefined })
expect(result[1]).toEqual({ fieldKey: 'categoryName', value: '店铺B', label: undefined })
expect(result[2]).toEqual({ fieldKey: 'sceneName', value: '场景1', label: undefined })
})
it('should parse CSV with header row', () => {
const csv = `fieldKey,value,label
categoryName,店铺A,店铺A显示名
categoryName,店铺B,店铺B显示名`
const result = parseCSVOptions(csv)
expect(result).toHaveLength(2)
expect(result[0]).toEqual({ fieldKey: 'categoryName', value: '店铺A', label: '店铺A显示名' })
expect(result[1]).toEqual({ fieldKey: 'categoryName', value: '店铺B', label: '店铺B显示名' })
})
it('should handle CSV with optional label', () => {
const csv = `categoryName,店铺A,显示名
categoryName,店铺B`
const result = parseCSVOptions(csv)
expect(result).toHaveLength(2)
expect(result[0].label).toBe('显示名')
expect(result[1].label).toBeUndefined()
})
it('should handle quoted values with commas', () => {
const csv = `categoryName,"店铺A,分店",显示名
categoryName,"店铺B"`
const result = parseCSVOptions(csv)
expect(result).toHaveLength(2)
expect(result[0].value).toBe('店铺A,分店')
expect(result[1].value).toBe('店铺B')
})
it('should handle escaped quotes in values', () => {
const csv = `categoryName,"店铺""A""",显示名`
const result = parseCSVOptions(csv)
expect(result).toHaveLength(1)
expect(result[0].value).toBe('店铺"A"')
})
it('should skip empty lines', () => {
const csv = `categoryName,店铺A
categoryName,店铺B
`
const result = parseCSVOptions(csv)
expect(result).toHaveLength(2)
})
it('should skip lines with insufficient columns', () => {
const csv = `categoryName,店铺A
invalidLine
categoryName,店铺B`
const result = parseCSVOptions(csv)
expect(result).toHaveLength(2)
})
it('should return empty array for empty input', () => {
expect(parseCSVOptions('')).toEqual([])
expect(parseCSVOptions(' ')).toEqual([])
})
it('should trim whitespace from values', () => {
const csv = ` categoryName , 店铺A , 显示名 `
const result = parseCSVOptions(csv)
expect(result[0].fieldKey).toBe('categoryName')
expect(result[0].value).toBe('店铺A')
expect(result[0].label).toBe('显示名')
})
})
describe('parseJSONOptions', () => {
describe('array format', () => {
it('should parse array of options', () => {
const json = JSON.stringify([
{ fieldKey: 'categoryName', value: '店铺A' },
{ fieldKey: 'categoryName', value: '店铺B', label: '显示名B' },
])
const result = parseJSONOptions(json)
expect(result).toHaveLength(2)
expect(result[0]).toEqual({ fieldKey: 'categoryName', value: '店铺A', label: undefined })
expect(result[1]).toEqual({ fieldKey: 'categoryName', value: '店铺B', label: '显示名B' })
})
it('should filter out invalid items', () => {
const json = JSON.stringify([
{ fieldKey: 'categoryName', value: '店铺A' },
{ fieldKey: 'categoryName' }, // missing value
{ value: '店铺B' }, // missing fieldKey
{ fieldKey: '', value: '' }, // empty values
])
const result = parseJSONOptions(json)
expect(result).toHaveLength(1)
})
it('should convert non-string values to strings', () => {
const json = JSON.stringify([
{ fieldKey: 'count', value: 123, label: 456 },
])
const result = parseJSONOptions(json)
expect(result[0].fieldKey).toBe('count')
expect(result[0].value).toBe('123')
expect(result[0].label).toBe('456')
})
})
describe('object format', () => {
it('should parse object with string arrays', () => {
const json = JSON.stringify({
categoryName: ['店铺A', '店铺B'],
sceneName: ['场景1'],
})
const result = parseJSONOptions(json)
expect(result).toHaveLength(3)
expect(result.filter(o => o.fieldKey === 'categoryName')).toHaveLength(2)
expect(result.filter(o => o.fieldKey === 'sceneName')).toHaveLength(1)
})
it('should parse object with option objects', () => {
const json = JSON.stringify({
categoryName: [
{ value: '店铺A', label: '显示名A' },
{ value: '店铺B' },
],
})
const result = parseJSONOptions(json)
expect(result).toHaveLength(2)
expect(result[0]).toEqual({ fieldKey: 'categoryName', value: '店铺A', label: '显示名A' })
expect(result[1]).toEqual({ fieldKey: 'categoryName', value: '店铺B', label: undefined })
})
it('should handle mixed string and object values', () => {
const json = JSON.stringify({
categoryName: [
'店铺A',
{ value: '店铺B', label: '显示名B' },
],
})
const result = parseJSONOptions(json)
expect(result).toHaveLength(2)
expect(result[0].value).toBe('店铺A')
expect(result[1].value).toBe('店铺B')
expect(result[1].label).toBe('显示名B')
})
it('should skip non-array values', () => {
const json = JSON.stringify({
categoryName: ['店铺A'],
invalidField: 'not an array',
anotherInvalid: 123,
})
const result = parseJSONOptions(json)
expect(result).toHaveLength(1)
expect(result[0].fieldKey).toBe('categoryName')
})
})
it('should return empty array for invalid JSON', () => {
expect(parseJSONOptions('invalid json')).toEqual([])
expect(parseJSONOptions('{broken')).toEqual([])
})
it('should return empty array for null/primitive JSON', () => {
expect(parseJSONOptions('null')).toEqual([])
expect(parseJSONOptions('"string"')).toEqual([])
expect(parseJSONOptions('123')).toEqual([])
})
it('should return empty array for empty input', () => {
expect(parseJSONOptions('')).toEqual([])
})
})
describe('exportToCSV', () => {
it('should export fields to CSV format', () => {
const fields: ExportedField[] = [
{
key: 'categoryName',
label: '使用店铺',
filterDisplayName: null,
filterOrder: 0,
isFilterEnabled: true,
options: [
{ value: '店铺A', label: '显示名A' },
{ value: '店铺B', label: null },
],
},
]
const result = exportToCSV(fields)
const lines = result.split('\n')
expect(lines[0]).toBe('fieldKey,value,label')
expect(lines[1]).toBe('categoryName,店铺A,显示名A')
expect(lines[2]).toBe('categoryName,店铺B,')
})
it('should escape values with commas', () => {
const fields: ExportedField[] = [
{
key: 'categoryName',
label: '使用店铺',
filterDisplayName: null,
filterOrder: 0,
isFilterEnabled: true,
options: [
{ value: '店铺A,分店', label: null },
],
},
]
const result = exportToCSV(fields)
const lines = result.split('\n')
expect(lines[1]).toBe('categoryName,"店铺A,分店",')
})
it('should escape values with quotes', () => {
const fields: ExportedField[] = [
{
key: 'categoryName',
label: '使用店铺',
filterDisplayName: null,
filterOrder: 0,
isFilterEnabled: true,
options: [
{ value: '店铺"A"', label: null },
],
},
]
const result = exportToCSV(fields)
const lines = result.split('\n')
expect(lines[1]).toBe('categoryName,"店铺""A""",')
})
it('should escape values with newlines', () => {
const fields: ExportedField[] = [
{
key: 'categoryName',
label: '使用店铺',
filterDisplayName: null,
filterOrder: 0,
isFilterEnabled: true,
options: [
{ value: '店铺A\n分店', label: null },
],
},
]
const result = exportToCSV(fields)
const lines = result.split('\n')
// 第二行应该包含转义的换行符
expect(lines[1]).toContain('"店铺A')
})
it('should handle empty options', () => {
const fields: ExportedField[] = [
{
key: 'categoryName',
label: '使用店铺',
filterDisplayName: null,
filterOrder: 0,
isFilterEnabled: true,
options: [],
},
]
const result = exportToCSV(fields)
const lines = result.split('\n')
expect(lines).toHaveLength(1) // Only header
expect(lines[0]).toBe('fieldKey,value,label')
})
it('should handle multiple fields', () => {
const fields: ExportedField[] = [
{
key: 'categoryName',
label: '使用店铺',
filterDisplayName: null,
filterOrder: 0,
isFilterEnabled: true,
options: [{ value: '店铺A', label: null }],
},
{
key: 'sceneName',
label: '场景类别',
filterDisplayName: null,
filterOrder: 1,
isFilterEnabled: true,
options: [{ value: '场景1', label: null }],
},
]
const result = exportToCSV(fields)
const lines = result.split('\n')
expect(lines).toHaveLength(3)
expect(lines[1]).toContain('categoryName')
expect(lines[2]).toContain('sceneName')
})
})
describe('exportToJSON', () => {
it('should export fields to JSON format', () => {
const fields: ExportedField[] = [
{
key: 'categoryName',
label: '使用店铺',
filterDisplayName: '店铺筛选',
filterOrder: 0,
isFilterEnabled: true,
options: [
{ value: '店铺A', label: '显示名A' },
],
},
]
const result = exportToJSON(fields)
const parsed = JSON.parse(result)
expect(parsed).toHaveLength(1)
expect(parsed[0].key).toBe('categoryName')
expect(parsed[0].label).toBe('使用店铺')
expect(parsed[0].filterDisplayName).toBe('店铺筛选')
expect(parsed[0].options).toHaveLength(1)
})
it('should format JSON with indentation', () => {
const fields: ExportedField[] = [
{
key: 'categoryName',
label: '使用店铺',
filterDisplayName: null,
filterOrder: 0,
isFilterEnabled: true,
options: [],
},
]
const result = exportToJSON(fields)
// Should have newlines (formatted)
expect(result).toContain('\n')
// Should have indentation
expect(result).toContain(' ')
})
it('should handle empty array', () => {
const result = exportToJSON([])
expect(JSON.parse(result)).toEqual([])
})
})
describe('CSV round-trip', () => {
it('should be able to parse exported CSV', () => {
const originalFields: ExportedField[] = [
{
key: 'categoryName',
label: '使用店铺',
filterDisplayName: null,
filterOrder: 0,
isFilterEnabled: true,
options: [
{ value: '店铺A', label: '显示名A' },
{ value: '店铺B', label: null },
],
},
{
key: 'sceneName',
label: '场景类别',
filterDisplayName: null,
filterOrder: 1,
isFilterEnabled: true,
options: [
{ value: '场景1', label: null },
],
},
]
const csv = exportToCSV(originalFields)
const parsed = parseCSVOptions(csv)
expect(parsed).toHaveLength(3)
expect(parsed[0].fieldKey).toBe('categoryName')
expect(parsed[0].value).toBe('店铺A')
expect(parsed[0].label).toBe('显示名A')
})
})
describe('type definitions', () => {
it('BatchOperationResult should have correct structure', () => {
const result = {
success: true,
processed: 5,
failed: 1,
errors: [{ key: 'field1', error: 'error message' }],
}
expect(result.success).toBe(true)
expect(result.processed).toBe(5)
expect(result.failed).toBe(1)
expect(result.errors).toHaveLength(1)
})
it('ImportedOption should have correct structure', () => {
const option: ImportedOption = {
fieldKey: 'categoryName',
value: '店铺A',
label: '显示名',
}
expect(option.fieldKey).toBe('categoryName')
expect(option.value).toBe('店铺A')
expect(option.label).toBe('显示名')
})
it('ExportedField should have correct structure', () => {
const field: ExportedField = {
key: 'categoryName',
label: '使用店铺',
filterDisplayName: '店铺筛选',
filterOrder: 0,
isFilterEnabled: true,
options: [{ value: '店铺A', label: null }],
}
expect(field.key).toBe('categoryName')
expect(field.isFilterEnabled).toBe(true)
expect(field.options).toHaveLength(1)
})
})
})