basic.test.tsx 9.1 KB

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