basic.test.tsx 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. import React from 'react'
  2. import { render, screen, fireEvent, waitFor } from '@testing-library/react'
  3. import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
  4. import SearchResultPage from '@/pages/search-result/index'
  5. // 导入Taro mock函数
  6. import {
  7. mockNavigateTo,
  8. mockGetCurrentInstance,
  9. mockStopPullDownRefresh
  10. } from '~/__mocks__/taroMock'
  11. // Mock components
  12. jest.mock('@/components/ui/navbar', () => ({
  13. Navbar: ({ title, onClickLeft }: { title: string; onClickLeft: () => void }) => (
  14. <div data-testid="navbar">
  15. <span>{title}</span>
  16. <button onClick={onClickLeft}>返回</button>
  17. </div>
  18. ),
  19. }))
  20. jest.mock('@/components/goods-list', () => ({
  21. __esModule: true,
  22. default: ({
  23. goodsList,
  24. onClick,
  25. onAddCart
  26. }: {
  27. goodsList: any[],
  28. onClick: (goods: any) => void,
  29. onAddCart: (goods: any) => void
  30. }) => (
  31. <div data-testid="goods-list">
  32. {goodsList.map((goods, index) => (
  33. <div
  34. key={goods.id}
  35. data-testid={`goods-item-${index}`}
  36. onClick={() => onClick(goods)}
  37. >
  38. <span data-testid="goods-name">{goods.name}</span>
  39. <span data-testid="goods-price">{goods.price}</span>
  40. <button
  41. data-testid="add-cart-btn"
  42. onClick={() => onAddCart(goods)}
  43. >
  44. 加入购物车
  45. </button>
  46. </div>
  47. ))}
  48. </div>
  49. ),
  50. }))
  51. // Mock API client
  52. jest.mock('@/api', () => ({
  53. goodsClient: {
  54. $get: jest.fn()
  55. }
  56. }))
  57. // Mock cart hook
  58. jest.mock('@/utils/cart', () => ({
  59. useCart: () => ({
  60. addToCart: jest.fn()
  61. })
  62. }))
  63. describe('SearchResultPage', () => {
  64. let queryClient: QueryClient
  65. beforeEach(() => {
  66. queryClient = new QueryClient({
  67. defaultOptions: {
  68. queries: { retry: false },
  69. mutations: { retry: false },
  70. },
  71. })
  72. // Reset all mocks
  73. jest.clearAllMocks()
  74. // Mock Taro.getCurrentInstance
  75. mockGetCurrentInstance.mockReturnValue({
  76. router: {
  77. params: {
  78. keyword: '手机'
  79. }
  80. }
  81. })
  82. // Mock API response
  83. const { goodsClient } = require('@/api')
  84. goodsClient.$get.mockResolvedValue({
  85. status: 200,
  86. json: () => Promise.resolve({
  87. data: [
  88. {
  89. id: 1,
  90. name: 'iPhone 15',
  91. price: 599900,
  92. originPrice: 699900,
  93. stock: 10,
  94. salesNum: 100,
  95. imageFile: {
  96. fullUrl: 'https://example.com/iphone15.jpg'
  97. }
  98. },
  99. {
  100. id: 2,
  101. name: 'MacBook Pro',
  102. price: 1299900,
  103. originPrice: 1499900,
  104. stock: 5,
  105. salesNum: 50,
  106. imageFile: {
  107. fullUrl: 'https://example.com/macbook.jpg'
  108. }
  109. }
  110. ],
  111. pagination: {
  112. current: 1,
  113. pageSize: 10,
  114. total: 2,
  115. totalPages: 1
  116. }
  117. })
  118. })
  119. })
  120. const renderWithProviders = (component: React.ReactElement) => {
  121. return render(
  122. <QueryClientProvider client={queryClient}>
  123. {component}
  124. </QueryClientProvider>
  125. )
  126. }
  127. it('渲染页面标题和布局', async () => {
  128. renderWithProviders(<SearchResultPage />)
  129. await waitFor(() => {
  130. expect(screen.getByTestId('navbar')).toBeInTheDocument()
  131. expect(screen.getByTestId('navbar')).toHaveTextContent('搜索结果')
  132. })
  133. })
  134. it('显示搜索栏和关键词', async () => {
  135. renderWithProviders(<SearchResultPage />)
  136. await waitFor(() => {
  137. // 验证搜索栏存在
  138. const searchInput = document.querySelector('.search-input') as HTMLInputElement
  139. expect(searchInput).toBeInTheDocument()
  140. expect(searchInput.placeholder).toBe('搜索商品...')
  141. expect(searchInput.value).toBe('手机')
  142. // 验证搜索结果标题
  143. expect(screen.getByText('搜索结果:"手机"')).toBeInTheDocument()
  144. expect(screen.getByText('共找到 2 件商品')).toBeInTheDocument()
  145. })
  146. })
  147. it('显示搜索结果列表', async () => {
  148. renderWithProviders(<SearchResultPage />)
  149. await waitFor(() => {
  150. expect(screen.getByTestId('goods-list')).toBeInTheDocument()
  151. const goodsItems = screen.getAllByTestId(/goods-item-\d+/)
  152. expect(goodsItems).toHaveLength(2)
  153. expect(screen.getByText('iPhone 15')).toBeInTheDocument()
  154. expect(screen.getByText('MacBook Pro')).toBeInTheDocument()
  155. })
  156. })
  157. it('处理搜索提交', async () => {
  158. renderWithProviders(<SearchResultPage />)
  159. await waitFor(() => {
  160. const searchInput = document.querySelector('.search-input') as HTMLInputElement
  161. // 修改搜索关键词
  162. fireEvent.change(searchInput, { target: { value: 'iPad' } })
  163. // 提交搜索
  164. fireEvent.keyPress(searchInput, { key: 'Enter', code: 'Enter' })
  165. })
  166. // 验证API被重新调用
  167. await waitFor(() => {
  168. const { goodsClient } = require('@/api')
  169. // 由于React Query的异步特性,这里可能被调用多次,我们检查最后一次调用
  170. const lastCall = goodsClient.$get.mock.calls[goodsClient.$get.mock.calls.length - 1]
  171. expect(lastCall[0]).toEqual({
  172. query: {
  173. page: 1,
  174. pageSize: 10,
  175. keyword: 'iPad',
  176. filters: JSON.stringify({ state: 1 })
  177. }
  178. })
  179. })
  180. })
  181. it('显示空状态', async () => {
  182. // Mock empty response
  183. const { goodsClient } = require('@/api')
  184. goodsClient.$get.mockResolvedValue({
  185. status: 200,
  186. json: () => Promise.resolve({
  187. data: [],
  188. pagination: {
  189. current: 1,
  190. pageSize: 10,
  191. total: 0,
  192. totalPages: 0
  193. }
  194. })
  195. })
  196. renderWithProviders(<SearchResultPage />)
  197. await waitFor(() => {
  198. expect(screen.getByText('暂无相关商品')).toBeInTheDocument()
  199. expect(screen.getByText('换个关键词试试吧')).toBeInTheDocument()
  200. })
  201. })
  202. it('处理商品点击', async () => {
  203. renderWithProviders(<SearchResultPage />)
  204. await waitFor(() => {
  205. const firstGoodsItem = screen.getByTestId('goods-item-0')
  206. fireEvent.click(firstGoodsItem)
  207. })
  208. // 验证跳转到商品详情页面
  209. expect(mockNavigateTo).toHaveBeenCalledWith({
  210. url: '/pages/goods-detail/index?id=1'
  211. })
  212. })
  213. it('处理添加到购物车', async () => {
  214. renderWithProviders(<SearchResultPage />)
  215. await waitFor(() => {
  216. const addCartButtons = screen.getAllByTestId('add-cart-btn')
  217. fireEvent.click(addCartButtons[0])
  218. })
  219. // 验证购物车功能被调用
  220. const { useCart } = require('@/utils/cart')
  221. const { addToCart } = useCart()
  222. expect(addToCart).toHaveBeenCalledWith({
  223. id: 1,
  224. name: 'iPhone 15',
  225. price: 599900,
  226. image: 'https://example.com/iphone15.jpg',
  227. stock: 10,
  228. quantity: 1
  229. })
  230. })
  231. it('处理下拉刷新', async () => {
  232. renderWithProviders(<SearchResultPage />)
  233. await waitFor(() => {
  234. // 模拟下拉刷新 - 直接调用onRefresherRefresh
  235. const scrollView = document.querySelector('.search-result-content')
  236. if (scrollView) {
  237. // 触发下拉刷新事件
  238. const event = new Event('refresherrefresh')
  239. scrollView.dispatchEvent(event)
  240. }
  241. })
  242. // 验证API被重新调用
  243. await waitFor(() => {
  244. const { goodsClient } = require('@/api')
  245. expect(goodsClient.$get).toHaveBeenCalled()
  246. })
  247. })
  248. it('处理清除搜索输入', async () => {
  249. renderWithProviders(<SearchResultPage />)
  250. await waitFor(() => {
  251. const searchInput = document.querySelector('.search-input') as HTMLInputElement
  252. // 输入内容
  253. fireEvent.change(searchInput, { target: { value: '测试商品' } })
  254. // 验证清除按钮出现
  255. const clearIcon = document.querySelector('.clear-icon')
  256. expect(clearIcon).toBeInTheDocument()
  257. // 点击清除按钮
  258. fireEvent.click(clearIcon!)
  259. // 验证搜索输入被清空
  260. expect(searchInput.value).toBe('')
  261. })
  262. })
  263. it('验证样式类名应用', async () => {
  264. renderWithProviders(<SearchResultPage />)
  265. await waitFor(() => {
  266. const container = document.querySelector('.search-result-page')
  267. expect(container).toBeInTheDocument()
  268. const content = document.querySelector('.search-result-content')
  269. expect(content).toBeInTheDocument()
  270. const searchBar = document.querySelector('.search-bar-container')
  271. expect(searchBar).toBeInTheDocument()
  272. const resultContainer = document.querySelector('.result-container')
  273. expect(resultContainer).toBeInTheDocument()
  274. const goodsListContainer = document.querySelector('.goods-list-container')
  275. expect(goodsListContainer).toBeInTheDocument()
  276. })
  277. })
  278. it('显示加载状态', async () => {
  279. // Mock loading state
  280. const { goodsClient } = require('@/api')
  281. goodsClient.$get.mockImplementation(() => new Promise(() => {})) // Never resolves
  282. renderWithProviders(<SearchResultPage />)
  283. await waitFor(() => {
  284. expect(screen.getByText('搜索中...')).toBeInTheDocument()
  285. })
  286. })
  287. })