goods-spec-selector.test.tsx 6.6 KB

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