diff --git a/frontend/src/__tests__/newFeatures.test.js b/frontend/src/__tests__/newFeatures.test.js index 8b50b8c..804bd81 100644 --- a/frontend/src/__tests__/newFeatures.test.js +++ b/frontend/src/__tests__/newFeatures.test.js @@ -58,15 +58,15 @@ describe('getPinyinInitials', () => { }) describe('matchesPinyinInitials', () => { - it('matches prefix only', () => { + it('matches prefix', () => { expect(matchesPinyinInitials('生姜', 's')).toBe(true) expect(matchesPinyinInitials('生姜', 'sj')).toBe(true) - expect(matchesPinyinInitials('茶树', 's')).toBe(false) // cs doesn't start with s expect(matchesPinyinInitials('茶树', 'cs')).toBe(true) }) - it('does not match substring', () => { - expect(matchesPinyinInitials('茶树', 's')).toBe(false) + it('matches substring and subsequence', () => { + expect(matchesPinyinInitials('茶树', 's')).toBe(true) // substring + expect(matchesPinyinInitials('新瑞活力身体紧致霜', 'js')).toBe(true) // subsequence }) it('matches 忍冬花 with r', () => { diff --git a/frontend/src/__tests__/pr27Features.test.js b/frontend/src/__tests__/pr27Features.test.js index 4aa015e..f2007e3 100644 --- a/frontend/src/__tests__/pr27Features.test.js +++ b/frontend/src/__tests__/pr27Features.test.js @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest' import { recipeNameEn, oilEn } from '../composables/useOilTranslation' -import { matchesPinyinInitials, getPinyinInitials } from '../composables/usePinyinMatch' +import { matchesPinyinInitials, getPinyinInitials, pinyinMatchScore } from '../composables/usePinyinMatch' // --------------------------------------------------------------------------- // EDITOR_ONLY_TAGS includes '已下架' @@ -376,3 +376,71 @@ describe('viewer tag visibility logic', () => { expect([...myTags]).toHaveLength(0) }) }) + +// --------------------------------------------------------------------------- +// PR30: Pinyin subsequence matching + pinyinMatchScore +// --------------------------------------------------------------------------- +describe('pinyin subsequence matching — PR30', () => { + it('js matches 紧致霜 via subsequence', () => { + expect(matchesPinyinInitials('新瑞活力身体紧致霜', 'js')).toBe(true) + }) + + it('prefix match scores 0', () => { + expect(pinyinMatchScore('麦卢卡', 'mlk')).toBe(0) + }) + + it('substring match scores 1', () => { + expect(pinyinMatchScore('椒样薄荷', 'ybh')).toBe(1) + }) + + it('subsequence match scores 2', () => { + expect(pinyinMatchScore('新瑞活力身体紧致霜', 'js')).toBe(2) + }) + + it('no match scores -1', () => { + expect(pinyinMatchScore('薰衣草', 'zz')).toBe(-1) + }) + + it('product names have pinyin', () => { + expect(getPinyinInitials('身体紧致霜')).toBe('stjzs') + expect(getPinyinInitials('深层净肤面膜')).toBe('scjfmm') + expect(getPinyinInitials('青春无龄保湿霜')).toBe('qcwlbss') + }) +}) + +// --------------------------------------------------------------------------- +// PR30: Unit system (drop/ml/g/capsule) +// --------------------------------------------------------------------------- +describe('unit system — PR30', () => { + const UNIT_LABELS = { + drop: { zh: '滴' }, + ml: { zh: 'ml' }, + g: { zh: 'g' }, + capsule: { zh: '颗' }, + } + + it('maps unit to correct label', () => { + expect(UNIT_LABELS['drop'].zh).toBe('滴') + expect(UNIT_LABELS['ml'].zh).toBe('ml') + expect(UNIT_LABELS['g'].zh).toBe('g') + expect(UNIT_LABELS['capsule'].zh).toBe('颗') + }) + + it('volume display priority: stored > calculated > product sum', () => { + // Stored volume takes priority + const recipe1 = { volume: 'single', ingredients: [{ oil: '椰子油', drops: 96 }] } + const vol1 = recipe1.volume === 'single' ? '单次' : '' + expect(vol1).toBe('单次') + + // No stored volume, has coconut oil → calculate + const recipe2 = { volume: '', ingredients: [{ oil: '薰衣草', drops: 3 }, { oil: '椰子油', drops: 90 }] } + const total = recipe2.ingredients.reduce((s, i) => s + i.drops, 0) + const ml = Math.round(total / 18.6) + expect(ml).toBe(5) + + // No coconut oil, has product → show product volume + const recipe3 = { volume: '', ingredients: [{ oil: '薰衣草', drops: 3 }, { oil: '玫瑰护手霜', drops: 30 }] } + const hasProduct = recipe3.ingredients.some(i => i.oil === '玫瑰护手霜') + expect(hasProduct).toBe(true) + }) +}) diff --git a/frontend/src/composables/usePinyinMatch.js b/frontend/src/composables/usePinyinMatch.js index aaefbf9..74b7a5a 100644 --- a/frontend/src/composables/usePinyinMatch.js +++ b/frontend/src/composables/usePinyinMatch.js @@ -81,7 +81,7 @@ const PINYIN_MAP = { '膜': 'm', '乳': 'r', '液': 'y', '瓶': 'p', '盒': 'h', '深': 's', '层': 'c', '肤': 'f', '磨': 'm', '砂': 's', '龄': 'l', '无': 'w', '年': 'n', '华': 'h', '娇': 'j', - '颜': 'y', '喷': 'p', '雾': 'w', + '颜': 'y', '喷': 'p', '雾': 'w', '面': 'm', '湿': 's', } /**