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

202 lines
7.1 KiB
TypeScript

import { describe, it, expect, beforeEach } from 'vitest'
import * as fc from 'fast-check'
import { setActivePinia, createPinia } from 'pinia'
import { useCartStore } from './cart'
import type { CartItem } from '../types'
import type { Product, SpecData } from '../types/product'
// --- Arbitraries ---
const productArb: fc.Arbitrary<Product> = fc.record({
id: fc.integer({ min: 1, max: 100000 }),
name: fc.string({ minLength: 1, maxLength: 20 }),
basePrice: fc.float({ min: 0, max: 100000, noNaN: true }),
styleNo: fc.string({ minLength: 1, maxLength: 10 }),
stock: fc.integer({ min: 0, max: 10000 }),
totalStock: fc.integer({ min: 1, max: 10000 }),
loss: fc.float({ min: 0, max: 10, noNaN: true }),
laborCost: fc.float({ min: 0, max: 10000, noNaN: true }),
categoryId: fc.integer({ min: 1, max: 100 }),
bannerImages: fc.array(fc.string(), { minLength: 0, maxLength: 3 }),
bannerVideo: fc.option(fc.string(), { nil: undefined }),
status: fc.constantFrom('on' as const, 'off' as const),
createdAt: fc.constant('2025-01-01'),
updatedAt: fc.constant('2025-01-01'),
})
const specDataArb: fc.Arbitrary<SpecData> = fc.record({
id: fc.integer({ min: 1, max: 100000 }),
productId: fc.integer({ min: 1, max: 100000 }),
modelName: fc.string({ minLength: 1, maxLength: 20 }),
fineness: fc.string({ minLength: 1, maxLength: 10 }),
mainStone: fc.string({ minLength: 1, maxLength: 10 }),
ringSize: fc.string({ minLength: 1, maxLength: 5 }),
goldTotalWeight: fc.float({ min: 0, max: 1000, noNaN: true }),
goldNetWeight: fc.float({ min: 0, max: 1000, noNaN: true }),
loss: fc.float({ min: 0, max: 10, noNaN: true }),
goldLoss: fc.float({ min: 0, max: 100, noNaN: true }),
goldPrice: fc.float({ min: 0, max: 10000, noNaN: true }),
goldValue: fc.float({ min: 0, max: 100000, noNaN: true }),
mainStoneCount: fc.integer({ min: 0, max: 100 }),
mainStoneWeight: fc.float({ min: 0, max: 100, noNaN: true }),
mainStoneUnitPrice: fc.float({ min: 0, max: 100000, noNaN: true }),
mainStoneAmount: fc.float({ min: 0, max: 100000, noNaN: true }),
sideStoneCount: fc.integer({ min: 0, max: 100 }),
sideStoneWeight: fc.float({ min: 0, max: 100, noNaN: true }),
sideStoneUnitPrice: fc.float({ min: 0, max: 100000, noNaN: true }),
sideStoneAmount: fc.float({ min: 0, max: 100000, noNaN: true }),
accessoryAmount: fc.float({ min: 0, max: 10000, noNaN: true }),
processingFee: fc.float({ min: 0, max: 10000, noNaN: true }),
settingFee: fc.float({ min: 0, max: 10000, noNaN: true }),
totalLaborCost: fc.float({ min: 0, max: 10000, noNaN: true }),
totalPrice: fc.float({ min: 0, max: 1000000, noNaN: true }),
})
const cartItemArb: fc.Arbitrary<CartItem> = fc.record({
id: fc.integer({ min: 1, max: 100000 }),
userId: fc.integer({ min: 1, max: 10000 }),
productId: fc.integer({ min: 1, max: 100000 }),
specDataId: fc.integer({ min: 1, max: 100000 }),
quantity: fc.integer({ min: 1, max: 100 }),
product: productArb,
specData: specDataArb,
checked: fc.boolean(),
})
// Feature: jewelry-mall, Property 1: 购物车添加商品不变量
// **Validates: Requirements 2.1**
describe('Property 1: 购物车添加商品不变量', () => {
it('添加商品后购物车应包含该项且项数增加 1', () => {
fc.assert(
fc.property(cartItemArb, (newItem) => {
setActivePinia(createPinia())
const store = useCartStore()
const countBefore = store.items.length
store.addToCart(newItem)
expect(store.items.length).toBe(countBefore + 1)
expect(store.items).toContainEqual(newItem)
}),
{ numRuns: 100 },
)
})
})
// Feature: jewelry-mall, Property 2: 购物车总金额计算正确性
// **Validates: Requirements 2.3**
describe('Property 2: 购物车总金额计算正确性', () => {
it('总金额应等于所有已勾选商品的单价乘以数量之和', () => {
fc.assert(
fc.property(fc.array(cartItemArb, { minLength: 0, maxLength: 20 }), (items) => {
setActivePinia(createPinia())
const store = useCartStore()
for (const item of items) {
store.addToCart(item)
}
const expectedTotal = items
.filter((item) => item.checked)
.reduce((sum, item) => sum + item.specData.totalPrice * item.quantity, 0)
expect(store.totalAmount).toBeCloseTo(expectedTotal, 5)
}),
{ numRuns: 100 },
)
})
})
// 购物车单元测试
// Requirements: 2.1-2.4
describe('购物车单元测试', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
function makeCartItem(overrides: Partial<CartItem> = {}): CartItem {
return {
id: 1,
userId: 1,
productId: 1,
specDataId: 1,
quantity: 1,
product: {
id: 1, name: '测试商品', basePrice: 100, styleNo: 'S001',
stock: 10, totalStock: 100, loss: 0.05, laborCost: 50,
categoryId: 1, bannerImages: [], status: 'on',
createdAt: '2025-01-01', updatedAt: '2025-01-01',
},
specData: {
id: 1, productId: 1, modelName: 'M1', fineness: '18K',
mainStone: '钻石', ringSize: '12', goldTotalWeight: 5,
goldNetWeight: 4.5, loss: 0.05, goldLoss: 0.25,
goldPrice: 500, goldValue: 2250, mainStoneCount: 1,
mainStoneWeight: 0.5, mainStoneUnitPrice: 10000,
mainStoneAmount: 5000, sideStoneCount: 10,
sideStoneWeight: 0.3, sideStoneUnitPrice: 500,
sideStoneAmount: 150, accessoryAmount: 100,
processingFee: 200, settingFee: 300,
totalLaborCost: 500, totalPrice: 8000,
},
checked: false,
...overrides,
}
}
it('空购物车的总金额为 0', () => {
const store = useCartStore()
expect(store.totalAmount).toBe(0)
expect(store.checkedItems).toHaveLength(0)
})
it('空购物车移除商品不报错', () => {
const store = useCartStore()
expect(() => store.removeFromCart(999)).not.toThrow()
})
it('重复添加同一商品 ID 会产生多条记录', () => {
const store = useCartStore()
const item = makeCartItem()
store.addToCart(item)
store.addToCart({ ...item })
expect(store.items).toHaveLength(2)
})
it('全选后所有商品被勾选', () => {
const store = useCartStore()
store.addToCart(makeCartItem({ id: 1, checked: false }))
store.addToCart(makeCartItem({ id: 2, checked: false }))
store.toggleCheckAll()
expect(store.items.every((i) => i.checked)).toBe(true)
})
it('全选后再次全选取消所有勾选', () => {
const store = useCartStore()
store.addToCart(makeCartItem({ id: 1, checked: true }))
store.addToCart(makeCartItem({ id: 2, checked: true }))
store.toggleCheckAll()
expect(store.items.every((i) => !i.checked)).toBe(true)
})
it('更新数量后数量正确', () => {
const store = useCartStore()
store.addToCart(makeCartItem({ id: 1, quantity: 1 }))
store.updateQuantity(1, 5)
expect(store.items[0].quantity).toBe(5)
})
it('勾选商品后总金额正确计算', () => {
const store = useCartStore()
store.addToCart(makeCartItem({ id: 1, checked: true, quantity: 2 }))
// totalPrice = 8000, quantity = 2 => 16000
expect(store.totalAmount).toBe(16000)
})
})