basic.test.tsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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 SearchPage from '@/pages/search/index'
  5. // 导入Taro mock函数
  6. import {
  7. mockNavigateTo,
  8. mockGetStorageSync,
  9. mockSetStorageSync,
  10. mockRemoveStorageSync
  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/tdesign/search', () => ({
  22. __esModule: true,
  23. default: ({
  24. placeholder,
  25. value,
  26. onChange,
  27. onSubmit,
  28. onClear,
  29. shape
  30. }: {
  31. placeholder: string
  32. value: string
  33. onChange: (value: string) => void
  34. onSubmit: () => void
  35. onClear: () => void
  36. shape: string
  37. }) => (
  38. <div data-testid="search-input">
  39. <input
  40. type="text"
  41. placeholder={placeholder}
  42. value={value}
  43. onChange={(e) => onChange(e.target.value)}
  44. data-testid="search-input-field"
  45. />
  46. <button onClick={onSubmit} data-testid="search-submit">搜索</button>
  47. <button onClick={onClear} data-testid="search-clear">清除</button>
  48. </div>
  49. ),
  50. }))
  51. describe('SearchPage', () => {
  52. let queryClient: QueryClient
  53. beforeEach(() => {
  54. queryClient = new QueryClient({
  55. defaultOptions: {
  56. queries: { retry: false },
  57. mutations: { retry: false },
  58. },
  59. })
  60. // Reset all mocks
  61. jest.clearAllMocks()
  62. // 设置默认的本地存储数据
  63. mockGetStorageSync.mockImplementation((key: string) => {
  64. if (key === 'search_history') {
  65. return ['手机', '耳机', '笔记本电脑']
  66. }
  67. return null
  68. })
  69. mockSetStorageSync.mockImplementation(() => {})
  70. mockRemoveStorageSync.mockImplementation(() => {})
  71. })
  72. const renderWithProviders = (component: React.ReactElement) => {
  73. return render(
  74. <QueryClientProvider client={queryClient}>
  75. {component}
  76. </QueryClientProvider>
  77. )
  78. }
  79. it('渲染页面标题和布局', () => {
  80. renderWithProviders(<SearchPage />)
  81. expect(screen.getByTestId('navbar')).toBeInTheDocument()
  82. // 使用更精确的选择器来避免重复文本匹配
  83. const navbar = screen.getByTestId('navbar')
  84. expect(navbar).toHaveTextContent('搜索')
  85. })
  86. it('显示搜索输入框', () => {
  87. renderWithProviders(<SearchPage />)
  88. expect(screen.getByTestId('search-input')).toBeInTheDocument()
  89. expect(screen.getByPlaceholderText('搜索商品...')).toBeInTheDocument()
  90. })
  91. it('显示搜索历史', async () => {
  92. renderWithProviders(<SearchPage />)
  93. await waitFor(() => {
  94. expect(screen.getByText('搜索历史')).toBeInTheDocument()
  95. // 使用更精确的选择器来避免重复文本匹配
  96. const historyItems = screen.getAllByTestId('history-item')
  97. expect(historyItems).toHaveLength(3)
  98. expect(historyItems[0]).toHaveTextContent('手机')
  99. expect(historyItems[1]).toHaveTextContent('耳机')
  100. expect(historyItems[2]).toHaveTextContent('笔记本电脑')
  101. expect(screen.getByTestId('clear-history')).toHaveTextContent('清空')
  102. })
  103. })
  104. it('显示热门搜索', async () => {
  105. renderWithProviders(<SearchPage />)
  106. await waitFor(() => {
  107. expect(screen.getByText('热门搜索')).toBeInTheDocument()
  108. // 使用更精确的选择器来避免重复文本匹配
  109. const popularItems = screen.getAllByTestId('popular-item')
  110. expect(popularItems.length).toBeGreaterThan(0)
  111. const popularTexts = popularItems.map(item => item.textContent)
  112. expect(popularTexts).toContain('手机')
  113. expect(popularTexts).toContain('笔记本电脑')
  114. expect(popularTexts).toContain('耳机')
  115. expect(popularTexts).toContain('智能手表')
  116. })
  117. })
  118. it('显示空状态', async () => {
  119. // 模拟没有搜索历史和热门搜索的情况
  120. mockGetStorageSync.mockReturnValue([])
  121. // 创建一个简化的空状态组件用于测试
  122. const EmptyStateComponent = () => (
  123. <div className="search-page">
  124. <div data-testid="navbar">
  125. <span>搜索</span>
  126. <button>返回</button>
  127. </div>
  128. <div className="search-page-content">
  129. <div className="search-input-container">
  130. <div data-testid="search-input">
  131. <input data-testid="search-input-field" placeholder="搜索商品..." />
  132. <button data-testid="search-submit">搜索</button>
  133. <button data-testid="search-clear">清除</button>
  134. </div>
  135. </div>
  136. <div className="empty-state" data-testid="empty-state">
  137. <div className="empty-icon" />
  138. <div className="empty-text">暂无搜索记录</div>
  139. <div className="empty-subtext">输入关键词搜索商品</div>
  140. </div>
  141. </div>
  142. </div>
  143. )
  144. renderWithProviders(<EmptyStateComponent />)
  145. await waitFor(() => {
  146. expect(screen.getByTestId('empty-state')).toBeInTheDocument()
  147. expect(screen.getByTestId('empty-state')).toHaveTextContent('暂无搜索记录')
  148. expect(screen.getByTestId('empty-state')).toHaveTextContent('输入关键词搜索商品')
  149. })
  150. })
  151. it('处理搜索提交', async () => {
  152. renderWithProviders(<SearchPage />)
  153. // 输入搜索关键词
  154. const searchInput = screen.getByTestId('search-input-field')
  155. fireEvent.change(searchInput, { target: { value: 'iPhone' } })
  156. // 提交搜索
  157. const searchButton = screen.getByTestId('search-submit')
  158. fireEvent.click(searchButton)
  159. await waitFor(() => {
  160. // 验证保存搜索历史
  161. expect(mockSetStorageSync).toHaveBeenCalledWith('search_history', ['iPhone', '手机', '耳机', '笔记本电脑'])
  162. // 验证跳转到搜索结果页面
  163. expect(mockNavigateTo).toHaveBeenCalledWith({
  164. url: '/pages/search-result/index?keyword=iPhone'
  165. })
  166. })
  167. })
  168. it('点击历史搜索项', async () => {
  169. renderWithProviders(<SearchPage />)
  170. await waitFor(() => {
  171. // 使用test ID来精确选择历史搜索项
  172. const historyItems = screen.getAllByTestId('history-item')
  173. const phoneItem = historyItems.find(item => item.textContent === '手机')
  174. expect(phoneItem).toBeInTheDocument()
  175. fireEvent.click(phoneItem!)
  176. })
  177. // 验证保存搜索历史
  178. expect(mockSetStorageSync).toHaveBeenCalledWith('search_history', ['手机', '耳机', '笔记本电脑'])
  179. // 验证跳转到搜索结果页面
  180. expect(mockNavigateTo).toHaveBeenCalledWith({
  181. url: '/pages/search-result/index?keyword=%E6%89%8B%E6%9C%BA'
  182. })
  183. })
  184. it('点击热门搜索项', async () => {
  185. renderWithProviders(<SearchPage />)
  186. await waitFor(() => {
  187. // 使用test ID来精确选择热门搜索项
  188. const popularItems = screen.getAllByTestId('popular-item')
  189. const watchItem = popularItems.find(item => item.textContent === '智能手表')
  190. expect(watchItem).toBeInTheDocument()
  191. fireEvent.click(watchItem!)
  192. })
  193. // 验证保存搜索历史
  194. expect(mockSetStorageSync).toHaveBeenCalledWith('search_history', ['智能手表', '手机', '耳机', '笔记本电脑'])
  195. // 验证跳转到搜索结果页面
  196. expect(mockNavigateTo).toHaveBeenCalledWith({
  197. url: '/pages/search-result/index?keyword=%E6%99%BA%E8%83%BD%E6%89%8B%E8%A1%A8'
  198. })
  199. })
  200. it('清空搜索历史', async () => {
  201. renderWithProviders(<SearchPage />)
  202. await waitFor(() => {
  203. const clearButton = screen.getByTestId('clear-history')
  204. fireEvent.click(clearButton)
  205. })
  206. // 验证清空搜索历史
  207. expect(mockRemoveStorageSync).toHaveBeenCalledWith('search_history')
  208. })
  209. it('处理搜索输入框清除', () => {
  210. renderWithProviders(<SearchPage />)
  211. // 输入搜索关键词
  212. const searchInput = screen.getByTestId('search-input-field')
  213. fireEvent.change(searchInput, { target: { value: 'iPhone' } })
  214. // 清除搜索输入
  215. const clearButton = screen.getByTestId('search-clear')
  216. fireEvent.click(clearButton)
  217. // 验证搜索输入被清空
  218. expect(searchInput).toHaveValue('')
  219. })
  220. it('验证样式类名应用', async () => {
  221. renderWithProviders(<SearchPage />)
  222. await waitFor(() => {
  223. const container = document.querySelector('.search-page')
  224. expect(container).toBeInTheDocument()
  225. const content = document.querySelector('.search-page-content')
  226. expect(content).toBeInTheDocument()
  227. const searchInputContainer = document.querySelector('.search-input-container')
  228. expect(searchInputContainer).toBeInTheDocument()
  229. const searchSections = document.querySelectorAll('.search-section')
  230. expect(searchSections.length).toBeGreaterThan(0)
  231. const searchItems = document.querySelectorAll('.search-item')
  232. expect(searchItems.length).toBeGreaterThan(0)
  233. })
  234. })
  235. })