JewelryMall/miniprogram/utils/calculator.test.ts
2026-02-14 19:29:15 +08:00

140 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, it, expect } from 'vitest'
import * as fc from 'fast-check'
import { calculateRing, validateInput } from './calculator'
import type { RingCalculatorInput } from '../types/calculator'
// 生成有效的非负数输入
const validInputArb: fc.Arbitrary<RingCalculatorInput> = fc.record({
goldWeight: fc.float({ min: 0, max: 1000, noNaN: true }),
mainStoneWeight: fc.float({ min: 0, max: 100, noNaN: true }),
sideStoneWeight: fc.float({ min: 0, max: 100, noNaN: true }),
lossRate: fc.float({ min: 0, max: 10, noNaN: true }),
moldGoldPrice: fc.float({ min: 0, max: 10000, noNaN: true }),
mainStoneUnitPrice: fc.float({ min: 0, max: 100000, noNaN: true }),
sideStoneUnitPrice: fc.float({ min: 0, max: 100000, noNaN: true }),
sideStoneCount: fc.integer({ min: 0, max: 1000 }),
microSettingFee: fc.float({ min: 0, max: 1000, noNaN: true }),
mainStoneSettingFee: fc.float({ min: 0, max: 100000, noNaN: true }),
threeDFee: fc.float({ min: 0, max: 100000, noNaN: true }),
basicLaborCost: fc.float({ min: 0, max: 100000, noNaN: true }),
otherCost: fc.float({ min: 0, max: 100000, noNaN: true }),
})
// Feature: jewelry-mall, Property 4: 钻戒计算器公式正确性
// **Validates: Requirements 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8**
describe('Property 4: 钻戒计算器公式正确性', () => {
it('所有输出字段应符合设计文档公式', () => {
fc.assert(
fc.property(validInputArb, (input) => {
const result = calculateRing(input)
// 4.2 净金重 = 金重 - 主石重×0.2 - 副石重×0.2
const expectedNetGold = input.goldWeight - input.mainStoneWeight * 0.2 - input.sideStoneWeight * 0.2
expect(result.netGoldWeight).toBeCloseTo(expectedNetGold, 10)
// 4.3 含耗重 = 净金重 × 损耗
const expectedWeightWithLoss = expectedNetGold * input.lossRate
expect(result.weightWithLoss).toBeCloseTo(expectedWeightWithLoss, 10)
// 4.4 金值 = 含耗重 × 倒模金价
const expectedGoldValue = expectedWeightWithLoss * input.moldGoldPrice
expect(result.goldValue).toBeCloseTo(expectedGoldValue, 5)
// 4.5 主石总价 = 主石重 × 主石单价
const expectedMainStone = input.mainStoneWeight * input.mainStoneUnitPrice
expect(result.mainStoneTotal).toBeCloseTo(expectedMainStone, 10)
// 4.6 副石总价 = 副石重 × 副石单价
const expectedSideStone = input.sideStoneWeight * input.sideStoneUnitPrice
expect(result.sideStoneTotal).toBeCloseTo(expectedSideStone, 10)
// 4.7 微镶总价 = 副石粒数 × 微镶费
const expectedMicroSetting = input.sideStoneCount * input.microSettingFee
expect(result.microSettingTotal).toBeCloseTo(expectedMicroSetting, 10)
// 4.8 总价 = 金值 + 主石总价 + 副石总价 + 主石镶费 + 微镶总价 + 3D起板费 + 基本工费 + 其他费用
const expectedTotal = expectedGoldValue + expectedMainStone + expectedSideStone
+ input.mainStoneSettingFee + expectedMicroSetting
+ input.threeDFee + input.basicLaborCost + input.otherCost
expect(result.totalPrice).toBeCloseTo(expectedTotal, 5)
}),
{ numRuns: 100 },
)
})
})
// 单元测试:边界情况和非法输入
// Requirements: 4.2-4.8
describe('calculateRing 单元测试', () => {
const zeroInput: RingCalculatorInput = {
goldWeight: 0,
mainStoneWeight: 0,
sideStoneWeight: 0,
lossRate: 0,
moldGoldPrice: 0,
mainStoneUnitPrice: 0,
sideStoneUnitPrice: 0,
sideStoneCount: 0,
microSettingFee: 0,
mainStoneSettingFee: 0,
threeDFee: 0,
basicLaborCost: 0,
otherCost: 0,
}
it('所有输入为 0 时,所有输出应为 0', () => {
const result = calculateRing(zeroInput)
expect(result.netGoldWeight).toBe(0)
expect(result.weightWithLoss).toBe(0)
expect(result.goldValue).toBe(0)
expect(result.mainStoneTotal).toBe(0)
expect(result.sideStoneTotal).toBe(0)
expect(result.microSettingTotal).toBe(0)
expect(result.totalPrice).toBe(0)
})
it('仅金重有值时,净金重等于金重', () => {
const input = { ...zeroInput, goldWeight: 10, lossRate: 1.05, moldGoldPrice: 500 }
const result = calculateRing(input)
expect(result.netGoldWeight).toBe(10)
expect(result.weightWithLoss).toBeCloseTo(10.5, 10)
expect(result.goldValue).toBeCloseTo(5250, 5)
expect(result.totalPrice).toBeCloseTo(5250, 5)
})
it('极大值输入不应抛出异常', () => {
const input: RingCalculatorInput = {
goldWeight: 999999,
mainStoneWeight: 999999,
sideStoneWeight: 999999,
lossRate: 999999,
moldGoldPrice: 999999,
mainStoneUnitPrice: 999999,
sideStoneUnitPrice: 999999,
sideStoneCount: 999999,
microSettingFee: 999999,
mainStoneSettingFee: 999999,
threeDFee: 999999,
basicLaborCost: 999999,
otherCost: 999999,
}
expect(() => calculateRing(input)).not.toThrow()
})
it('负数金重应被拒绝', () => {
expect(() => calculateRing({ ...zeroInput, goldWeight: -1 })).toThrow('金重不能为负数')
})
it('负数主石重应被拒绝', () => {
expect(() => calculateRing({ ...zeroInput, mainStoneWeight: -0.5 })).toThrow('主石重不能为负数')
})
it('负数损耗应被拒绝', () => {
expect(() => calculateRing({ ...zeroInput, lossRate: -1 })).toThrow('损耗不能为负数')
})
it('负数其他费用应被拒绝', () => {
expect(() => calculateRing({ ...zeroInput, otherCost: -100 })).toThrow('其他费用不能为负数')
})
})