CartContext.test.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. import React from 'react'
  2. import { render } from '@testing-library/react'
  3. import { CartProvider, useCart, CartItem } from '@/contexts/CartContext'
  4. import { mockShowToast, mockGetStorageSync, mockSetStorageSync } from '~/__mocks__/taroMock'
  5. // Mock Taro API
  6. jest.mock('@tarojs/taro', () => jest.requireActual('~/__mocks__/taroMock'))
  7. // 测试组件用于访问购物车hook
  8. const TestComponent = ({ action, item }: { action: string; item?: CartItem }) => {
  9. const cart = useCart()
  10. React.useEffect(() => {
  11. console.log('TestComponent useEffect called, action:', action, 'item:', item, 'isLoading:', cart.isLoading)
  12. // 等待购物车加载完成后再添加商品
  13. if (!cart.isLoading && action === 'add' && item) {
  14. console.log('Calling addToCart with item:', item)
  15. cart.addToCart(item)
  16. }
  17. }, [action, item, cart.isLoading])
  18. console.log('TestComponent rendering, cart items:', cart.cart.items, 'isLoading:', cart.isLoading)
  19. return (
  20. <div>
  21. <div data-testid="items-count">{cart.cart.items.length}</div>
  22. <div data-testid="total-count">{cart.cart.totalCount}</div>
  23. <div data-testid="total-amount">{cart.cart.totalAmount}</div>
  24. {cart.cart.items.map((item, index) => (
  25. <div key={index} data-testid={`item-${index}`}>
  26. <span data-testid={`item-${index}-id`}>{item.id}</span>
  27. <span data-testid={`item-${index}-name`}>{item.name}</span>
  28. {/* spec字段已移除 */}
  29. <span data-testid={`item-${index}-quantity`}>{item.quantity}</span>
  30. <span data-testid={`item-${index}-stock`} style={{ display: 'none' }}>{item.stock}</span>
  31. </div>
  32. ))}
  33. </div>
  34. )
  35. }
  36. describe('CartContext - 规格支持', () => {
  37. beforeEach(() => {
  38. mockGetStorageSync.mockReturnValue(null)
  39. mockSetStorageSync.mockClear()
  40. mockShowToast.mockClear()
  41. })
  42. it('应该支持添加父商品到购物车', () => {
  43. const parentGoods: CartItem = {
  44. id: 1001,
  45. parentGoodsId: 0, // 父商品,无父商品
  46. name: '测试父商品',
  47. price: 99.9,
  48. image: 'parent.jpg',
  49. stock: 10,
  50. quantity: 2,
  51. }
  52. const { getByTestId } = render(
  53. <CartProvider>
  54. <TestComponent action="add" item={parentGoods} />
  55. </CartProvider>
  56. )
  57. expect(getByTestId('items-count').textContent).toBe('1')
  58. expect(getByTestId('item-0-id').textContent).toBe('1001')
  59. expect(getByTestId('item-0-name').textContent).toBe('测试父商品')
  60. expect(mockSetStorageSync).toHaveBeenCalled()
  61. })
  62. it('应该支持添加子商品(带规格)到购物车', () => {
  63. const childGoods: CartItem = {
  64. id: 2001, // 子商品ID
  65. parentGoodsId: 2000, // 父商品ID
  66. name: '红色/M', // 规格名称
  67. price: 109.9,
  68. image: 'child.jpg',
  69. stock: 5,
  70. quantity: 1,
  71. }
  72. const { getByTestId } = render(
  73. <CartProvider>
  74. <TestComponent action="add" item={childGoods} />
  75. </CartProvider>
  76. )
  77. expect(getByTestId('items-count').textContent).toBe('1')
  78. expect(getByTestId('item-0-id').textContent).toBe('2001')
  79. expect(getByTestId('item-0-name').textContent).toBe('红色/M')
  80. expect(mockSetStorageSync).toHaveBeenCalled()
  81. })
  82. it('应该支持添加同一子商品多次(数量累加)', () => {
  83. const childGoods1: CartItem = {
  84. id: 3001,
  85. parentGoodsId: 3000, // 父商品ID
  86. name: '蓝色/L', // 规格名称
  87. price: 89.9,
  88. image: 'goods.jpg',
  89. stock: 10,
  90. quantity: 1,
  91. }
  92. const childGoods2: CartItem = {
  93. id: 3001, // 同一子商品ID
  94. parentGoodsId: 3000, // 父商品ID
  95. name: '蓝色/L', // 规格名称
  96. price: 89.9,
  97. image: 'goods.jpg',
  98. stock: 10,
  99. quantity: 3,
  100. }
  101. const { getByTestId, rerender } = render(
  102. <CartProvider>
  103. <TestComponent action="add" item={childGoods1} />
  104. </CartProvider>
  105. )
  106. expect(getByTestId('items-count').textContent).toBe('1')
  107. console.log('Item 0 id:', getByTestId('item-0-id').textContent)
  108. console.log('Item 0 name:', getByTestId('item-0-name').textContent)
  109. const quantityElement = getByTestId('item-0-quantity')
  110. console.log('Quantity element text:', quantityElement.textContent)
  111. // 修复:检查数量是否正确,应该是1而不是库存值10
  112. expect(parseInt(quantityElement.textContent || '0')).toBe(1)
  113. // 重新渲染添加更多数量
  114. rerender(
  115. <CartProvider>
  116. <TestComponent action="add" item={childGoods2} />
  117. </CartProvider>
  118. )
  119. expect(getByTestId('item-0-quantity').textContent).toBe('4') // 1 + 3
  120. })
  121. it('应该限制数量不超过库存', () => {
  122. const childGoods: CartItem = {
  123. id: 4001,
  124. parentGoodsId: 4000, // 父商品ID
  125. name: '测试商品 - 黑色/XL',
  126. price: 129.9,
  127. image: 'goods.jpg',
  128. stock: 2, // 库存只有2
  129. quantity: 3, // 尝试购买3个
  130. }
  131. const { getByTestId } = render(
  132. <CartProvider>
  133. <TestComponent action="add" item={childGoods} />
  134. </CartProvider>
  135. )
  136. // 应该显示库存不足提示
  137. expect(mockShowToast).toHaveBeenCalledWith(
  138. expect.objectContaining({ title: '库存不足' })
  139. )
  140. // 商品不应被添加
  141. expect(getByTestId('items-count').textContent).toBe('0')
  142. })
  143. it('应该支持同时添加父商品和不同子商品', () => {
  144. const parentGoods: CartItem = {
  145. id: 5001,
  146. parentGoodsId: 0, // 父商品,无父商品
  147. name: '测试父商品',
  148. price: 199.9,
  149. image: 'parent.jpg',
  150. stock: 20,
  151. quantity: 1,
  152. }
  153. const childGoods1: CartItem = {
  154. id: 5002, // 子商品ID1
  155. parentGoodsId: 5001, // 父商品ID
  156. name: '测试父商品 - 规格A',
  157. price: 219.9,
  158. image: 'child1.jpg',
  159. stock: 5,
  160. quantity: 2,
  161. }
  162. const childGoods2: CartItem = {
  163. id: 5003, // 子商品ID2
  164. parentGoodsId: 5001, // 父商品ID
  165. name: '测试父商品 - 规格B',
  166. price: 229.9,
  167. image: 'child2.jpg',
  168. stock: 3,
  169. quantity: 1,
  170. }
  171. const { getByTestId, rerender } = render(
  172. <CartProvider>
  173. <TestComponent action="add" item={parentGoods} />
  174. </CartProvider>
  175. )
  176. expect(getByTestId('items-count').textContent).toBe('1')
  177. // 添加第一个子商品
  178. rerender(
  179. <CartProvider>
  180. <TestComponent action="add" item={childGoods1} />
  181. </CartProvider>
  182. )
  183. expect(getByTestId('items-count').textContent).toBe('2')
  184. // 添加第二个子商品
  185. rerender(
  186. <CartProvider>
  187. <TestComponent action="add" item={childGoods2} />
  188. </CartProvider>
  189. )
  190. expect(getByTestId('items-count').textContent).toBe('3')
  191. expect(getByTestId('item-0-id').textContent).toBe('5001')
  192. expect(getByTestId('item-1-id').textContent).toBe('5002')
  193. expect(getByTestId('item-2-id').textContent).toBe('5003')
  194. })
  195. it('应该支持切换购物车项规格', () => {
  196. // 首先添加一个子商品到购物车
  197. const childGoods: CartItem = {
  198. id: 6001,
  199. parentGoodsId: 6000, // 父商品ID
  200. name: '测试父商品 - 规格A',
  201. price: 99.9,
  202. image: 'child1.jpg',
  203. stock: 10,
  204. quantity: 2,
  205. }
  206. // 创建一个新的测试组件来测试switchSpec
  207. const TestSwitchSpecComponent = ({ cartItemId, newChildGoods }: {
  208. cartItemId?: number,
  209. newChildGoods?: { id: number; name: string; price: number; stock: number; image?: string }
  210. }) => {
  211. const cart = useCart()
  212. React.useEffect(() => {
  213. if (!cart.isLoading && cartItemId && newChildGoods) {
  214. cart.switchSpec(cartItemId, newChildGoods)
  215. }
  216. }, [cart.isLoading, cartItemId, newChildGoods])
  217. return (
  218. <div>
  219. <div data-testid="items-count">{cart.cart.items.length}</div>
  220. {cart.cart.items.map((item, index) => (
  221. <div key={index} data-testid={`item-${index}`}>
  222. <span data-testid={`item-${index}-id`}>{item.id}</span>
  223. <span data-testid={`item-${index}-name`}>{item.name}</span>
  224. {/* spec字段已移除 */}
  225. <span data-testid={`item-${index}-quantity`}>{item.quantity}</span>
  226. <span data-testid={`item-${index}-price`}>{item.price}</span>
  227. </div>
  228. ))}
  229. </div>
  230. )
  231. }
  232. const { getByTestId, rerender } = render(
  233. <CartProvider>
  234. <TestComponent action="add" item={childGoods} />
  235. </CartProvider>
  236. )
  237. expect(getByTestId('items-count').textContent).toBe('1')
  238. expect(getByTestId('item-0-id').textContent).toBe('6001')
  239. expect(getByTestId('item-0-name').textContent).toBe('测试父商品 - 规格A')
  240. // 切换到新规格
  241. const newChildGoods = {
  242. id: 6002,
  243. name: '规格B', // 规格名称
  244. price: 119.9,
  245. stock: 5,
  246. image: 'child2.jpg',
  247. }
  248. rerender(
  249. <CartProvider>
  250. <TestSwitchSpecComponent cartItemId={6001} newChildGoods={newChildGoods} />
  251. </CartProvider>
  252. )
  253. // 验证规格已切换
  254. expect(getByTestId('items-count').textContent).toBe('1')
  255. expect(getByTestId('item-0-id').textContent).toBe('6002') // ID已更新
  256. expect(getByTestId('item-0-name').textContent).toBe('规格B') // 规格名称
  257. expect(getByTestId('item-0-price').textContent).toBe('119.9')
  258. expect(getByTestId('item-0-quantity').textContent).toBe('2') // 数量保持不变
  259. })
  260. it('切换规格时应该验证库存', () => {
  261. const childGoods: CartItem = {
  262. id: 7001,
  263. parentGoodsId: 7000,
  264. name: '测试商品 - 规格A',
  265. price: 50,
  266. image: 'test.jpg',
  267. stock: 10,
  268. quantity: 8, // 当前数量8
  269. }
  270. const TestSwitchSpecComponent = ({ cartItemId, newChildGoods }: {
  271. cartItemId?: number,
  272. newChildGoods?: { id: number; name: string; price: number; stock: number; image?: string }
  273. }) => {
  274. const cart = useCart()
  275. React.useEffect(() => {
  276. if (!cart.isLoading && cartItemId && newChildGoods) {
  277. cart.switchSpec(cartItemId, newChildGoods)
  278. }
  279. }, [cart.isLoading, cartItemId, newChildGoods])
  280. return <div data-testid="toast-called">{mockShowToast.mock.calls.length}</div>
  281. }
  282. // 添加商品到购物车
  283. const { getByTestId, rerender } = render(
  284. <CartProvider>
  285. <TestComponent action="add" item={childGoods} />
  286. </CartProvider>
  287. )
  288. // 尝试切换到库存不足的规格(库存只有5,但当前数量是8)
  289. const newChildGoods = {
  290. id: 7002,
  291. name: '测试商品 - 规格B',
  292. price: 60,
  293. stock: 5, // 库存不足
  294. image: 'test2.jpg',
  295. }
  296. rerender(
  297. <CartProvider>
  298. <TestSwitchSpecComponent cartItemId={7001} newChildGoods={newChildGoods} />
  299. </CartProvider>
  300. )
  301. // 应该显示库存不足提示
  302. expect(mockShowToast).toHaveBeenCalledWith(
  303. expect.objectContaining({ title: expect.stringContaining('库存不足') })
  304. )
  305. })
  306. it('单规格商品不应该支持切换规格', () => {
  307. const singleSpecGoods: CartItem = {
  308. id: 8001,
  309. parentGoodsId: 0, // 单规格商品
  310. name: '单规格商品',
  311. price: 30,
  312. image: 'single.jpg',
  313. stock: 10,
  314. quantity: 1,
  315. }
  316. const TestSwitchSpecComponent = ({ cartItemId, newChildGoods }: {
  317. cartItemId?: number,
  318. newChildGoods?: { id: number; name: string; price: number; stock: number; image?: string }
  319. }) => {
  320. const cart = useCart()
  321. React.useEffect(() => {
  322. if (!cart.isLoading && cartItemId && newChildGoods) {
  323. cart.switchSpec(cartItemId, newChildGoods)
  324. }
  325. }, [cart.isLoading, cartItemId, newChildGoods])
  326. return <div>Test</div>
  327. }
  328. // 添加单规格商品
  329. const { rerender } = render(
  330. <CartProvider>
  331. <TestComponent action="add" item={singleSpecGoods} />
  332. </CartProvider>
  333. )
  334. const newChildGoods = {
  335. id: 8002,
  336. name: '新规格',
  337. price: 40,
  338. stock: 5,
  339. }
  340. rerender(
  341. <CartProvider>
  342. <TestSwitchSpecComponent cartItemId={8001} newChildGoods={newChildGoods} />
  343. </CartProvider>
  344. )
  345. // 应该显示不支持切换的提示
  346. expect(mockShowToast).toHaveBeenCalledWith(
  347. expect.objectContaining({ title: '该商品不支持切换规格' })
  348. )
  349. })
  350. })