From 83078f8f8b300cf058e0811832b590e14711b702 Mon Sep 17 00:00:00 2001 From: Hera Zhao Date: Sat, 11 Apr 2026 10:44:54 +0000 Subject: [PATCH] =?UTF-8?q?test:=20PR#23=E6=96=B0=E5=A2=9E=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95=EF=BC=8813=E4=B8=AA=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 套装定义:家庭医生10种、居家呵护21种、芳香调理8种 - 配方匹配:排除椰子油、至少1种匹配、全匹配 - 消耗分析:计算次数、最先消耗完、全部相同 - 项目定价:JSON序列化和反序列化 全部通过: 204 unit + 36 e2e Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/__tests__/inventoryCommercial.test.js | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 frontend/src/__tests__/inventoryCommercial.test.js diff --git a/frontend/src/__tests__/inventoryCommercial.test.js b/frontend/src/__tests__/inventoryCommercial.test.js new file mode 100644 index 0000000..7688cde --- /dev/null +++ b/frontend/src/__tests__/inventoryCommercial.test.js @@ -0,0 +1,146 @@ +import { describe, it, expect } from 'vitest' + +// --------------------------------------------------------------------------- +// Kit definitions +// --------------------------------------------------------------------------- +describe('Kit definitions', () => { + const KITS = { + family: ['乳香', '茶树', '薰衣草', '柠檬', '椒样薄荷', '保卫', '牛至', '乐活', '顺畅呼吸', '舒缓'], + home3988: ['乳香', '野橘', '柠檬', '薰衣草', '椒样薄荷', '冬青', '茶树', '生姜', '柠檬草', '西班牙牛至', + '西洋蓍草石榴籽', '乐活', '保卫', '新瑞活力', '舒缓', '安定情绪', '安宁神气', '顺畅呼吸', '柑橘清新', '芳香调理', '元气焕能'], + aroma: ['薰衣草', '舒缓', '安定情绪', '芳香调理', '野橘', '椒样薄荷', '保卫', '茶树'], + } + + it('family kit has 10 oils', () => { + expect(KITS.family).toHaveLength(10) + }) + + it('home3988 kit has 21 oils', () => { + expect(KITS.home3988).toHaveLength(21) + }) + + it('aroma kit has 8 oils', () => { + expect(KITS.aroma).toHaveLength(8) + expect(KITS.aroma).toContain('薰衣草') + expect(KITS.aroma).toContain('芳香调理') + expect(KITS.aroma).toContain('茶树') + }) +}) + +// --------------------------------------------------------------------------- +// Recipe matching (exclude coconut oil) +// --------------------------------------------------------------------------- +describe('Recipe matching', () => { + function matchRecipe(recipe, ownedSet) { + const needed = recipe.ingredients.filter(i => i.oil !== '椰子油').map(i => i.oil) + if (needed.length === 0) return false + const coverage = needed.filter(o => ownedSet.has(o)).length + return coverage >= 1 + } + + it('matches recipe when user has at least one oil', () => { + const recipe = { ingredients: [{ oil: '乳香', drops: 3 }, { oil: '茶树', drops: 2 }, { oil: '椰子油', drops: 100 }] } + expect(matchRecipe(recipe, new Set(['乳香']))).toBe(true) + }) + + it('does not match when user has none of the oils', () => { + const recipe = { ingredients: [{ oil: '乳香', drops: 3 }, { oil: '茶树', drops: 2 }] } + expect(matchRecipe(recipe, new Set(['薰衣草']))).toBe(false) + }) + + it('excludes coconut oil from matching', () => { + const recipe = { ingredients: [{ oil: '椰子油', drops: 100 }] } + expect(matchRecipe(recipe, new Set(['椰子油']))).toBe(false) + }) + + it('matches when user has all oils', () => { + const recipe = { ingredients: [{ oil: '乳香', drops: 3 }, { oil: '茶树', drops: 2 }] } + expect(matchRecipe(recipe, new Set(['乳香', '茶树']))).toBe(true) + }) +}) + +// --------------------------------------------------------------------------- +// Consumption analysis +// --------------------------------------------------------------------------- +describe('Consumption analysis', () => { + function calcConsumption(ingredients, oilsMeta) { + return ingredients + .filter(i => i.oil && i.oil !== '椰子油' && i.drops > 0) + .map(i => { + const bottleDrops = oilsMeta[i.oil]?.dropCount || 0 + const sessions = bottleDrops > 0 ? Math.floor(bottleDrops / i.drops) : 0 + return { oil: i.oil, drops: i.drops, bottleDrops, sessions } + }) + } + + function findLimit(data) { + const valid = data.filter(c => c.sessions > 0) + if (!valid.length) return { oil: '', sessions: 0, allSame: true } + const min = Math.min(...valid.map(c => c.sessions)) + const allSame = valid.every(c => c.sessions === valid[0].sessions) + const limiting = valid.find(c => c.sessions === min) + return { oil: limiting.oil, sessions: min, allSame } + } + + const meta = { + '芳香调理': { dropCount: 280 }, + '薰衣草': { dropCount: 280 }, + '茶树': { dropCount: 280 }, + } + + it('calculates sessions correctly', () => { + const data = calcConsumption([{ oil: '芳香调理', drops: 10 }], meta) + expect(data[0].sessions).toBe(28) + }) + + it('finds limiting oil', () => { + const data = calcConsumption([ + { oil: '芳香调理', drops: 10 }, + { oil: '薰衣草', drops: 20 }, + ], meta) + const limit = findLimit(data) + expect(limit.oil).toBe('薰衣草') + expect(limit.sessions).toBe(14) + expect(limit.allSame).toBe(false) + }) + + it('detects all same sessions', () => { + const data = calcConsumption([ + { oil: '芳香调理', drops: 10 }, + { oil: '茶树', drops: 10 }, + ], meta) + const limit = findLimit(data) + expect(limit.allSame).toBe(true) + expect(limit.sessions).toBe(28) + }) + + it('excludes coconut oil', () => { + const data = calcConsumption([ + { oil: '芳香调理', drops: 10 }, + { oil: '椰子油', drops: 200 }, + ], meta) + expect(data).toHaveLength(1) + expect(data[0].oil).toBe('芳香调理') + }) +}) + +// --------------------------------------------------------------------------- +// Project pricing persistence +// --------------------------------------------------------------------------- +describe('Project pricing JSON', () => { + it('serializes extra fields to JSON', () => { + const extra = { selling_price: 299, packaging_cost: 5, labor_cost: 30, other_cost: 10, quantity: 1 } + const json = JSON.stringify(extra) + const parsed = JSON.parse(json) + expect(parsed.selling_price).toBe(299) + expect(parsed.quantity).toBe(1) + }) + + it('merges extra fields back into project', () => { + const project = { id: 1, name: 'test', ingredients: [], pricing: '{"selling_price":299,"quantity":1}' } + const extra = JSON.parse(project.pricing) + const merged = { ...project, ...extra } + expect(merged.selling_price).toBe(299) + expect(merged.quantity).toBe(1) + }) +})