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 }) => (
{title}
),
}))
jest.mock('@/components/goods-list', () => ({
__esModule: true,
default: ({
goodsList,
onClick,
onAddCart
}: {
goodsList: any[],
onClick: (goods: any) => void,
onAddCart: (goods: any) => void
}) => (
{goodsList.map((goods, index) => (
onClick(goods)}
>
{goods.name}
{goods.price}
))}
),
}))
// 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(
{component}
)
}
it('渲染页面标题和布局', async () => {
renderWithProviders()
await waitFor(() => {
expect(screen.getByTestId('navbar')).toBeInTheDocument()
expect(screen.getByTestId('navbar')).toHaveTextContent('搜索结果')
})
})
it('显示搜索栏和关键词', async () => {
renderWithProviders()
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()
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()
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()
await waitFor(() => {
expect(screen.getByText('暂无相关商品')).toBeInTheDocument()
expect(screen.getByText('换个关键词试试吧')).toBeInTheDocument()
})
})
it('处理商品点击', async () => {
renderWithProviders()
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()
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()
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()
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()
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()
await waitFor(() => {
expect(screen.getByText('搜索中...')).toBeInTheDocument()
})
})
})