Bläddra i källkod

feat(credit-payment): 完成故事004.003模块间集成验证

- 更新故事状态为Ready for Review
- 验证订单模块与额度模块集成:订单模块导入CreditBalanceService,取消订单时调用restoreBalanceForCancelOrder()
- 验证支付模块与额度模块集成:额度支付是独立支付方式,小程序直接调用额度API
- 验证小程序前端与后端API集成:API客户端正确定义creditBalanceClient,页面正确调用API
- 验证类型安全:server包导出CreditBalanceRoutes类型,确保类型安全
- 验证依赖关系:订单模块和server包已包含额度模块依赖
- 修复测试中的文本匹配问题,使用data-testid和正则表达式
- 修复测试中的CartContext mock问题

🤖 Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 6 dagar sedan
förälder
incheckning
c3ebd7369b

+ 12 - 7
docs/stories/004.003.integrate-credit-payment.story.md

@@ -1,7 +1,7 @@
 # Story 004.003: 集成额度支付到现有支付流程
 
 ## Status
-Draft
+Ready for Review
 
 ## Story
 **As a** 小程序用户,
@@ -66,11 +66,11 @@ Draft
   - [x] **额度恢复集成测试**:在 `mini/tests/integration/` 创建 `credit-balance-restore.test.tsx`,测试额度恢复逻辑
   - [x] **更新现有测试文件**:检查现有支付相关测试,确保与额度支付兼容
 
-- [ ] **验证模块间集成** (AC: 7)
-  - [ ] 验证订单模块与额度模块的集成
-  - [ ] 验证支付模块与额度模块的集成
-  - [ ] 验证小程序前端与后端API的集成
-  - [ ] 确保所有模块间调用类型安全
+- [x] **验证模块间集成** (AC: 7)
+  - [x] 验证订单模块与额度模块的集成
+  - [x] 验证支付模块与额度模块的集成
+  - [x] 验证小程序前端与后端API的集成
+  - [x] 确保所有模块间调用类型安全
 
 ## Dev Notes
 
@@ -474,8 +474,13 @@ Claude Code (d8d-model)
      - 修复集成测试中的文本匹配问题,避免多个相同文本元素
      - 所有额度支付相关测试(单元测试和集成测试)已通过
 
-6. **完成的工作**:
+6. **完成的工作**:
    - 验证模块间集成
+     - **订单模块与额度模块的集成**: 已验证订单模块导入了`CreditBalanceService`,在取消订单时调用`restoreBalanceForCancelOrder()`方法
+     - **支付模块与额度模块的集成**: 额度支付是独立支付方式,小程序支付页面直接调用额度模块的`/api/credit-balance/payment` API
+     - **小程序前端与后端API的集成**: 已验证小程序API客户端正确定义了`creditBalanceClient`,支付页面和个人中心页面正确调用额度API
+     - **类型安全**: 已验证server包导出`CreditBalanceRoutes`类型,小程序API客户端使用该类型确保类型安全
+     - **依赖关系**: 已验证订单模块和server包的package.json包含额度模块依赖
 
 ### File List
 **已修改的文件**:

+ 22 - 20
mini/tests/integration/credit-payment-flow.test.tsx

@@ -126,8 +126,8 @@ describe('额度支付流程集成测试', () => {
 
     // 1. 验证页面加载和额度显示
     await waitFor(() => {
-      expect(screen.getByText('支付订单')).toBeInTheDocument()
-      expect(screen.getByText('可用额度: ¥800.00')).toBeInTheDocument()
+      expect(screen.getByTestId('payment-page-title')).toBeInTheDocument()
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent(/可用额度: ¥800\.00/)
     })
 
     // 2. 选择额度支付方式
@@ -136,17 +136,18 @@ describe('额度支付流程集成测试', () => {
 
     await waitFor(() => {
       expect(creditOption).toHaveClass('border-blue-500')
-      expect(screen.getByText('额度支付 ¥100.00')).toBeInTheDocument()
+      expect(screen.getByTestId('pay-button')).toHaveTextContent('额度支付 ¥100.00')
     })
 
     // 3. 验证额度详情显示
-    expect(screen.getByText('• 使用信用额度支付,无需立即付款')).toBeInTheDocument()
-    expect(screen.getByText('• 可用额度: ¥800.00')).toBeInTheDocument()
-    expect(screen.getByText('• 总额度: ¥1000.00')).toBeInTheDocument()
-    expect(screen.getByText('• 已用额度: ¥200.00')).toBeInTheDocument()
+    const creditDetails = screen.getByTestId('credit-payment-details')
+    expect(creditDetails).toHaveTextContent(/使用信用额度支付,无需立即付款/)
+    expect(creditDetails).toHaveTextContent(/可用额度: ¥800\.00/)
+    expect(creditDetails).toHaveTextContent(/总额度: ¥1000\.00/)
+    expect(creditDetails).toHaveTextContent(/已用额度: ¥200\.00/)
 
     // 4. 点击支付按钮
-    const payButton = screen.getByText('额度支付 ¥100.00')
+    const payButton = screen.getByTestId('pay-button')
     fireEvent.click(payButton)
 
     // 5. 验证支付处理中状态
@@ -167,7 +168,8 @@ describe('额度支付流程集成测试', () => {
 
     // 7. 验证支付成功状态
     await waitFor(() => {
-      expect(screen.getByText('支付成功')).toBeInTheDocument()
+      // 使用类名选择器找到支付成功状态文本
+      expect(screen.getByText('支付成功', { selector: 'span.text-xl' })).toBeInTheDocument()
     })
 
     // 8. 验证跳转到成功页面
@@ -211,7 +213,7 @@ describe('额度支付流程集成测试', () => {
 
     // 等待页面加载
     await waitFor(() => {
-      expect(screen.getByText('可用额度: ¥800.00')).toBeInTheDocument()
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent(/可用额度: ¥800\.00/)
     })
 
     // 选择额度支付
@@ -219,7 +221,7 @@ describe('额度支付流程集成测试', () => {
     fireEvent.click(creditOption!)
 
     // 点击支付按钮(第一次失败)
-    const payButton = screen.getByText('额度支付 ¥100.00')
+    const payButton = screen.getByTestId('pay-button')
     fireEvent.click(payButton)
 
     // 验证显示错误信息
@@ -261,7 +263,7 @@ describe('额度支付流程集成测试', () => {
 
     // 等待页面加载
     await waitFor(() => {
-      expect(screen.getByText('可用额度: ¥5.00 (不足)')).toBeInTheDocument()
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent(/可用额度: ¥5\.00 \(不足\)/)
     })
 
     // 验证额度支付选项被禁用
@@ -273,7 +275,7 @@ describe('额度支付流程集成测试', () => {
     expect(creditOption).not.toHaveClass('border-blue-500')
 
     // 验证支付按钮被禁用
-    const payButton = screen.getByText('额度支付 ¥100.00')
+    const payButton = screen.getByTestId('pay-button')
     expect(payButton).toBeDisabled()
   })
 
@@ -306,7 +308,7 @@ describe('额度支付流程集成测试', () => {
     expect(creditOption).toHaveClass('opacity-50')
 
     // 验证支付按钮被禁用
-    const payButton = screen.getByText('额度支付 ¥100.00')
+    const payButton = screen.getByTestId('pay-button')
     expect(payButton).toBeDisabled()
   })
 
@@ -333,7 +335,7 @@ describe('额度支付流程集成测试', () => {
 
     // 等待页面加载
     await waitFor(() => {
-      expect(screen.getByText('可用额度: ¥800.00')).toBeInTheDocument()
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent(/可用额度: ¥800\.00/)
     })
 
     // 初始为微信支付选中
@@ -347,7 +349,7 @@ describe('额度支付流程集成测试', () => {
 
     await waitFor(() => {
       expect(creditOption).toHaveClass('border-blue-500')
-      expect(screen.getByText('额度支付 ¥100.00')).toBeInTheDocument()
+      expect(screen.getByTestId('pay-button')).toHaveTextContent('额度支付 ¥100.00')
     })
 
     // 验证额度详情显示
@@ -358,7 +360,7 @@ describe('额度支付流程集成测试', () => {
 
     await waitFor(() => {
       expect(wechatOption).toHaveClass('border-blue-500')
-      expect(screen.getByText('微信支付 ¥100.00')).toBeInTheDocument()
+      expect(screen.getByTestId('pay-button')).toHaveTextContent('微信支付 ¥100.00')
     })
 
     // 验证额度详情隐藏
@@ -386,7 +388,7 @@ describe('额度支付流程集成测试', () => {
     expect(creditOption).toHaveClass('opacity-50')
 
     // 验证支付按钮被禁用
-    const payButton = screen.getByText('额度支付 ¥100.00')
+    const payButton = screen.getByTestId('pay-button')
     expect(payButton).toBeDisabled()
   })
 
@@ -413,7 +415,7 @@ describe('额度支付流程集成测试', () => {
 
     // 等待页面加载
     await waitFor(() => {
-      expect(screen.getByText('可用额度: ¥800.00')).toBeInTheDocument()
+      expect(screen.getByTestId('available-amount-text')).toHaveTextContent(/可用额度: ¥800\.00/)
     })
 
     // 选择额度支付
@@ -421,7 +423,7 @@ describe('额度支付流程集成测试', () => {
     fireEvent.click(creditOption!)
 
     // 点击支付按钮
-    const payButton = screen.getByText('额度支付 ¥100.00')
+    const payButton = screen.getByTestId('pay-button')
     fireEvent.click(payButton)
 
     // 验证支付处理中状态

+ 27 - 5
mini/tests/unit/pages/profile/credit-balance-display.test.tsx

@@ -101,6 +101,25 @@ const createTestQueryClient = () => new QueryClient({
   },
 })
 
+// Mock CartContext
+jest.mock('@/contexts/CartContext', () => ({
+  CartProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
+  useCart: () => ({
+    cart: {
+      items: [],
+      totalAmount: 0,
+      totalCount: 0,
+    },
+    addToCart: jest.fn(),
+    removeFromCart: jest.fn(),
+    updateQuantity: jest.fn(),
+    clearCart: jest.fn(),
+    isInCart: jest.fn(),
+    getItemQuantity: jest.fn(),
+    isLoading: false,
+  }),
+}))
+
 // 测试包装器
 const TestWrapper = ({ children }: { children: React.ReactNode }) => (
   <QueryClientProvider client={createTestQueryClient()}>
@@ -227,8 +246,11 @@ describe('个人中心欠款显示功能测试', () => {
       </TestWrapper>
     )
 
-    // 验证加载状态显示
-    expect(screen.getByText('加载中...')).toBeInTheDocument()
+    // 验证加载状态显示 - 使用更灵活的选择器
+    await waitFor(() => {
+      const loadingElement = screen.getByText(/加载中/)
+      expect(loadingElement).toBeInTheDocument()
+    })
 
     // 解析查询
     const mockCreditBalance = createTestCreditBalance({ usedAmount: 200 })
@@ -239,7 +261,7 @@ describe('个人中心欠款显示功能测试', () => {
 
     // 等待加载完成
     await waitFor(() => {
-      expect(screen.queryByText('加载中...')).not.toBeInTheDocument()
+      expect(screen.queryByText(/加载中/)).not.toBeInTheDocument()
       expect(screen.getByText('欠款信息')).toBeInTheDocument()
     })
   })
@@ -360,8 +382,8 @@ describe('个人中心欠款显示功能测试', () => {
       expect(screen.getByText('欠款信息')).toBeInTheDocument()
     })
 
-    // 验证金额正确格式化
-    expect(screen.getByText('¥12,345.67')).toBeInTheDocument()
+    // 验证金额正确格式化 - 使用正则表达式匹配
+    expect(screen.getByText(/¥12,345\.67/)).toBeInTheDocument()
   })
 
   test('应该正确显示欠款卡片的样式', async () => {