| 1 | import { describe, expect, it } from 'vitest' |
| 2 | import { |
| 3 | buildStyleCaseOptions, |
| 4 | filterByStyleCase, |
| 5 | filterByStyleKeyword, |
| 6 | parseStyleCases |
| 7 | } from '../../../src/renderer/src/lib/style-case' |
| 8 | |
| 9 | describe('style case filters', () => { |
| 10 | it('splits, trims and deduplicates style cases', () => { |
| 11 | expect(parseStyleCases('技术分享、 产品发布、技术分享')).toEqual(['技术分享', '产品发布']) |
| 12 | expect(parseStyleCases('教学,培训;工作坊')).toEqual(['教学', '培训', '工作坊']) |
| 13 | }) |
| 14 | |
| 15 | it('counts style case options and sorts popular cases first', () => { |
| 16 | expect( |
| 17 | buildStyleCaseOptions([ |
| 18 | { styleCase: '技术分享、产品发布' }, |
| 19 | { styleCase: '技术分享、年度总结' }, |
| 20 | { styleCase: '' } |
| 21 | ]) |
| 22 | ).toEqual([ |
| 23 | { label: '技术分享', count: 2 }, |
| 24 | { label: '产品发布', count: 1 }, |
| 25 | { label: '年度总结', count: 1 } |
| 26 | ]) |
| 27 | }) |
| 28 | |
| 29 | it('filters styles by an exact style case tag', () => { |
| 30 | const styles = [ |
| 31 | { id: 'one', styleCase: '技术分享、产品发布' }, |
| 32 | { id: 'two', styleCase: '产品发布会、年度总结' } |
| 33 | ] |
| 34 | |
| 35 | expect(filterByStyleCase(styles, '产品发布')).toEqual([styles[0]]) |
| 36 | expect(filterByStyleCase(styles, '')).toEqual(styles) |
| 37 | }) |
| 38 | |
| 39 | // 风格库页(styles.tsx)的筛选契约:tag 栏来自 buildStyleCaseOptions,列表来自 filterByStyleCase。 |
| 40 | // 风格库现在是「每个风格 3 个用途」格式,需保证一个风格能从它挂的任意一个 tag 筛到。 |
| 41 | it('powers the styles page filter: a style is reachable from each of its tags', () => { |
| 42 | const options = [ |
| 43 | { id: 'tokyo-night', label: '东京夜', styleCase: '技术分享、教学科普、产品发布' }, |
| 44 | { id: 'gold-ivory', label: '金象牙', styleCase: '餐饮美食、品牌营销、艺术视觉' }, |
| 45 | { id: 'minimal-white', label: '极简白', styleCase: '技术分享、商业汇报、教学科普' } |
| 46 | ] |
| 47 | |
| 48 | // tag 栏聚合了所有出现过的用途,按命中数排序 |
| 49 | const tags = buildStyleCaseOptions(options).map((tag) => tag.label) |
| 50 | expect(tags).toContain('技术分享') |
| 51 | expect(tags).toContain('餐饮美食') |
| 52 | expect(tags).toContain('商业汇报') |
| 53 | |
| 54 | // 技术分享 同时命中 tokyo-night 和 minimal-white |
| 55 | expect(filterByStyleCase(options, '技术分享').map((o) => o.id)).toEqual([ |
| 56 | 'tokyo-night', |
| 57 | 'minimal-white' |
| 58 | ]) |
| 59 | // 餐饮美食 只命中 gold-ivory |
| 60 | expect(filterByStyleCase(options, '餐饮美食').map((o) => o.id)).toEqual(['gold-ivory']) |
| 61 | // 空标签 = 全部 |
| 62 | expect(filterByStyleCase(options, '')).toEqual(options) |
| 63 | // 没有风格挂的用途 = 空列表(下拉显示「没有匹配的风格」) |
| 64 | expect(filterByStyleCase(options, '融资路演')).toEqual([]) |
| 65 | }) |
| 66 | |
| 67 | // StyleSelect 下拉的搜索框:按名称/描述/用途模糊匹配,与用途 tag 筛选叠加(AND)。 |
| 68 | it('filters styles by keyword over name, description and use cases', () => { |
| 69 | const options = [ |
| 70 | { id: 'tokyo-night', label: '东京夜', description: '深色技术风', styleCase: '技术分享、教学科普' }, |
| 71 | { id: 'gold-ivory', label: '金象牙', description: '奢华品牌', styleCase: '餐饮美食、品牌营销' }, |
| 72 | { id: 'arctic-cool', label: '北极冷', description: '商业数据汇报', styleCase: '商业汇报' } |
| 73 | ] |
| 74 | |
| 75 | // 命中名称 |
| 76 | expect(filterByStyleKeyword(options, '东京').map((o) => o.id)).toEqual(['tokyo-night']) |
| 77 | // 命中描述 |
| 78 | expect(filterByStyleKeyword(options, '奢华').map((o) => o.id)).toEqual(['gold-ivory']) |
| 79 | // 命中用途 |
| 80 | expect(filterByStyleKeyword(options, '商业').map((o) => o.id)).toEqual(['arctic-cool']) |
| 81 | // 大小写无关 + 首尾空格容错;未命中返回空 |
| 82 | expect(filterByStyleKeyword(options, ' 融资 ').map((o) => o.id)).toEqual([]) |
| 83 | // 空关键词 = 全部 |
| 84 | expect(filterByStyleKeyword(options, '')).toEqual(options) |
| 85 | // 与 tag 筛选叠加:先按用途「品牌营销」再按关键词「金」 |
| 86 | expect( |
| 87 | filterByStyleKeyword(filterByStyleCase(options, '品牌营销'), '金').map((o) => o.id) |
| 88 | ).toEqual(['gold-ivory']) |
| 89 | }) |
| 90 | }) |
| 91 |