goods-spec-selector.test.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. import { render, screen, fireEvent, waitFor } from '@testing-library/react'
  2. import { GoodsSpecSelector } from '@/components/goods-spec-selector'
  3. import { goodsClient } from '@/api'
  4. // Mock API客户端
  5. jest.mock('@/api', () => ({
  6. goodsClient: {
  7. ':id': {
  8. children: {
  9. $get: jest.fn()
  10. }
  11. }
  12. }
  13. }))
  14. // Mock Taro组件
  15. jest.mock('@tarojs/components', () => ({
  16. View: ({ children, className, onClick }: any) => (
  17. <div className={className} onClick={onClick}>
  18. {children}
  19. </div>
  20. ),
  21. Text: ({ children, className }: any) => (
  22. <span className={className}>{children}</span>
  23. ),
  24. ScrollView: ({ children, className }: any) => (
  25. <div className={className}>{children}</div>
  26. ),
  27. Input: ({ className, type, value, onInput, onBlur, placeholder, maxlength, confirmType }: any) => (
  28. <input
  29. className={className}
  30. type={type}
  31. value={value}
  32. onChange={(e) => onInput?.({ detail: { value: e.target.value } })}
  33. onBlur={(e) => onBlur?.({ detail: { value: e.target.value } })}
  34. placeholder={placeholder}
  35. maxLength={maxlength}
  36. data-testid="input"
  37. />
  38. )
  39. }))
  40. // Mock UI组件
  41. jest.mock('@/components/ui/button', () => ({
  42. Button: ({ children, onClick, className, disabled }: any) => (
  43. <button className={className} onClick={onClick} disabled={disabled}>
  44. {children}
  45. </button>
  46. )
  47. }))
  48. describe('GoodsSpecSelector组件', () => {
  49. const mockOnClose = jest.fn()
  50. const mockOnConfirm = jest.fn()
  51. beforeEach(() => {
  52. jest.clearAllMocks()
  53. })
  54. it('当visible为false时不渲染', () => {
  55. const { container } = render(
  56. <GoodsSpecSelector
  57. visible={false}
  58. onClose={mockOnClose}
  59. onConfirm={mockOnConfirm}
  60. parentGoodsId={1}
  61. />
  62. )
  63. expect(container.firstChild).toBeNull()
  64. })
  65. it('当visible为true时渲染组件', () => {
  66. render(
  67. <GoodsSpecSelector
  68. visible={true}
  69. onClose={mockOnClose}
  70. onConfirm={mockOnConfirm}
  71. parentGoodsId={1}
  72. />
  73. )
  74. expect(screen.getByText('选择规格')).toBeInTheDocument()
  75. expect(screen.getByText('加载规格选项...')).toBeInTheDocument()
  76. })
  77. it('成功获取子商品列表后显示规格选项', async () => {
  78. // Mock成功的API响应
  79. const mockResponse = {
  80. status: 200,
  81. json: async () => ({
  82. data: [
  83. {
  84. id: 101,
  85. name: '红色款',
  86. price: 299,
  87. stock: 50,
  88. imageFile: { fullUrl: 'http://example.com/image.jpg' }
  89. },
  90. {
  91. id: 102,
  92. name: '蓝色款',
  93. price: 319,
  94. stock: 30,
  95. imageFile: null
  96. }
  97. ],
  98. total: 2,
  99. page: 1,
  100. pageSize: 100,
  101. totalPages: 1
  102. })
  103. }
  104. ;(goodsClient[':id'].children.$get as jest.Mock).mockResolvedValue(mockResponse)
  105. render(
  106. <GoodsSpecSelector
  107. visible={true}
  108. onClose={mockOnClose}
  109. onConfirm={mockOnConfirm}
  110. parentGoodsId={1}
  111. />
  112. )
  113. // 等待加载完成
  114. await waitFor(() => {
  115. expect(screen.getByText('红色款')).toBeInTheDocument()
  116. })
  117. expect(screen.getByText('蓝色款')).toBeInTheDocument()
  118. expect(screen.getByText('¥299.00')).toBeInTheDocument()
  119. expect(screen.getByText('库存: 50')).toBeInTheDocument()
  120. })
  121. it('API调用失败时显示错误信息', async () => {
  122. // Mock失败的API响应
  123. ;(goodsClient[':id'].children.$get as jest.Mock).mockRejectedValue(
  124. new Error('网络错误')
  125. )
  126. render(
  127. <GoodsSpecSelector
  128. visible={true}
  129. onClose={mockOnClose}
  130. onConfirm={mockOnConfirm}
  131. parentGoodsId={1}
  132. />
  133. )
  134. // 等待错误显示
  135. await waitFor(() => {
  136. expect(screen.getByText('网络错误')).toBeInTheDocument()
  137. })
  138. expect(screen.getByText('重试')).toBeInTheDocument()
  139. })
  140. it('没有规格选项时显示空状态', async () => {
  141. // Mock空响应
  142. const mockResponse = {
  143. status: 200,
  144. json: async () => ({
  145. data: [],
  146. total: 0,
  147. page: 1,
  148. pageSize: 100,
  149. totalPages: 0
  150. })
  151. }
  152. ;(goodsClient[':id'].children.$get as jest.Mock).mockResolvedValue(mockResponse)
  153. render(
  154. <GoodsSpecSelector
  155. visible={true}
  156. onClose={mockOnClose}
  157. onConfirm={mockOnConfirm}
  158. parentGoodsId={1}
  159. />
  160. )
  161. await waitFor(() => {
  162. expect(screen.getByText('暂无规格选项')).toBeInTheDocument()
  163. })
  164. })
  165. it('点击规格选项时选中该规格', async () => {
  166. const mockResponse = {
  167. status: 200,
  168. json: async () => ({
  169. data: [
  170. {
  171. id: 101,
  172. name: '红色款',
  173. price: 299,
  174. stock: 50,
  175. imageFile: null
  176. }
  177. ],
  178. total: 1,
  179. page: 1,
  180. pageSize: 100,
  181. totalPages: 1
  182. })
  183. }
  184. ;(goodsClient[':id'].children.$get as jest.Mock).mockResolvedValue(mockResponse)
  185. render(
  186. <GoodsSpecSelector
  187. visible={true}
  188. onClose={mockOnClose}
  189. onConfirm={mockOnConfirm}
  190. parentGoodsId={1}
  191. />
  192. )
  193. await waitFor(() => {
  194. expect(screen.getByText('红色款')).toBeInTheDocument()
  195. })
  196. // 点击规格选项
  197. fireEvent.click(screen.getByText('红色款'))
  198. // 应该显示数量选择器
  199. expect(screen.getByText('数量')).toBeInTheDocument()
  200. })
  201. it('点击确认按钮时调用onConfirm回调', async () => {
  202. const mockResponse = {
  203. status: 200,
  204. json: async () => ({
  205. data: [
  206. {
  207. id: 101,
  208. name: '红色款',
  209. price: 299,
  210. stock: 50,
  211. imageFile: null
  212. }
  213. ],
  214. total: 1,
  215. page: 1,
  216. pageSize: 100,
  217. totalPages: 1
  218. })
  219. }
  220. ;(goodsClient[':id'].children.$get as jest.Mock).mockResolvedValue(mockResponse)
  221. render(
  222. <GoodsSpecSelector
  223. visible={true}
  224. onClose={mockOnClose}
  225. onConfirm={mockOnConfirm}
  226. parentGoodsId={1}
  227. />
  228. )
  229. await waitFor(() => {
  230. expect(screen.getByText('红色款')).toBeInTheDocument()
  231. })
  232. // 选择规格
  233. fireEvent.click(screen.getByText('红色款'))
  234. // 点击确认按钮
  235. const confirmButton = screen.getByRole('button', { name: /确定/ })
  236. fireEvent.click(confirmButton)
  237. expect(mockOnConfirm).toHaveBeenCalledWith(
  238. expect.objectContaining({
  239. id: 101,
  240. name: '红色款',
  241. price: 299,
  242. stock: 50
  243. }),
  244. 1,
  245. undefined
  246. )
  247. expect(mockOnClose).toHaveBeenCalled()
  248. })
  249. it('点击关闭按钮时调用onClose回调', () => {
  250. const { container } = render(
  251. <GoodsSpecSelector
  252. visible={true}
  253. onClose={mockOnClose}
  254. onConfirm={mockOnConfirm}
  255. parentGoodsId={1}
  256. />
  257. )
  258. // 点击关闭按钮(使用className查找)
  259. const closeButton = container.querySelector('.spec-modal-close')
  260. expect(closeButton).not.toBeNull()
  261. fireEvent.click(closeButton!)
  262. expect(mockOnClose).toHaveBeenCalled()
  263. })
  264. })