Pārlūkot izejas kodu

✨ feat(cart): 增强购物车规格支持并添加单元测试

- 为CartItem接口字段添加中文注释,明确字段用途
- 在addToCart方法中添加注释说明父子商品支持及租户验证机制
- 【测试】新增完整的购物车上下文单元测试文件
- 【测试】验证父商品和带规格子商品的添加功能
- 【测试】验证同一子商品数量累加逻辑
- 【测试】验证库存限制机制
- 【测试】验证同时添加父商品和多个不同子商品的功能
yourname 1 mēnesi atpakaļ
vecāks
revīzija
b6b734e9f7

+ 9 - 8
mini/src/contexts/CartContext.tsx

@@ -2,13 +2,13 @@ import React, { createContext, useContext, useState, useEffect, ReactNode } from
 import Taro from '@tarojs/taro'
 
 export interface CartItem {
-  id: number
-  name: string
-  price: number
-  image: string
-  stock: number
-  quantity: number
-  spec?: string
+  id: number        // 商品ID(父商品ID或子商品ID)
+  name: string      // 商品名称(包含规格信息的完整名称)
+  price: number     // 商品价格
+  image: string     // 商品图片
+  stock: number     // 商品库存
+  quantity: number  // 购买数量
+  spec?: string     // 规格信息(可选,用于显示)
 }
 
 export interface CartState {
@@ -87,7 +87,8 @@ export const CartProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
     }
   }
 
-  // 添加商品到购物车
+  // 添加商品到购物车,支持父商品和子商品
+  // 注意:父子商品的租户一致性验证在API层面进行
   const addToCart = (item: CartItem) => {
     const existingItem = cart.items.find(cartItem => cartItem.id === item.id)
 

+ 213 - 0
mini/tests/unit/contexts/CartContext.test.tsx

@@ -0,0 +1,213 @@
+import React from 'react'
+import { render } from '@testing-library/react'
+import { CartProvider, useCart, CartItem } from '@/contexts/CartContext'
+import { mockShowToast, mockGetStorageSync, mockSetStorageSync } from '~/__mocks__/taroMock'
+
+// Mock Taro API
+jest.mock('@tarojs/taro', () => jest.requireActual('~/__mocks__/taroMock'))
+
+// 测试组件用于访问购物车hook
+const TestComponent = ({ action, item }: { action: string; item?: CartItem }) => {
+  const cart = useCart()
+
+  React.useEffect(() => {
+    if (action === 'add' && item) {
+      cart.addToCart(item)
+    }
+  }, [action, item, cart])
+
+  return (
+    <div>
+      <div data-testid="items-count">{cart.cart.items.length}</div>
+      <div data-testid="total-count">{cart.cart.totalCount}</div>
+      <div data-testid="total-amount">{cart.cart.totalAmount}</div>
+      {cart.cart.items.map((item, index) => (
+        <div key={index} data-testid={`item-${index}`}>
+          <span data-testid={`item-${index}-id`}>{item.id}</span>
+          <span data-testid={`item-${index}-name`}>{item.name}</span>
+          <span data-testid={`item-${index}-spec`}>{item.spec || ''}</span>
+          <span data-testid={`item-${index}-quantity`}>{item.quantity}</span>
+        </div>
+      ))}
+    </div>
+  )
+}
+
+describe('CartContext - 规格支持', () => {
+  beforeEach(() => {
+    mockGetStorageSync.mockReturnValue(null)
+    mockSetStorageSync.mockClear()
+    mockShowToast.mockClear()
+  })
+
+  it('应该支持添加父商品到购物车', () => {
+    const parentGoods: CartItem = {
+      id: 1001,
+      name: '测试父商品',
+      price: 99.9,
+      image: 'parent.jpg',
+      stock: 10,
+      quantity: 2,
+    }
+
+    const { getByTestId } = render(
+      <CartProvider>
+        <TestComponent action="add" item={parentGoods} />
+      </CartProvider>
+    )
+
+    expect(getByTestId('items-count').textContent).toBe('1')
+    expect(getByTestId('item-0-id').textContent).toBe('1001')
+    expect(getByTestId('item-0-name').textContent).toBe('测试父商品')
+    expect(mockSetStorageSync).toHaveBeenCalled()
+  })
+
+  it('应该支持添加子商品(带规格)到购物车', () => {
+    const childGoods: CartItem = {
+      id: 2001, // 子商品ID
+      name: '测试父商品 - 红色/M', // 包含规格信息的完整名称
+      price: 109.9,
+      image: 'child.jpg',
+      stock: 5,
+      quantity: 1,
+      spec: '红色/M', // 规格信息
+    }
+
+    const { getByTestId } = render(
+      <CartProvider>
+        <TestComponent action="add" item={childGoods} />
+      </CartProvider>
+    )
+
+    expect(getByTestId('items-count').textContent).toBe('1')
+    expect(getByTestId('item-0-id').textContent).toBe('2001')
+    expect(getByTestId('item-0-name').textContent).toBe('测试父商品 - 红色/M')
+    expect(getByTestId('item-0-spec').textContent).toBe('红色/M')
+    expect(mockSetStorageSync).toHaveBeenCalled()
+  })
+
+  it('应该支持添加同一子商品多次(数量累加)', () => {
+    const childGoods1: CartItem = {
+      id: 3001,
+      name: '测试商品 - 蓝色/L',
+      price: 89.9,
+      image: 'goods.jpg',
+      stock: 10,
+      quantity: 1,
+      spec: '蓝色/L',
+    }
+
+    const childGoods2: CartItem = {
+      id: 3001, // 同一子商品ID
+      name: '测试商品 - 蓝色/L',
+      price: 89.9,
+      image: 'goods.jpg',
+      stock: 10,
+      quantity: 3,
+      spec: '蓝色/L',
+    }
+
+    const { getByTestId, rerender } = render(
+      <CartProvider>
+        <TestComponent action="add" item={childGoods1} />
+      </CartProvider>
+    )
+
+    expect(getByTestId('items-count').textContent).toBe('1')
+    expect(getByTestId('item-0-quantity').textContent).toBe('1')
+
+    // 重新渲染添加更多数量
+    rerender(
+      <CartProvider>
+        <TestComponent action="add" item={childGoods2} />
+      </CartProvider>
+    )
+
+    expect(getByTestId('item-0-quantity').textContent).toBe('4') // 1 + 3
+  })
+
+  it('应该限制数量不超过库存', () => {
+    const childGoods: CartItem = {
+      id: 4001,
+      name: '测试商品 - 黑色/XL',
+      price: 129.9,
+      image: 'goods.jpg',
+      stock: 2, // 库存只有2
+      quantity: 3, // 尝试购买3个
+      spec: '黑色/XL',
+    }
+
+    const { getByTestId } = render(
+      <CartProvider>
+        <TestComponent action="add" item={childGoods} />
+      </CartProvider>
+    )
+
+    // 应该显示库存不足提示
+    expect(mockShowToast).toHaveBeenCalledWith(
+      expect.objectContaining({ title: '库存不足' })
+    )
+    // 商品不应被添加
+    expect(getByTestId('items-count').textContent).toBe('0')
+  })
+
+  it('应该支持同时添加父商品和不同子商品', () => {
+    const parentGoods: CartItem = {
+      id: 5001,
+      name: '测试父商品',
+      price: 199.9,
+      image: 'parent.jpg',
+      stock: 20,
+      quantity: 1,
+    }
+
+    const childGoods1: CartItem = {
+      id: 5002, // 子商品ID1
+      name: '测试父商品 - 规格A',
+      price: 219.9,
+      image: 'child1.jpg',
+      stock: 5,
+      quantity: 2,
+      spec: '规格A',
+    }
+
+    const childGoods2: CartItem = {
+      id: 5003, // 子商品ID2
+      name: '测试父商品 - 规格B',
+      price: 229.9,
+      image: 'child2.jpg',
+      stock: 3,
+      quantity: 1,
+      spec: '规格B',
+    }
+
+    const { getByTestId, rerender } = render(
+      <CartProvider>
+        <TestComponent action="add" item={parentGoods} />
+      </CartProvider>
+    )
+
+    expect(getByTestId('items-count').textContent).toBe('1')
+
+    // 添加第一个子商品
+    rerender(
+      <CartProvider>
+        <TestComponent action="add" item={childGoods1} />
+      </CartProvider>
+    )
+
+    expect(getByTestId('items-count').textContent).toBe('2')
+
+    // 添加第二个子商品
+    rerender(
+      <CartProvider>
+        <TestComponent action="add" item={childGoods2} />
+      </CartProvider>
+    )
+
+    expect(getByTestId('items-count').textContent).toBe('3')
+    expect(getByTestId('item-0-id').textContent).toBe('5001')
+    expect(getByTestId('item-1-id').textContent).toBe('5002')
+    expect(getByTestId('item-2-id').textContent).toBe('5003')
+  })
+})