|
|
@@ -0,0 +1,401 @@
|
|
|
+import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
|
|
+import GoodsCard, { GoodsData } from '@/components/goods-card'
|
|
|
+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, id }: any) => (
|
|
|
+ <div className={className} onClick={onClick} id={id}>
|
|
|
+ {children}
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ Image: ({ src, className, mode, lazyLoad }: any) => (
|
|
|
+ <img src={src} className={className} data-mode={mode} data-lazyload={lazyLoad} alt="" />
|
|
|
+ ),
|
|
|
+ Text: ({ children, className }: any) => (
|
|
|
+ <span className={className}>{children}</span>
|
|
|
+ )
|
|
|
+}))
|
|
|
+
|
|
|
+// Mock TDesignIcon组件
|
|
|
+jest.mock('@/components/tdesign/icon', () => ({
|
|
|
+ __esModule: true,
|
|
|
+ default: ({ name, size, color, onClick }: any) => (
|
|
|
+ <div
|
|
|
+ data-testid="tdesign-icon"
|
|
|
+ data-name={name}
|
|
|
+ data-size={size}
|
|
|
+ data-color={color}
|
|
|
+ onClick={onClick}
|
|
|
+ >
|
|
|
+ {name}图标
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}))
|
|
|
+
|
|
|
+// Mock UI Button组件
|
|
|
+jest.mock('@/components/ui/button', () => ({
|
|
|
+ Button: ({ children, onClick, className, disabled, size, variant }: any) => (
|
|
|
+ <button
|
|
|
+ className={className}
|
|
|
+ onClick={onClick}
|
|
|
+ disabled={disabled}
|
|
|
+ data-size={size}
|
|
|
+ data-variant={variant}
|
|
|
+ >
|
|
|
+ {children}
|
|
|
+ </button>
|
|
|
+ )
|
|
|
+}))
|
|
|
+
|
|
|
+describe('GoodsCard组件', () => {
|
|
|
+ const mockOnClick = jest.fn()
|
|
|
+ const mockOnAddCart = jest.fn()
|
|
|
+
|
|
|
+ beforeEach(() => {
|
|
|
+ jest.clearAllMocks()
|
|
|
+
|
|
|
+ // 设置默认的API模拟响应
|
|
|
+ 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)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 测试单规格商品直接添加到购物车场景
|
|
|
+ it('单规格商品直接添加到购物车', () => {
|
|
|
+ const goodsData: GoodsData = {
|
|
|
+ id: '1',
|
|
|
+ name: '测试商品',
|
|
|
+ cover_image: 'http://example.com/image.jpg',
|
|
|
+ price: 299,
|
|
|
+ originPrice: 399,
|
|
|
+ tags: ['热销'],
|
|
|
+ hasSpecOptions: false,
|
|
|
+ parentGoodsId: undefined,
|
|
|
+ quantity: 1
|
|
|
+ }
|
|
|
+
|
|
|
+ render(
|
|
|
+ <GoodsCard
|
|
|
+ data={goodsData}
|
|
|
+ onClick={mockOnClick}
|
|
|
+ onAddCart={mockOnAddCart}
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ // 验证商品信息显示
|
|
|
+ expect(screen.getByText('测试商品')).toBeInTheDocument()
|
|
|
+ expect(screen.getByAltText('')).toHaveAttribute('src', 'http://example.com/image.jpg')
|
|
|
+ expect(screen.getByText('299.00')).toBeInTheDocument()
|
|
|
+
|
|
|
+ // 点击购物车按钮
|
|
|
+ const cartButton = screen.getByTestId('tdesign-icon')
|
|
|
+ fireEvent.click(cartButton)
|
|
|
+
|
|
|
+ // 验证直接调用onAddCart,不显示规格选择器
|
|
|
+ expect(mockOnAddCart).toHaveBeenCalledWith(goodsData)
|
|
|
+ expect(screen.queryByTestId('goods-spec-selector')).not.toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ // 测试多规格商品弹出规格选择器场景
|
|
|
+ it('多规格商品弹出规格选择器', async () => {
|
|
|
+ const goodsData: GoodsData = {
|
|
|
+ id: '1',
|
|
|
+ name: '多规格商品',
|
|
|
+ cover_image: 'http://example.com/image.jpg',
|
|
|
+ price: 299,
|
|
|
+ originPrice: 399,
|
|
|
+ hasSpecOptions: true,
|
|
|
+ parentGoodsId: 1, // 父商品ID
|
|
|
+ quantity: 1
|
|
|
+ }
|
|
|
+
|
|
|
+ render(
|
|
|
+ <GoodsCard
|
|
|
+ data={goodsData}
|
|
|
+ onClick={mockOnClick}
|
|
|
+ onAddCart={mockOnAddCart}
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ // 初始不显示规格选择器
|
|
|
+ expect(screen.queryByText('选择规格')).not.toBeInTheDocument()
|
|
|
+
|
|
|
+ // 点击购物车按钮
|
|
|
+ const cartButton = screen.getByTestId('tdesign-icon')
|
|
|
+ fireEvent.click(cartButton)
|
|
|
+
|
|
|
+ // 验证显示规格选择器(等待加载完成)
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('选择规格')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ // 验证规格选项加载
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('红色款')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ // 验证未调用onAddCart
|
|
|
+ expect(mockOnAddCart).not.toHaveBeenCalled()
|
|
|
+ })
|
|
|
+
|
|
|
+ // 测试规格选择后成功添加到购物车场景
|
|
|
+ it('规格选择后成功添加到购物车', async () => {
|
|
|
+ const goodsData: GoodsData = {
|
|
|
+ id: '1',
|
|
|
+ name: '多规格商品',
|
|
|
+ cover_image: 'http://example.com/image.jpg',
|
|
|
+ price: 299,
|
|
|
+ hasSpecOptions: true,
|
|
|
+ parentGoodsId: 1,
|
|
|
+ quantity: 1
|
|
|
+ }
|
|
|
+
|
|
|
+ render(
|
|
|
+ <GoodsCard
|
|
|
+ data={goodsData}
|
|
|
+ onClick={mockOnClick}
|
|
|
+ onAddCart={mockOnAddCart}
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ // 点击购物车按钮弹出规格选择器
|
|
|
+ const cartButton = screen.getByTestId('tdesign-icon')
|
|
|
+ fireEvent.click(cartButton)
|
|
|
+
|
|
|
+ // 等待规格选择器加载
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('选择规格')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ // 等待规格选项加载
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('红色款')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ // 点击规格选项
|
|
|
+ fireEvent.click(screen.getByText('红色款'))
|
|
|
+
|
|
|
+ // 等待确认按钮出现(规格选中后应该出现)
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText(/加入购物车/)).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ // 点击确认按钮
|
|
|
+ const confirmButton = screen.getByText(/加入购物车/)
|
|
|
+ fireEvent.click(confirmButton)
|
|
|
+
|
|
|
+ // 验证onAddCart被调用,且包含规格信息
|
|
|
+ expect(mockOnAddCart).toHaveBeenCalledWith({
|
|
|
+ id: '101', // 规格ID转换为字符串
|
|
|
+ name: '红色款', // 规格名称
|
|
|
+ cover_image: 'http://example.com/image.jpg',
|
|
|
+ price: 299, // 规格价格
|
|
|
+ hasSpecOptions: true,
|
|
|
+ parentGoodsId: 1, // 父商品ID保持不变
|
|
|
+ quantity: 1 // 数量
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ // 测试父子商品关系正确记录场景
|
|
|
+ it('父子商品关系正确记录', () => {
|
|
|
+ // 测试父商品
|
|
|
+ const parentGoodsData: GoodsData = {
|
|
|
+ id: '1',
|
|
|
+ name: '父商品',
|
|
|
+ cover_image: 'http://example.com/image.jpg',
|
|
|
+ price: 299,
|
|
|
+ hasSpecOptions: true,
|
|
|
+ parentGoodsId: 1, // 父商品的parentGoodsId是自己的ID
|
|
|
+ quantity: 1
|
|
|
+ }
|
|
|
+
|
|
|
+ const { rerender } = render(
|
|
|
+ <GoodsCard
|
|
|
+ data={parentGoodsData}
|
|
|
+ onAddCart={mockOnAddCart}
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ // 点击购物车按钮
|
|
|
+ const cartButton = screen.getByTestId('tdesign-icon')
|
|
|
+ fireEvent.click(cartButton)
|
|
|
+
|
|
|
+ // 验证规格选择器中的parentGoodsId正确
|
|
|
+ expect(screen.getByTestId('spec-selector-parent-id')).toHaveTextContent('1')
|
|
|
+
|
|
|
+ // 测试子商品
|
|
|
+ const childGoodsData: GoodsData = {
|
|
|
+ id: '101',
|
|
|
+ name: '子商品规格',
|
|
|
+ cover_image: 'http://example.com/image.jpg',
|
|
|
+ price: 299,
|
|
|
+ hasSpecOptions: false, // 子商品没有规格选项
|
|
|
+ parentGoodsId: 1, // 指向父商品
|
|
|
+ quantity: 1
|
|
|
+ }
|
|
|
+
|
|
|
+ rerender(
|
|
|
+ <GoodsCard
|
|
|
+ data={childGoodsData}
|
|
|
+ onAddCart={mockOnAddCart}
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ // 子商品直接添加到购物车
|
|
|
+ fireEvent.click(screen.getByTestId('tdesign-icon'))
|
|
|
+ expect(mockOnAddCart).toHaveBeenCalledWith(childGoodsData)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 测试商品卡片在不同页面的数据传递正确性
|
|
|
+ it('处理无规格选项但有parentGoodsId的情况', () => {
|
|
|
+ const goodsData: GoodsData = {
|
|
|
+ id: '101',
|
|
|
+ name: '子商品',
|
|
|
+ cover_image: 'http://example.com/image.jpg',
|
|
|
+ price: 299,
|
|
|
+ hasSpecOptions: false, // 明确无规格选项
|
|
|
+ parentGoodsId: 1, // 但有父商品ID
|
|
|
+ quantity: 1
|
|
|
+ }
|
|
|
+
|
|
|
+ render(
|
|
|
+ <GoodsCard
|
|
|
+ data={goodsData}
|
|
|
+ onAddCart={mockOnAddCart}
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ // 点击购物车按钮
|
|
|
+ const cartButton = screen.getByTestId('tdesign-icon')
|
|
|
+ fireEvent.click(cartButton)
|
|
|
+
|
|
|
+ // 应该直接添加到购物车,不显示规格选择器
|
|
|
+ expect(mockOnAddCart).toHaveBeenCalledWith(goodsData)
|
|
|
+ expect(screen.queryByTestId('goods-spec-selector')).not.toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ // 测试商品卡片点击事件
|
|
|
+ it('点击商品卡片触发onClick回调', () => {
|
|
|
+ const goodsData: GoodsData = {
|
|
|
+ id: '1',
|
|
|
+ name: '测试商品',
|
|
|
+ cover_image: 'http://example.com/image.jpg',
|
|
|
+ price: 299
|
|
|
+ }
|
|
|
+
|
|
|
+ render(
|
|
|
+ <GoodsCard
|
|
|
+ data={goodsData}
|
|
|
+ onClick={mockOnClick}
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ // 点击商品卡片
|
|
|
+ const goodsCard = screen.getByText('测试商品').closest('div')
|
|
|
+ fireEvent.click(goodsCard!)
|
|
|
+
|
|
|
+ expect(mockOnClick).toHaveBeenCalledWith(goodsData)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 测试规格选择器关闭功能
|
|
|
+ it('关闭规格选择器', () => {
|
|
|
+ const goodsData: GoodsData = {
|
|
|
+ id: '1',
|
|
|
+ name: '多规格商品',
|
|
|
+ cover_image: 'http://example.com/image.jpg',
|
|
|
+ price: 299,
|
|
|
+ hasSpecOptions: true,
|
|
|
+ parentGoodsId: 1,
|
|
|
+ quantity: 1
|
|
|
+ }
|
|
|
+
|
|
|
+ render(
|
|
|
+ <GoodsCard
|
|
|
+ data={goodsData}
|
|
|
+ onAddCart={mockOnAddCart}
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ // 点击购物车按钮弹出规格选择器
|
|
|
+ const cartButton = screen.getByTestId('tdesign-icon')
|
|
|
+ fireEvent.click(cartButton)
|
|
|
+
|
|
|
+ // 确认规格选择器显示
|
|
|
+ expect(screen.getByTestId('goods-spec-selector')).toBeInTheDocument()
|
|
|
+
|
|
|
+ // 点击关闭按钮
|
|
|
+ const closeButton = screen.getByTestId('spec-selector-close')
|
|
|
+ fireEvent.click(closeButton)
|
|
|
+
|
|
|
+ // 验证规格选择器消失
|
|
|
+ expect(screen.queryByTestId('goods-spec-selector')).not.toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ // 测试货币符号显示
|
|
|
+ it('显示自定义货币符号', () => {
|
|
|
+ const goodsData: GoodsData = {
|
|
|
+ id: '1',
|
|
|
+ name: '测试商品',
|
|
|
+ price: 299
|
|
|
+ }
|
|
|
+
|
|
|
+ render(
|
|
|
+ <GoodsCard
|
|
|
+ data={goodsData}
|
|
|
+ currency="$"
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ // 验证货币符号显示
|
|
|
+ expect(screen.getByText('$')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('299.00')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ // 测试无价格商品
|
|
|
+ it('处理无价格商品', () => {
|
|
|
+ const goodsData: GoodsData = {
|
|
|
+ id: '1',
|
|
|
+ name: '无价格商品'
|
|
|
+ }
|
|
|
+
|
|
|
+ render(
|
|
|
+ <GoodsCard
|
|
|
+ data={goodsData}
|
|
|
+ />
|
|
|
+ )
|
|
|
+
|
|
|
+ // 验证商品名称显示
|
|
|
+ expect(screen.getByText('无价格商品')).toBeInTheDocument()
|
|
|
+ // 价格区域不应该显示
|
|
|
+ expect(screen.queryByText('¥')).not.toBeInTheDocument()
|
|
|
+ })
|
|
|
+})
|