test: PR#23新增单元测试(13个)
Some checks failed
Deploy Production / test (push) Successful in 7s
Deploy Production / deploy (push) Successful in 8s
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 5s
Test / build-check (push) Successful in 3s
PR Preview / test (pull_request) Successful in 5s
PR Preview / deploy-preview (pull_request) Successful in 11s
Test / e2e-test (push) Has been cancelled
Some checks failed
Deploy Production / test (push) Successful in 7s
Deploy Production / deploy (push) Successful in 8s
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 5s
Test / build-check (push) Successful in 3s
PR Preview / test (pull_request) Successful in 5s
PR Preview / deploy-preview (pull_request) Successful in 11s
Test / e2e-test (push) Has been cancelled
- 套装定义:家庭医生10种、居家呵护21种、芳香调理8种 - 配方匹配:排除椰子油、至少1种匹配、全匹配 - 消耗分析:计算次数、最先消耗完、全部相同 - 项目定价:JSON序列化和反序列化 全部通过: 204 unit + 36 e2e Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit was merged in pull request #23.
This commit is contained in:
146
frontend/src/__tests__/inventoryCommercial.test.js
Normal file
146
frontend/src/__tests__/inventoryCommercial.test.js
Normal file
@@ -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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user