|
@@ -0,0 +1,441 @@
|
|
|
|
|
+import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
|
|
|
|
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
|
|
|
+import { mockShowToast, mockNavigateTo, mockGetStorageSync, mockSetStorageSync } from '~/__mocks__/taroMock'
|
|
|
|
|
+import GoodsDetailPage from '@/pages/goods-detail/index'
|
|
|
|
|
+import { goodsClient } from '@/api'
|
|
|
|
|
+import { useCart } from '@/contexts/CartContext'
|
|
|
|
|
+
|
|
|
|
|
+// Mock API客户端
|
|
|
|
|
+jest.mock('@/api', () => ({
|
|
|
|
|
+ goodsClient: {
|
|
|
|
|
+ ':id': {
|
|
|
|
|
+ $get: jest.fn(),
|
|
|
|
|
+ children: {
|
|
|
|
|
+ $get: jest.fn()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}))
|
|
|
|
|
+
|
|
|
|
|
+// Mock Cart Context
|
|
|
|
|
+const mockAddToCart = jest.fn()
|
|
|
|
|
+jest.mock('@/contexts/CartContext', () => ({
|
|
|
|
|
+ useCart: () => ({
|
|
|
|
|
+ addToCart: mockAddToCart
|
|
|
|
|
+ })
|
|
|
|
|
+}))
|
|
|
|
|
+
|
|
|
|
|
+// Mock Taro useRouter
|
|
|
|
|
+const mockUseRouter = jest.fn()
|
|
|
|
|
+jest.mock('@tarojs/taro', () => ({
|
|
|
|
|
+ ...jest.requireActual('~/__mocks__/taroMock'),
|
|
|
|
|
+ useRouter: () => mockUseRouter(),
|
|
|
|
|
+ useShareAppMessage: () => {},
|
|
|
|
|
+ previewImage: jest.fn(),
|
|
|
|
|
+ navigateBack: jest.fn(),
|
|
|
|
|
+ switchTab: 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>
|
|
|
|
|
+ ),
|
|
|
|
|
+ Input: ({ value, className, onInput, onBlur, placeholder, maxlength, confirmType }: any) => (
|
|
|
|
|
+ <input
|
|
|
|
|
+ className={className}
|
|
|
|
|
+ value={value}
|
|
|
|
|
+ onChange={(e) => onInput && onInput({ detail: { value: e.target.value } })}
|
|
|
|
|
+ onBlur={onBlur}
|
|
|
|
|
+ placeholder={placeholder}
|
|
|
|
|
+ maxLength={maxlength}
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ />
|
|
|
|
|
+ ),
|
|
|
|
|
+ RichText: ({ nodes, className }: any) => (
|
|
|
|
|
+ <div className={className} dangerouslySetInnerHTML={{ __html: nodes }} />
|
|
|
|
|
+ )
|
|
|
|
|
+}))
|
|
|
|
|
+
|
|
|
|
|
+// Mock UI组件
|
|
|
|
|
+jest.mock('@/components/ui/navbar', () => ({
|
|
|
|
|
+ Navbar: ({ title, leftIcon, onClickLeft }: any) => (
|
|
|
|
|
+ <div className="navbar" onClick={onClickLeft}>
|
|
|
|
|
+ {title}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )
|
|
|
|
|
+}))
|
|
|
|
|
+
|
|
|
|
|
+jest.mock('@/components/ui/button', () => ({
|
|
|
|
|
+ Button: ({ children, onClick, className, disabled, size, variant }: any) => (
|
|
|
|
|
+ <button className={className} onClick={onClick} disabled={disabled}>
|
|
|
|
|
+ {children}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ )
|
|
|
|
|
+}))
|
|
|
|
|
+
|
|
|
|
|
+jest.mock('@/components/ui/carousel', () => ({
|
|
|
|
|
+ Carousel: ({ items, height, autoplay, interval, circular, onItemClick }: any) => (
|
|
|
|
|
+ <div className="carousel">
|
|
|
|
|
+ {items.map((item: any, index: number) => (
|
|
|
|
|
+ <div key={index} onClick={() => onItemClick && onItemClick(item, index)}>
|
|
|
|
|
+ {/* 图片轮播不显示文本内容 */}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )
|
|
|
|
|
+}))
|
|
|
|
|
+
|
|
|
|
|
+// Mock GoodsSpecSelector组件
|
|
|
|
|
+jest.mock('@/components/goods-spec-selector', () => ({
|
|
|
|
|
+ GoodsSpecSelector: ({ visible, onClose, onConfirm, parentGoodsId, currentSpec, currentQuantity }: any) => {
|
|
|
|
|
+ if (!visible) return null
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="spec-modal" data-testid="spec-modal">
|
|
|
|
|
+ <div className="spec-modal-content">
|
|
|
|
|
+ <div className="spec-modal-header">
|
|
|
|
|
+ <span>选择规格</span>
|
|
|
|
|
+ <div className="spec-modal-close" onClick={onClose}>×</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="spec-options">
|
|
|
|
|
+ <div className="spec-option" data-testid="spec-option-red" onClick={() => onConfirm({ id: 101, name: '红色款', price: 299, stock: 50 }, 1)}>
|
|
|
|
|
+ 红色款
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="spec-option" data-testid="spec-option-blue" onClick={() => onConfirm({ id: 102, name: '蓝色款', price: 319, stock: 30 }, 1)}>
|
|
|
|
|
+ 蓝色款
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+}))
|
|
|
|
|
+
|
|
|
|
|
+const createTestQueryClient = () => new QueryClient({
|
|
|
|
|
+ defaultOptions: {
|
|
|
|
|
+ queries: { retry: false },
|
|
|
|
|
+ mutations: { retry: false }
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
|
|
|
|
+ <QueryClientProvider client={createTestQueryClient()}>
|
|
|
|
|
+ {children}
|
|
|
|
|
+ </QueryClientProvider>
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+describe('GoodsDetailPage集成测试', () => {
|
|
|
|
|
+ beforeEach(() => {
|
|
|
|
|
+ jest.clearAllMocks()
|
|
|
|
|
+ mockUseRouter.mockReturnValue({ params: { id: '1' } })
|
|
|
|
|
+ mockGetStorageSync.mockReturnValue(null)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Mock商品数据
|
|
|
|
|
+ const mockGoods = {
|
|
|
|
|
+ id: 1,
|
|
|
|
|
+ name: '测试商品',
|
|
|
|
|
+ price: 299,
|
|
|
|
|
+ costPrice: 399,
|
|
|
|
|
+ stock: 100,
|
|
|
|
|
+ salesNum: 50,
|
|
|
|
|
+ instructions: '测试商品描述',
|
|
|
|
|
+ detail: '<p>商品详情</p>',
|
|
|
|
|
+ slideImages: [
|
|
|
|
|
+ { fullUrl: 'http://example.com/image1.jpg' },
|
|
|
|
|
+ { fullUrl: 'http://example.com/image2.jpg' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ imageFile: { fullUrl: 'http://example.com/main.jpg' }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Mock子商品数据
|
|
|
|
|
+ const mockChildren = {
|
|
|
|
|
+ data: [
|
|
|
|
|
+ { id: 101, name: '红色款', price: 299, stock: 50, imageFile: null },
|
|
|
|
|
+ { id: 102, name: '蓝色款', price: 319, stock: 30, imageFile: null }
|
|
|
|
|
+ ],
|
|
|
|
|
+ total: 2,
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ pageSize: 100,
|
|
|
|
|
+ totalPages: 1
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ it('渲染商品详情页面', async () => {
|
|
|
|
|
+ // Mock商品详情API响应
|
|
|
|
|
+ const mockGoodsResponse = {
|
|
|
|
|
+ status: 200,
|
|
|
|
|
+ json: async () => mockGoods
|
|
|
|
|
+ }
|
|
|
|
|
+ ;(goodsClient[':id'].$get as jest.Mock).mockResolvedValue(mockGoodsResponse)
|
|
|
|
|
+
|
|
|
|
|
+ // Mock子商品API响应
|
|
|
|
|
+ const mockChildrenResponse = {
|
|
|
|
|
+ status: 200,
|
|
|
|
|
+ json: async () => mockChildren
|
|
|
|
|
+ }
|
|
|
|
|
+ ;(goodsClient[':id'].children.$get as jest.Mock).mockResolvedValue(mockChildrenResponse)
|
|
|
|
|
+
|
|
|
|
|
+ render(
|
|
|
|
|
+ <TestWrapper>
|
|
|
|
|
+ <GoodsDetailPage />
|
|
|
|
|
+ </TestWrapper>
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // 等待商品加载 - 通过商品标题class来定位
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(screen.getByText('测试商品', { selector: '.goods-title' })).toBeInTheDocument()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 验证商品信息显示
|
|
|
|
|
+ expect(screen.getByText('¥299.00')).toBeInTheDocument()
|
|
|
|
|
+ expect(screen.getByText('¥399.00')).toBeInTheDocument()
|
|
|
|
|
+ expect(screen.getByText('已售50件')).toBeInTheDocument()
|
|
|
|
|
+ expect(screen.getByText('测试商品描述')).toBeInTheDocument()
|
|
|
|
|
+ expect(screen.getByText('加入购物车')).toBeInTheDocument()
|
|
|
|
|
+ expect(screen.getByText('立即购买')).toBeInTheDocument()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('打开规格选择弹窗', async () => {
|
|
|
|
|
+ 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('选择规格')
|
|
|
|
|
+ fireEvent.click(specButton)
|
|
|
|
|
+
|
|
|
|
|
+ // 验证规格选择弹窗显示
|
|
|
|
|
+ expect(screen.getByTestId('spec-modal')).toBeInTheDocument()
|
|
|
|
|
+ expect(screen.getByTestId('spec-option-red')).toBeInTheDocument()
|
|
|
|
|
+ expect(screen.getByTestId('spec-option-blue')).toBeInTheDocument()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('选择规格后更新显示', async () => {
|
|
|
|
|
+ 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('选择规格')
|
|
|
|
|
+ fireEvent.click(specButton)
|
|
|
|
|
+
|
|
|
|
|
+ // 选择规格
|
|
|
|
|
+ const redSpec = screen.getByTestId('spec-option-red')
|
|
|
|
|
+ fireEvent.click(redSpec)
|
|
|
|
|
+
|
|
|
|
|
+ // 验证规格信息显示
|
|
|
|
|
+ expect(screen.getByText('红色款')).toBeInTheDocument()
|
|
|
|
|
+ expect(screen.getByText('¥299.00', { selector: '.spec-price' })).toBeInTheDocument()
|
|
|
|
|
+ expect(screen.getByText('库存: 50', { selector: '.spec-stock' })).toBeInTheDocument()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('选择规格后加入购物车', async () => {
|
|
|
|
|
+ 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('选择规格')
|
|
|
|
|
+ fireEvent.click(specButton)
|
|
|
|
|
+ const redSpec = screen.getByTestId('spec-option-red')
|
|
|
|
|
+ fireEvent.click(redSpec)
|
|
|
|
|
+
|
|
|
|
|
+ // 点击加入购物车
|
|
|
|
|
+ const addToCartButton = screen.getByText('加入购物车')
|
|
|
|
|
+ fireEvent.click(addToCartButton)
|
|
|
|
|
+
|
|
|
|
|
+ // 验证addToCart被调用,使用规格信息
|
|
|
|
|
+ expect(mockAddToCart).toHaveBeenCalledWith({
|
|
|
|
|
+ id: 101, // 子商品ID
|
|
|
|
|
+ name: '红色款',
|
|
|
|
|
+ price: 299,
|
|
|
|
|
+ image: 'http://example.com/main.jpg',
|
|
|
|
|
+ stock: 50,
|
|
|
|
|
+ quantity: 1,
|
|
|
|
|
+ spec: '红色款'
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('选择规格后立即购买', async () => {
|
|
|
|
|
+ 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('选择规格')
|
|
|
|
|
+ fireEvent.click(specButton)
|
|
|
|
|
+ const redSpec = screen.getByTestId('spec-option-red')
|
|
|
|
|
+ fireEvent.click(redSpec)
|
|
|
|
|
+
|
|
|
|
|
+ // 点击立即购买
|
|
|
|
|
+ const buyNowButton = screen.getByText('立即购买')
|
|
|
|
|
+ fireEvent.click(buyNowButton)
|
|
|
|
|
+
|
|
|
|
|
+ // 验证setStorageSync被调用,存储购买信息
|
|
|
|
|
+ expect(mockSetStorageSync).toHaveBeenCalledWith('buyNow', {
|
|
|
|
|
+ goods: {
|
|
|
|
|
+ id: 101,
|
|
|
|
|
+ name: '红色款',
|
|
|
|
|
+ price: 299,
|
|
|
|
|
+ image: 'http://example.com/main.jpg',
|
|
|
|
|
+ quantity: 1,
|
|
|
|
|
+ spec: '红色款'
|
|
|
|
|
+ },
|
|
|
|
|
+ totalAmount: 299
|
|
|
|
|
+ })
|
|
|
|
|
+ expect(mockNavigateTo).toHaveBeenCalledWith({
|
|
|
|
|
+ url: '/pages/order-submit/index'
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('无规格商品时使用父商品信息', async () => {
|
|
|
|
|
+ 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 () => ({ data: [], total: 0, page: 1, pageSize: 100, totalPages: 0 })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ render(
|
|
|
|
|
+ <TestWrapper>
|
|
|
|
|
+ <GoodsDetailPage />
|
|
|
|
|
+ </TestWrapper>
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(screen.getByText('测试商品', { selector: '.goods-title' })).toBeInTheDocument()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 不选择规格,直接加入购物车
|
|
|
|
|
+ const addToCartButton = screen.getByText('加入购物车')
|
|
|
|
|
+ fireEvent.click(addToCartButton)
|
|
|
|
|
+
|
|
|
|
|
+ // 验证addToCart被调用,使用父商品信息
|
|
|
|
|
+ expect(mockAddToCart).toHaveBeenCalledWith({
|
|
|
|
|
+ id: 1, // 父商品ID
|
|
|
|
|
+ name: '测试商品',
|
|
|
|
|
+ price: 299,
|
|
|
|
|
+ image: 'http://example.com/main.jpg',
|
|
|
|
|
+ stock: 100,
|
|
|
|
|
+ quantity: 1,
|
|
|
|
|
+ spec: ''
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('规格库存限制数量选择', async () => {
|
|
|
|
|
+ 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('选择规格')
|
|
|
|
|
+ fireEvent.click(specButton)
|
|
|
|
|
+ const blueSpec = screen.getByTestId('spec-option-blue') // 库存30
|
|
|
|
|
+ fireEvent.click(blueSpec)
|
|
|
|
|
+
|
|
|
|
|
+ // 获取数量输入框
|
|
|
|
|
+ const quantityInput = screen.getByDisplayValue('1')
|
|
|
|
|
+
|
|
|
|
|
+ // 尝试输入超过库存的数量
|
|
|
|
|
+ fireEvent.change(quantityInput, { target: { value: '50' } })
|
|
|
|
|
+ fireEvent.blur(quantityInput)
|
|
|
|
|
+
|
|
|
|
|
+ // 验证toast显示库存限制
|
|
|
|
|
+ expect(mockShowToast).toHaveBeenCalledWith({
|
|
|
|
|
+ title: '库存只有30件',
|
|
|
|
|
+ icon: 'none',
|
|
|
|
|
+ duration: 1500
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+})
|