Explorar o código

✅ test(goods-detail): 添加商品详情页按钮状态和边界条件测试

- 新增API加载失败场景测试【商品详情API失败时按钮应禁用】
- 新增子商品列表API失败场景测试【子商品列表API失败时按钮状态正确】
- 新增库存边界条件测试【有规格选项但库存为0时按钮应禁用】
- 新增无规格商品库存为0场景测试【无规格选项且商品库存为0时按钮应禁用】
- 新增数量输入最小值边界测试【数量输入最小值边界】
- 新增规格选择弹窗取消操作测试【规格选择弹窗取消操作】
yourname hai 1 mes
pai
achega
f6bf9d549f
Modificáronse 1 ficheiros con 261 adicións e 0 borrados
  1. 261 0
      mini/tests/unit/pages/goods-detail/goods-detail.test.tsx

+ 261 - 0
mini/tests/unit/pages/goods-detail/goods-detail.test.tsx

@@ -481,4 +481,265 @@ describe('GoodsDetailPage集成测试', () => {
       duration: 1500
     })
   })
+
+  it('商品详情API加载失败时按钮应禁用', async () => {
+    // Mock商品详情API失败
+    ;(goodsClient[':id'].$get as jest.Mock).mockRejectedValue(new Error('网络错误'))
+    // Mock子商品API正常返回空列表
+    ;(goodsClient[':id'].children.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: async () => ({ data: [], total: 0, page: 1, pageSize: 100, totalPages: 0 })
+    })
+
+    render(
+      <TestWrapper>
+        <GoodsDetailPage />
+      </TestWrapper>
+    )
+
+    // 等待组件渲染完成(可能显示加载或错误状态)
+    await waitFor(() => {
+      // 验证页面显示商品不存在提示
+      expect(screen.getByText('商品不存在')).toBeInTheDocument()
+    })
+
+    // 验证按钮不存在(因为goods为null,按钮不会渲染)
+    expect(screen.queryByText('加入购物车')).not.toBeInTheDocument()
+    expect(screen.queryByText('立即购买')).not.toBeInTheDocument()
+  })
+
+  it('子商品列表API加载失败时按钮状态正确', async () => {
+    // Mock商品详情API成功
+    const mockGoodsResponse = {
+      status: 200,
+      json: async () => mockGoods
+    }
+    ;(goodsClient[':id'].$get as jest.Mock).mockResolvedValue(mockGoodsResponse)
+    // Mock子商品API失败
+    ;(goodsClient[':id'].children.$get as jest.Mock).mockRejectedValue(new Error('获取子商品列表失败'))
+
+    render(
+      <TestWrapper>
+        <GoodsDetailPage />
+      </TestWrapper>
+    )
+
+    // 等待商品加载完成
+    await waitFor(() => {
+      expect(screen.getByText('测试商品', { selector: '.goods-title' })).toBeInTheDocument()
+    })
+
+    // 验证按钮存在且不禁用(因为商品库存>0,且hasSpecOptions为false)
+    const addToCartButton = screen.getByText('加入购物车')
+    const buyNowButton = screen.getByText('立即购买')
+    expect(addToCartButton).toBeInTheDocument()
+    expect(buyNowButton).toBeInTheDocument()
+    expect(addToCartButton).not.toBeDisabled()
+    expect(buyNowButton).not.toBeDisabled()
+  })
+
+  it('有规格选项但库存为0时按钮应禁用', async () => {
+    // 创建库存为0的子商品数据
+    const zeroStockChildren = {
+      data: [
+        { id: 103, name: '黑色款', price: 299, stock: 0, imageFile: null }
+      ],
+      total: 1,
+      page: 1,
+      pageSize: 100,
+      totalPages: 1
+    }
+
+    // Mock商品详情API成功
+    const mockGoodsResponse = {
+      status: 200,
+      json: async () => mockGoods
+    }
+    ;(goodsClient[':id'].$get as jest.Mock).mockResolvedValue(mockGoodsResponse)
+    // Mock子商品API返回库存为0的子商品
+    ;(goodsClient[':id'].children.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: async () => zeroStockChildren
+    })
+
+    render(
+      <TestWrapper>
+        <GoodsDetailPage />
+      </TestWrapper>
+    )
+
+    // 等待商品加载完成
+    await waitFor(() => {
+      expect(screen.getByText('测试商品', { selector: '.goods-title' })).toBeInTheDocument()
+    })
+
+    // 打开规格选择弹窗
+    const specButton = screen.getByText('选择规格', { selector: '.spec-select-btn' })
+    fireEvent.click(specButton)
+
+    // 等待规格弹窗加载
+    await waitFor(() => {
+      expect(screen.getByText('黑色款')).toBeInTheDocument()
+    })
+
+    // 选择库存为0的规格
+    const blackSpec = screen.getByText('黑色款')
+    fireEvent.click(blackSpec)
+
+    // 点击确认按钮
+    const confirmButton = screen.getByText(/确定/)
+    fireEvent.click(confirmButton)
+
+    // 等待规格弹窗关闭,页面更新
+    await waitFor(() => {
+      expect(screen.getByText('黑色款')).toBeInTheDocument()
+    })
+
+    // 验证按钮禁用(因为选择的规格库存为0)
+    const addToCartButton = screen.getByText('加入购物车')
+    const buyNowButton = screen.getByText('立即购买')
+    expect(addToCartButton).toBeDisabled()
+    expect(buyNowButton).toBeDisabled()
+  })
+
+  it('无规格选项且商品库存为0时按钮应禁用', async () => {
+    // 创建库存为0的商品
+    const zeroStockGoods = {
+      ...mockGoods,
+      stock: 0
+    }
+
+    // Mock商品详情API成功
+    const mockGoodsResponse = {
+      status: 200,
+      json: async () => zeroStockGoods
+    }
+    ;(goodsClient[':id'].$get as jest.Mock).mockResolvedValue(mockGoodsResponse)
+    // Mock子商品API返回空列表
+    ;(goodsClient[':id'].children.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: async () => ({ data: [], total: 0, page: 1, pageSize: 100, totalPages: 0 })
+    })
+
+    render(
+      <TestWrapper>
+        <GoodsDetailPage />
+      </TestWrapper>
+    )
+
+    // 等待商品加载完成
+    await waitFor(() => {
+      expect(screen.getByText('测试商品', { selector: '.goods-title' })).toBeInTheDocument()
+    })
+
+    // 验证按钮禁用(因为商品库存为0)
+    const addToCartButton = screen.getByText('加入购物车')
+    const buyNowButton = screen.getByText('立即购买')
+    expect(addToCartButton).toBeDisabled()
+    expect(buyNowButton).toBeDisabled()
+  })
+
+  it('数量输入最小值边界', async () => {
+    // Mock商品详情API成功
+    const mockGoodsResponse = {
+      status: 200,
+      json: async () => mockGoods
+    }
+    ;(goodsClient[':id'].$get as jest.Mock).mockResolvedValue(mockGoodsResponse)
+    // Mock子商品API返回空列表(无规格)
+    ;(goodsClient[':id'].children.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: async () => ({ data: [], total: 0, page: 1, pageSize: 100, totalPages: 0 })
+    })
+
+    render(
+      <TestWrapper>
+        <GoodsDetailPage />
+      </TestWrapper>
+    )
+
+    // 等待商品加载完成
+    await waitFor(() => {
+      expect(screen.getByText('测试商品', { selector: '.goods-title' })).toBeInTheDocument()
+    })
+
+    // 获取数量输入框
+    const quantityInput = screen.getByDisplayValue('1')
+
+    // 尝试输入0
+    fireEvent.change(quantityInput, { target: { value: '0' } })
+    fireEvent.blur(quantityInput)
+
+    // 验证数量自动纠正为1
+    await waitFor(() => {
+      expect(quantityInput).toHaveValue(1)
+    })
+
+    // 尝试输入负数(负号会被移除,变为5)
+    fireEvent.change(quantityInput, { target: { value: '-5' } })
+    fireEvent.blur(quantityInput)
+
+    // 验证负号被移除,值变为5
+    await waitFor(() => {
+      expect(quantityInput).toHaveValue(5)
+    })
+  })
+
+  it('规格选择弹窗取消操作', async () => {
+    // Mock商品详情API成功
+    const mockGoodsResponse = {
+      status: 200,
+      json: async () => mockGoods
+    }
+    ;(goodsClient[':id'].$get as jest.Mock).mockResolvedValue(mockGoodsResponse)
+    ;(goodsClient[':id'].children.$get as jest.Mock).mockResolvedValue({
+      status: 200,
+      json: async () => mockChildren
+    })
+
+    render(
+      <TestWrapper>
+        <GoodsDetailPage />
+      </TestWrapper>
+    )
+
+    // 等待商品加载完成
+    await waitFor(() => {
+      expect(screen.getByText('测试商品', { selector: '.goods-title' })).toBeInTheDocument()
+    })
+
+    // 打开规格选择弹窗
+    const specButton = screen.getByText('选择规格', { selector: '.spec-select-btn' })
+    fireEvent.click(specButton)
+
+    // 等待规格弹窗加载
+    await waitFor(() => {
+      expect(screen.getByText('红色款')).toBeInTheDocument()
+    })
+
+    // 不选择规格,直接关闭弹窗(点击弹窗外部或关闭按钮)
+    // 查找关闭按钮(通过class或文本)
+    const closeButton = screen.getByText('选择规格', { selector: '.spec-modal-title' }).closest('.spec-modal-content')?.querySelector('.spec-modal-close')
+    if (closeButton) {
+      fireEvent.click(closeButton)
+    } else {
+      // 如果没有关闭按钮,直接模拟关闭操作
+      // 点击弹窗外部(通过点击页面其他区域)
+      fireEvent.click(document.body)
+    }
+
+    // 等待弹窗关闭
+    await waitFor(() => {
+      expect(screen.queryByText('红色款')).not.toBeInTheDocument()
+    })
+
+    // 验证页面没有选择规格
+    expect(screen.queryByText('红色款', { selector: '.spec-price' })).not.toBeInTheDocument()
+
+    // 验证按钮禁用(因为未选择规格但有规格选项)
+    const addToCartButton = screen.getByText('加入购物车')
+    const buyNowButton = screen.getByText('立即购买')
+    expect(addToCartButton).toBeDisabled()
+    expect(buyNowButton).toBeDisabled()
+  })
 })