|
|
@@ -0,0 +1,287 @@
|
|
|
+import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
|
|
+import { GoodsSpecSelector } from '@/components/goods-spec-selector'
|
|
|
+import { goodsClient } from '@/api'
|
|
|
+
|
|
|
+// Mock API客户端
|
|
|
+jest.mock('@/api', () => ({
|
|
|
+ goodsClient: {
|
|
|
+ ':id': {
|
|
|
+ children: {
|
|
|
+ $get: jest.fn()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}))
|
|
|
+
|
|
|
+// Mock Taro组件
|
|
|
+jest.mock('@tarojs/components', () => ({
|
|
|
+ View: ({ children, className, onClick }: any) => (
|
|
|
+ <div className={className} onClick={onClick}>
|
|
|
+ {children}
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ Text: ({ children, className }: any) => (
|
|
|
+ <span className={className}>{children}</span>
|
|
|
+ ),
|
|
|
+ ScrollView: ({ children, className }: any) => (
|
|
|
+ <div className={className}>{children}</div>
|
|
|
+ )
|
|
|
+}))
|
|
|
+
|
|
|
+// Mock UI组件
|
|
|
+jest.mock('@/components/ui/button', () => ({
|
|
|
+ Button: ({ children, onClick, className, disabled }: any) => (
|
|
|
+ <button className={className} onClick={onClick} disabled={disabled}>
|
|
|
+ {children}
|
|
|
+ </button>
|
|
|
+ )
|
|
|
+}))
|
|
|
+
|
|
|
+describe('GoodsSpecSelector组件', () => {
|
|
|
+ const mockOnClose = jest.fn()
|
|
|
+ const mockOnConfirm = jest.fn()
|
|
|
+
|
|
|
+ beforeEach(() => {
|
|
|
+ jest.clearAllMocks()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('当visible为false时不渲染', () => {
|
|
|
+ const { container } = render(
|
|
|
+ <GoodsSpecSelector
|
|
|
+ visible={false}
|
|
|
+ onClose={mockOnClose}
|
|
|
+ onConfirm={mockOnConfirm}
|
|
|
+ parentGoodsId={1}
|
|
|
+ />
|
|
|
+ )
|
|
|
+ expect(container.firstChild).toBeNull()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('当visible为true时渲染组件', () => {
|
|
|
+ render(
|
|
|
+ <GoodsSpecSelector
|
|
|
+ visible={true}
|
|
|
+ onClose={mockOnClose}
|
|
|
+ onConfirm={mockOnConfirm}
|
|
|
+ parentGoodsId={1}
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(screen.getByText('选择规格')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('加载规格选项...')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('成功获取子商品列表后显示规格选项', async () => {
|
|
|
+ // Mock成功的API响应
|
|
|
+ const mockResponse = {
|
|
|
+ status: 200,
|
|
|
+ json: async () => ({
|
|
|
+ data: [
|
|
|
+ {
|
|
|
+ id: 101,
|
|
|
+ name: '红色款',
|
|
|
+ price: 299,
|
|
|
+ stock: 50,
|
|
|
+ imageFile: { fullUrl: 'http://example.com/image.jpg' }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 102,
|
|
|
+ name: '蓝色款',
|
|
|
+ price: 319,
|
|
|
+ stock: 30,
|
|
|
+ imageFile: null
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ total: 2,
|
|
|
+ page: 1,
|
|
|
+ pageSize: 100,
|
|
|
+ totalPages: 1
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ ;(goodsClient[':id'].children.$get as jest.Mock).mockResolvedValue(mockResponse)
|
|
|
+
|
|
|
+ render(
|
|
|
+ <GoodsSpecSelector
|
|
|
+ visible={true}
|
|
|
+ onClose={mockOnClose}
|
|
|
+ onConfirm={mockOnConfirm}
|
|
|
+ parentGoodsId={1}
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ // 等待加载完成
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('红色款')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ expect(screen.getByText('蓝色款')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('¥299.00')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('库存: 50')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('API调用失败时显示错误信息', async () => {
|
|
|
+ // Mock失败的API响应
|
|
|
+ ;(goodsClient[':id'].children.$get as jest.Mock).mockRejectedValue(
|
|
|
+ new Error('网络错误')
|
|
|
+ )
|
|
|
+
|
|
|
+ render(
|
|
|
+ <GoodsSpecSelector
|
|
|
+ visible={true}
|
|
|
+ onClose={mockOnClose}
|
|
|
+ onConfirm={mockOnConfirm}
|
|
|
+ parentGoodsId={1}
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ // 等待错误显示
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('获取子商品列表异常')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ expect(screen.getByText('重试')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('没有规格选项时显示空状态', async () => {
|
|
|
+ // Mock空响应
|
|
|
+ const mockResponse = {
|
|
|
+ status: 200,
|
|
|
+ json: async () => ({
|
|
|
+ data: [],
|
|
|
+ total: 0,
|
|
|
+ page: 1,
|
|
|
+ pageSize: 100,
|
|
|
+ totalPages: 0
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ ;(goodsClient[':id'].children.$get as jest.Mock).mockResolvedValue(mockResponse)
|
|
|
+
|
|
|
+ render(
|
|
|
+ <GoodsSpecSelector
|
|
|
+ visible={true}
|
|
|
+ onClose={mockOnClose}
|
|
|
+ onConfirm={mockOnConfirm}
|
|
|
+ parentGoodsId={1}
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('暂无规格选项')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ it('点击规格选项时选中该规格', async () => {
|
|
|
+ const mockResponse = {
|
|
|
+ status: 200,
|
|
|
+ json: async () => ({
|
|
|
+ data: [
|
|
|
+ {
|
|
|
+ id: 101,
|
|
|
+ name: '红色款',
|
|
|
+ price: 299,
|
|
|
+ stock: 50,
|
|
|
+ imageFile: null
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ total: 1,
|
|
|
+ page: 1,
|
|
|
+ pageSize: 100,
|
|
|
+ totalPages: 1
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ ;(goodsClient[':id'].children.$get as jest.Mock).mockResolvedValue(mockResponse)
|
|
|
+
|
|
|
+ render(
|
|
|
+ <GoodsSpecSelector
|
|
|
+ visible={true}
|
|
|
+ onClose={mockOnClose}
|
|
|
+ onConfirm={mockOnConfirm}
|
|
|
+ parentGoodsId={1}
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('红色款')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ // 点击规格选项
|
|
|
+ fireEvent.click(screen.getByText('红色款'))
|
|
|
+
|
|
|
+ // 应该显示数量选择器
|
|
|
+ expect(screen.getByText('数量')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('点击确认按钮时调用onConfirm回调', async () => {
|
|
|
+ const mockResponse = {
|
|
|
+ status: 200,
|
|
|
+ json: async () => ({
|
|
|
+ data: [
|
|
|
+ {
|
|
|
+ id: 101,
|
|
|
+ name: '红色款',
|
|
|
+ price: 299,
|
|
|
+ stock: 50,
|
|
|
+ imageFile: null
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ total: 1,
|
|
|
+ page: 1,
|
|
|
+ pageSize: 100,
|
|
|
+ totalPages: 1
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ ;(goodsClient[':id'].children.$get as jest.Mock).mockResolvedValue(mockResponse)
|
|
|
+
|
|
|
+ render(
|
|
|
+ <GoodsSpecSelector
|
|
|
+ visible={true}
|
|
|
+ onClose={mockOnClose}
|
|
|
+ onConfirm={mockOnConfirm}
|
|
|
+ parentGoodsId={1}
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('红色款')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ // 选择规格
|
|
|
+ fireEvent.click(screen.getByText('红色款'))
|
|
|
+
|
|
|
+ // 点击确认按钮
|
|
|
+ const confirmButton = screen.getByRole('button', { name: /确定/ })
|
|
|
+ fireEvent.click(confirmButton)
|
|
|
+
|
|
|
+ expect(mockOnConfirm).toHaveBeenCalledWith(
|
|
|
+ expect.objectContaining({
|
|
|
+ id: 101,
|
|
|
+ name: '红色款',
|
|
|
+ price: 299,
|
|
|
+ stock: 50
|
|
|
+ }),
|
|
|
+ 1
|
|
|
+ )
|
|
|
+ expect(mockOnClose).toHaveBeenCalled()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('点击关闭按钮时调用onClose回调', () => {
|
|
|
+ render(
|
|
|
+ <GoodsSpecSelector
|
|
|
+ visible={true}
|
|
|
+ onClose={mockOnClose}
|
|
|
+ onConfirm={mockOnConfirm}
|
|
|
+ parentGoodsId={1}
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ // 点击关闭按钮(使用图标元素)
|
|
|
+ const closeButton = screen.getByRole('button', { name: '' })
|
|
|
+ fireEvent.click(closeButton)
|
|
|
+
|
|
|
+ expect(mockOnClose).toHaveBeenCalled()
|
|
|
+ })
|
|
|
+})
|