|
|
@@ -0,0 +1,339 @@
|
|
|
+import React from 'react'
|
|
|
+import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
|
|
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
|
+import SearchResultPage from '@/pages/search-result/index'
|
|
|
+
|
|
|
+// 导入Taro mock函数
|
|
|
+import {
|
|
|
+ mockNavigateTo,
|
|
|
+ mockGetCurrentInstance,
|
|
|
+ mockStopPullDownRefresh
|
|
|
+} from '~/__mocks__/taroMock'
|
|
|
+
|
|
|
+// Mock components
|
|
|
+jest.mock('@/components/ui/navbar', () => ({
|
|
|
+ Navbar: ({ title, onClickLeft }: { title: string; onClickLeft: () => void }) => (
|
|
|
+ <div data-testid="navbar">
|
|
|
+ <span>{title}</span>
|
|
|
+ <button onClick={onClickLeft}>返回</button>
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+}))
|
|
|
+
|
|
|
+jest.mock('@/components/goods-list', () => ({
|
|
|
+ __esModule: true,
|
|
|
+ default: ({
|
|
|
+ goodsList,
|
|
|
+ onClick,
|
|
|
+ onAddCart
|
|
|
+ }: {
|
|
|
+ goodsList: any[],
|
|
|
+ onClick: (goods: any) => void,
|
|
|
+ onAddCart: (goods: any) => void
|
|
|
+ }) => (
|
|
|
+ <div data-testid="goods-list">
|
|
|
+ {goodsList.map((goods, index) => (
|
|
|
+ <div
|
|
|
+ key={goods.id}
|
|
|
+ data-testid={`goods-item-${index}`}
|
|
|
+ onClick={() => onClick(goods)}
|
|
|
+ >
|
|
|
+ <span data-testid="goods-name">{goods.name}</span>
|
|
|
+ <span data-testid="goods-price">{goods.price}</span>
|
|
|
+ <button
|
|
|
+ data-testid="add-cart-btn"
|
|
|
+ onClick={() => onAddCart(goods)}
|
|
|
+ >
|
|
|
+ 加入购物车
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+}))
|
|
|
+
|
|
|
+// Mock API client
|
|
|
+jest.mock('@/api', () => ({
|
|
|
+ goodsClient: {
|
|
|
+ $get: jest.fn()
|
|
|
+ }
|
|
|
+}))
|
|
|
+
|
|
|
+// Mock cart hook
|
|
|
+jest.mock('@/utils/cart', () => ({
|
|
|
+ useCart: () => ({
|
|
|
+ addToCart: jest.fn()
|
|
|
+ })
|
|
|
+}))
|
|
|
+
|
|
|
+describe('SearchResultPage', () => {
|
|
|
+ let queryClient: QueryClient
|
|
|
+
|
|
|
+ beforeEach(() => {
|
|
|
+ queryClient = new QueryClient({
|
|
|
+ defaultOptions: {
|
|
|
+ queries: { retry: false },
|
|
|
+ mutations: { retry: false },
|
|
|
+ },
|
|
|
+ })
|
|
|
+
|
|
|
+ // Reset all mocks
|
|
|
+ jest.clearAllMocks()
|
|
|
+
|
|
|
+ // Mock Taro.getCurrentInstance
|
|
|
+ mockGetCurrentInstance.mockReturnValue({
|
|
|
+ router: {
|
|
|
+ params: {
|
|
|
+ keyword: '手机'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // Mock API response
|
|
|
+ const { goodsClient } = require('@/api')
|
|
|
+ goodsClient.$get.mockResolvedValue({
|
|
|
+ status: 200,
|
|
|
+ json: () => Promise.resolve({
|
|
|
+ data: [
|
|
|
+ {
|
|
|
+ id: 1,
|
|
|
+ name: 'iPhone 15',
|
|
|
+ price: 599900,
|
|
|
+ originPrice: 699900,
|
|
|
+ stock: 10,
|
|
|
+ salesNum: 100,
|
|
|
+ imageFile: {
|
|
|
+ fullUrl: 'https://example.com/iphone15.jpg'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 2,
|
|
|
+ name: 'MacBook Pro',
|
|
|
+ price: 1299900,
|
|
|
+ originPrice: 1499900,
|
|
|
+ stock: 5,
|
|
|
+ salesNum: 50,
|
|
|
+ imageFile: {
|
|
|
+ fullUrl: 'https://example.com/macbook.jpg'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ pagination: {
|
|
|
+ current: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ total: 2,
|
|
|
+ totalPages: 1
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ const renderWithProviders = (component: React.ReactElement) => {
|
|
|
+ return render(
|
|
|
+ <QueryClientProvider client={queryClient}>
|
|
|
+ {component}
|
|
|
+ </QueryClientProvider>
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ it('渲染页面标题和布局', async () => {
|
|
|
+ renderWithProviders(<SearchResultPage />)
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByTestId('navbar')).toBeInTheDocument()
|
|
|
+ expect(screen.getByTestId('navbar')).toHaveTextContent('搜索结果')
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ it('显示搜索栏和关键词', async () => {
|
|
|
+ renderWithProviders(<SearchResultPage />)
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ // 验证搜索栏存在
|
|
|
+ const searchInput = document.querySelector('.search-input') as HTMLInputElement
|
|
|
+ expect(searchInput).toBeInTheDocument()
|
|
|
+ expect(searchInput.placeholder).toBe('搜索商品...')
|
|
|
+ expect(searchInput.value).toBe('手机')
|
|
|
+
|
|
|
+ // 验证搜索结果标题
|
|
|
+ expect(screen.getByText('搜索结果:"手机"')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('共找到 2 件商品')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ it('显示搜索结果列表', async () => {
|
|
|
+ renderWithProviders(<SearchResultPage />)
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByTestId('goods-list')).toBeInTheDocument()
|
|
|
+
|
|
|
+ const goodsItems = screen.getAllByTestId(/goods-item-\d+/)
|
|
|
+ expect(goodsItems).toHaveLength(2)
|
|
|
+
|
|
|
+ expect(screen.getByText('iPhone 15')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('MacBook Pro')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ it('处理搜索提交', async () => {
|
|
|
+ renderWithProviders(<SearchResultPage />)
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ const searchInput = document.querySelector('.search-input') as HTMLInputElement
|
|
|
+
|
|
|
+ // 修改搜索关键词
|
|
|
+ fireEvent.change(searchInput, { target: { value: 'iPad' } })
|
|
|
+
|
|
|
+ // 提交搜索
|
|
|
+ fireEvent.keyPress(searchInput, { key: 'Enter', code: 'Enter' })
|
|
|
+ })
|
|
|
+
|
|
|
+ // 验证API被重新调用
|
|
|
+ await waitFor(() => {
|
|
|
+ const { goodsClient } = require('@/api')
|
|
|
+ // 由于React Query的异步特性,这里可能被调用多次,我们检查最后一次调用
|
|
|
+ const lastCall = goodsClient.$get.mock.calls[goodsClient.$get.mock.calls.length - 1]
|
|
|
+ expect(lastCall[0]).toEqual({
|
|
|
+ query: {
|
|
|
+ page: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ keyword: 'iPad',
|
|
|
+ filters: JSON.stringify({ state: 1 })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ it('显示空状态', async () => {
|
|
|
+ // Mock empty response
|
|
|
+ const { goodsClient } = require('@/api')
|
|
|
+ goodsClient.$get.mockResolvedValue({
|
|
|
+ status: 200,
|
|
|
+ json: () => Promise.resolve({
|
|
|
+ data: [],
|
|
|
+ pagination: {
|
|
|
+ current: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ total: 0,
|
|
|
+ totalPages: 0
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ renderWithProviders(<SearchResultPage />)
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('暂无相关商品')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('换个关键词试试吧')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ it('处理商品点击', async () => {
|
|
|
+ renderWithProviders(<SearchResultPage />)
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ const firstGoodsItem = screen.getByTestId('goods-item-0')
|
|
|
+ fireEvent.click(firstGoodsItem)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 验证跳转到商品详情页面
|
|
|
+ expect(mockNavigateTo).toHaveBeenCalledWith({
|
|
|
+ url: '/pages/goods-detail/index?id=1'
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ it('处理添加到购物车', async () => {
|
|
|
+ renderWithProviders(<SearchResultPage />)
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ const addCartButtons = screen.getAllByTestId('add-cart-btn')
|
|
|
+ fireEvent.click(addCartButtons[0])
|
|
|
+ })
|
|
|
+
|
|
|
+ // 验证购物车功能被调用
|
|
|
+ const { useCart } = require('@/utils/cart')
|
|
|
+ const { addToCart } = useCart()
|
|
|
+ expect(addToCart).toHaveBeenCalledWith({
|
|
|
+ id: 1,
|
|
|
+ name: 'iPhone 15',
|
|
|
+ price: 599900,
|
|
|
+ image: 'https://example.com/iphone15.jpg',
|
|
|
+ stock: 10,
|
|
|
+ quantity: 1
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ it('处理下拉刷新', async () => {
|
|
|
+ renderWithProviders(<SearchResultPage />)
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ // 模拟下拉刷新 - 直接调用onRefresherRefresh
|
|
|
+ const scrollView = document.querySelector('.search-result-content')
|
|
|
+ if (scrollView) {
|
|
|
+ // 触发下拉刷新事件
|
|
|
+ const event = new Event('refresherrefresh')
|
|
|
+ scrollView.dispatchEvent(event)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 验证API被重新调用
|
|
|
+ await waitFor(() => {
|
|
|
+ const { goodsClient } = require('@/api')
|
|
|
+ expect(goodsClient.$get).toHaveBeenCalled()
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ it('处理清除搜索输入', async () => {
|
|
|
+ renderWithProviders(<SearchResultPage />)
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ const searchInput = document.querySelector('.search-input') as HTMLInputElement
|
|
|
+
|
|
|
+ // 输入内容
|
|
|
+ fireEvent.change(searchInput, { target: { value: '测试商品' } })
|
|
|
+
|
|
|
+ // 验证清除按钮出现
|
|
|
+ const clearIcon = document.querySelector('.clear-icon')
|
|
|
+ expect(clearIcon).toBeInTheDocument()
|
|
|
+
|
|
|
+ // 点击清除按钮
|
|
|
+ fireEvent.click(clearIcon!)
|
|
|
+
|
|
|
+ // 验证搜索输入被清空
|
|
|
+ expect(searchInput.value).toBe('')
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ it('验证样式类名应用', async () => {
|
|
|
+ renderWithProviders(<SearchResultPage />)
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ const container = document.querySelector('.search-result-page')
|
|
|
+ expect(container).toBeInTheDocument()
|
|
|
+
|
|
|
+ const content = document.querySelector('.search-result-content')
|
|
|
+ expect(content).toBeInTheDocument()
|
|
|
+
|
|
|
+ const searchBar = document.querySelector('.search-bar-container')
|
|
|
+ expect(searchBar).toBeInTheDocument()
|
|
|
+
|
|
|
+ const resultContainer = document.querySelector('.result-container')
|
|
|
+ expect(resultContainer).toBeInTheDocument()
|
|
|
+
|
|
|
+ const goodsListContainer = document.querySelector('.goods-list-container')
|
|
|
+ expect(goodsListContainer).toBeInTheDocument()
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ it('显示加载状态', async () => {
|
|
|
+ // Mock loading state
|
|
|
+ const { goodsClient } = require('@/api')
|
|
|
+ goodsClient.$get.mockImplementation(() => new Promise(() => {})) // Never resolves
|
|
|
+
|
|
|
+ renderWithProviders(<SearchResultPage />)
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('搜索中...')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+ })
|
|
|
+})
|