test: 更新单元测试覆盖智能识别新功能和share_recipe日志
All checks were successful
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 5s
Test / build-check (push) Successful in 4s
PR Preview / test (pull_request) Successful in 5s
PR Preview / deploy-preview (pull_request) Successful in 16s
Test / e2e-test (push) Successful in 52s

- parseOilChunk: 无数字默认1滴、混合格式、_ml标记
- findOil: 2字不模糊匹配、同音词和子串仍可用
- parseMultiRecipes: 多配方分割、空行/分号、名称识别、去重
- e2e: audit-log create_recipe → share_recipe

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-11 23:14:05 +00:00
parent 10b0478c0d
commit e9626a08b9
2 changed files with 164 additions and 10 deletions

View File

@@ -41,7 +41,7 @@ describe('Audit Log Advanced', () => {
const recipeId = createRes.body.id
// Check audit log
cy.request({ url: '/api/audit-log?limit=5&offset=0', headers: authHeaders }).then(res => {
const entry = res.body.find(e => e.action === 'create_recipe' && e.target_name === 'Cypress审计测试')
const entry = res.body.find(e => e.action === 'share_recipe' && e.target_name === 'Cypress审计测试')
expect(entry).to.exist
})
// Cleanup

View File

@@ -6,6 +6,7 @@ import {
parseOilChunk,
parseSingleBlock,
splitRawIntoBlocks,
parseMultiRecipes,
OIL_HOMOPHONES,
} from '../composables/useSmartPaste'
import prodData from './fixtures/production-data.json'
@@ -202,22 +203,22 @@ describe('parseOilChunk', () => {
expect(result[1]).toEqual({ oil: '永久花', drops: 10 })
})
it('parses "薰衣草3ml" → [{薰衣草, drops: 60}] (3ml * 20)', () => {
it('parses "薰衣草3ml" → [{薰衣草, drops: 60, _ml: 3}] (3ml * 20)', () => {
const result = parseOilChunk('薰衣草3ml', oilNames)
expect(result).toHaveLength(1)
expect(result[0]).toEqual({ oil: '薰衣草', drops: 60 })
expect(result[0]).toMatchObject({ oil: '薰衣草', drops: 60, _ml: 3 })
})
it('parses "薰衣草5毫升" → [{薰衣草, drops: 100}] (5 * 20)', () => {
it('parses "薰衣草5毫升" → [{薰衣草, drops: 100, _ml: 5}] (5 * 20)', () => {
const result = parseOilChunk('薰衣草5毫升', oilNames)
expect(result).toHaveLength(1)
expect(result[0]).toEqual({ oil: '薰衣草', drops: 100 })
expect(result[0]).toMatchObject({ oil: '薰衣草', drops: 100, _ml: 5 })
})
it('parses "薰衣草3ML" → case-insensitive ml', () => {
const result = parseOilChunk('薰衣草3ML', oilNames)
expect(result).toHaveLength(1)
expect(result[0]).toEqual({ oil: '薰衣草', drops: 60 })
expect(result[0]).toMatchObject({ oil: '薰衣草', drops: 60, _ml: 3 })
})
it('handles decimal drops "乳香1.5"', () => {
@@ -233,10 +234,52 @@ describe('parseOilChunk', () => {
expect(result[0]).toEqual({ oil: '薰衣草', drops: 5 })
})
it('returns empty array for text with no numbers', () => {
// The regex requires a number, so pure text yields nothing
it('parses oil name without number → default 1 drop', () => {
const result = parseOilChunk('薰衣草', oilNames)
expect(result).toHaveLength(0)
expect(result).toHaveLength(1)
expect(result[0]).toEqual({ oil: '薰衣草', drops: 1 })
})
it('parses multiple oil names without numbers', () => {
const result = parseOilChunk('薰衣草 茶树 乳香', oilNames)
expect(result).toHaveLength(3)
expect(result.map(r => r.oil)).toEqual(['薰衣草', '茶树', '乳香'])
expect(result.every(r => r.drops === 1)).toBe(true)
})
it('parses mixed: some with numbers, some without', () => {
const result = parseOilChunk('薰衣草3茶树乳香2', oilNames)
expect(result).toHaveLength(3)
expect(result[0]).toEqual({ oil: '薰衣草', drops: 3 })
expect(result[1]).toEqual({ oil: '茶树', drops: 1 })
expect(result[2]).toEqual({ oil: '乳香', drops: 2 })
})
it('parses trailing oil after last number', () => {
const result = parseOilChunk('薰衣草3茶树2乳香', oilNames)
expect(result).toHaveLength(3)
expect(result[2]).toEqual({ oil: '乳香', drops: 1 })
})
it('preserves _ml for ml unit (coconut oil)', () => {
const result = parseOilChunk('椰子油15ml', oilNames)
expect(result).toHaveLength(1)
expect(result[0]._ml).toBe(15)
expect(result[0].drops).toBe(300)
})
it('no _ml for drops unit', () => {
const result = parseOilChunk('椰子油15滴', oilNames)
expect(result).toHaveLength(1)
expect(result[0]._ml).toBeUndefined()
expect(result[0].drops).toBe(15)
})
it('no _ml for no unit', () => {
const result = parseOilChunk('椰子油15', oilNames)
expect(result).toHaveLength(1)
expect(result[0]._ml).toBeUndefined()
expect(result[0].drops).toBe(15)
})
})
@@ -263,7 +306,7 @@ describe('parseSingleBlock', () => {
it('handles recipe with no name (all parts have oils)', () => {
const result = parseSingleBlock('薰衣草10茶树5', oilNames)
expect(result.name).toBe('未命名配方')
expect(result.name).toBe('')
expect(result.ingredients).toHaveLength(2)
})
@@ -370,3 +413,114 @@ describe('OIL_HOMOPHONES', () => {
expect(OIL_HOMOPHONES['薰衣草油']).toBe('薰衣草')
})
})
// ---------------------------------------------------------------------------
// findOil — short string fuzzy match restriction
// ---------------------------------------------------------------------------
describe('findOil — short string protection', () => {
it('does not fuzzy-match 2-char non-oil text', () => {
// 美容 should NOT match any oil via edit distance
expect(findOil('美容', oilNames)).toBeNull()
})
it('still matches 2-char exact oil names', () => {
expect(findOil('乳香', oilNames)).toBe('乳香')
expect(findOil('茶树', oilNames)).toBe('茶树')
})
it('still matches 2-char homophones', () => {
expect(findOil('如香', oilNames)).toBe('乳香')
})
it('still matches 2-char substrings', () => {
// 薄荷 is a substring of 椒样薄荷 etc.
const result = findOil('薄荷', oilNames)
expect(result).not.toBeNull()
})
it('fuzzy matches 3+ char inputs via edit distance', () => {
// 永久化 → 永久花 (1 edit)
expect(findOil('永久化', oilNames)).toBe('永久花')
})
})
// ---------------------------------------------------------------------------
// parseMultiRecipes
// ---------------------------------------------------------------------------
describe('parseMultiRecipes', () => {
it('parses single recipe with name', () => {
const results = parseMultiRecipes('助眠薰衣草3茶树2', oilNames)
expect(results).toHaveLength(1)
expect(results[0].name).toBe('助眠')
expect(results[0].ingredients).toHaveLength(2)
})
it('splits two recipes by name detection', () => {
const results = parseMultiRecipes('助眠 薰衣草3 茶树2 头疗 柠檬5 椒样薄荷3', oilNames)
expect(results).toHaveLength(2)
expect(results[0].name).toBe('助眠')
expect(results[1].name).toBe('头疗')
})
it('splits by blank lines', () => {
const results = parseMultiRecipes('助眠\n薰衣草3\n\n头疗\n柠檬5', oilNames)
expect(results).toHaveLength(2)
expect(results[0].name).toBe('助眠')
expect(results[1].name).toBe('头疗')
})
it('splits by semicolons when both sides have oils', () => {
const results = parseMultiRecipes('助眠薰衣草3茶树2;头疗柠檬5', oilNames)
expect(results).toHaveLength(2)
})
it('does NOT split by semicolons when one side has no oil', () => {
const results = parseMultiRecipes('助眠;薰衣草3茶树2', oilNames)
expect(results).toHaveLength(1)
expect(results[0].ingredients.length).toBeGreaterThanOrEqual(2)
})
it('handles oils without numbers (default 1 drop)', () => {
const results = parseMultiRecipes('头疗,薰衣草,茶树,乳香', oilNames)
expect(results).toHaveLength(1)
expect(results[0].name).toBe('头疗')
expect(results[0].ingredients).toHaveLength(3)
expect(results[0].ingredients.every(i => i.drops === 1)).toBe(true)
})
it('recognizes non-oil text with number as recipe name (first part)', () => {
// 美容1 is not an oil, should be treated as name "美容"
const results = parseMultiRecipes('美容1 牛至2 乳香3', oilNames)
expect(results).toHaveLength(1)
expect(results[0].name).toBe('美容')
expect(results[0].ingredients).toHaveLength(2)
})
it('returns empty name when no name detected', () => {
const results = parseMultiRecipes('薰衣草3茶树2', oilNames)
expect(results).toHaveLength(1)
expect(results[0].name).toBe('')
})
it('deduplicates ingredients within a recipe', () => {
const results = parseMultiRecipes('测试 薰衣草3 薰衣草2', oilNames)
expect(results[0].ingredients).toHaveLength(1)
expect(results[0].ingredients[0].drops).toBe(5)
})
it('handles coconut oil with ml unit', () => {
const results = parseMultiRecipes('测试 薰衣草3 椰子油15ml', oilNames)
const coco = results[0].ingredients.find(i => i.oil === '椰子油')
expect(coco).toBeTruthy()
expect(coco._ml).toBe(15)
})
it('handles complex real-world multi-recipe input', () => {
const input = '美容 牛至2 迷迭香3 乳香4 椰子油15 头疗七八九 檀香3 乳香4 薰衣草3'
const results = parseMultiRecipes(input, oilNames)
expect(results).toHaveLength(2)
expect(results[0].name).toBe('美容')
expect(results[0].ingredients.find(i => i.oil === '椰子油')).toBeTruthy()
expect(results[1].name).toBe('头疗七八九')
})
})