@salesforce/retail-react-app 7.1.0-preview.1 → 8.0.0-dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/CHANGELOG.md +8 -2
  2. package/app/components/_app/index.jsx +9 -7
  3. package/app/components/_app/index.test.js +2 -2
  4. package/app/components/_app-config/index.jsx +9 -3
  5. package/app/components/drawer-menu/drawer-menu.jsx +3 -1
  6. package/app/components/footer/index.jsx +3 -1
  7. package/app/components/header/index.jsx +3 -1
  8. package/app/components/header/index.test.js +2 -2
  9. package/app/components/island/README.md +1 -1
  10. package/app/components/island/index.jsx +3 -1
  11. package/app/components/island/index.test.js +94 -5
  12. package/app/components/item-variant/item-attributes.jsx +12 -3
  13. package/app/components/multiship/multiship-order-summary.jsx +137 -0
  14. package/app/components/multiship/multiship-order-summary.test.js +121 -0
  15. package/app/components/order-summary/index.jsx +2 -4
  16. package/app/components/pickup-or-delivery/index.jsx +80 -0
  17. package/app/components/pickup-or-delivery/index.test.jsx +182 -0
  18. package/app/components/product-item/index.jsx +26 -16
  19. package/app/components/product-item/index.test.js +29 -2
  20. package/app/components/product-item-list/index.jsx +10 -0
  21. package/app/components/product-item-list/index.test.jsx +14 -0
  22. package/app/components/product-view/index.jsx +9 -6
  23. package/app/components/product-view/index.test.js +25 -21
  24. package/app/components/quantity-picker/index.test.jsx +12 -12
  25. package/app/components/reset-password/index.test.js +1 -1
  26. package/app/components/shared/ui/AlertDescription/index.jsx +8 -0
  27. package/app/components/shared/ui/index.jsx +1 -0
  28. package/app/components/store-display/index.jsx +28 -4
  29. package/app/components/store-display/index.test.js +71 -0
  30. package/app/components/store-locator/form.test.jsx +16 -4
  31. package/app/components/store-locator/list.jsx +9 -4
  32. package/app/components/toggle-card/index.jsx +14 -0
  33. package/app/components/unavailable-product-confirmation-modal/index.jsx +19 -5
  34. package/app/components/unavailable-product-confirmation-modal/index.test.js +122 -1
  35. package/app/constants.js +20 -6
  36. package/app/contexts/store-locator-provider.jsx +7 -1
  37. package/app/contexts/store-locator-provider.test.jsx +36 -1
  38. package/app/hooks/use-address-form.js +155 -0
  39. package/app/hooks/use-address-form.test.js +501 -0
  40. package/app/hooks/use-auth-modal.js +2 -6
  41. package/app/hooks/use-current-basket.js +71 -2
  42. package/app/hooks/use-current-basket.test.js +37 -1
  43. package/app/hooks/use-dnt-notification.js +4 -4
  44. package/app/hooks/use-dnt-notification.test.js +5 -5
  45. package/app/hooks/use-item-shipment-management.js +233 -0
  46. package/app/hooks/use-item-shipment-management.test.js +696 -0
  47. package/app/hooks/use-multiship.js +589 -0
  48. package/app/hooks/use-multiship.test.js +776 -0
  49. package/app/hooks/use-pickup-shipment.js +70 -106
  50. package/app/hooks/use-pickup-shipment.test.js +345 -209
  51. package/app/hooks/use-product-address-assignment.js +280 -0
  52. package/app/hooks/use-product-address-assignment.test.js +414 -0
  53. package/app/hooks/use-product-inventory.js +100 -0
  54. package/app/hooks/use-product-inventory.test.js +254 -0
  55. package/app/hooks/use-shipment-operations.js +168 -0
  56. package/app/hooks/use-shipment-operations.test.js +385 -0
  57. package/app/hooks/use-store-locator.js +24 -2
  58. package/app/hooks/use-store-locator.test.jsx +109 -1
  59. package/app/pages/account/index.test.js +1 -1
  60. package/app/pages/account/profile.test.js +0 -2
  61. package/app/pages/cart/index.jsx +397 -157
  62. package/app/pages/cart/index.test.js +353 -2
  63. package/app/pages/cart/partials/bonus-products-title.jsx +10 -8
  64. package/app/pages/cart/partials/cart-secondary-button-group.test.js +1 -1
  65. package/app/pages/cart/partials/order-type-display.jsx +68 -0
  66. package/app/pages/cart/partials/order-type-display.test.js +241 -0
  67. package/app/pages/checkout/confirmation.jsx +79 -158
  68. package/app/pages/checkout/index.jsx +34 -9
  69. package/app/pages/checkout/index.test.js +245 -118
  70. package/app/pages/checkout/partials/contact-info.jsx +2 -6
  71. package/app/pages/checkout/partials/contact-info.test.js +93 -7
  72. package/app/pages/checkout/partials/payment.jsx +19 -5
  73. package/app/pages/checkout/partials/pickup-address.jsx +340 -70
  74. package/app/pages/checkout/partials/pickup-address.test.js +1075 -82
  75. package/app/pages/checkout/partials/product-shipping-address-card.jsx +382 -0
  76. package/app/pages/checkout/partials/shipment-details.jsx +209 -0
  77. package/app/pages/checkout/partials/shipment-details.test.js +246 -0
  78. package/app/pages/checkout/partials/shipping-address.jsx +156 -68
  79. package/app/pages/checkout/partials/shipping-address.test.js +673 -0
  80. package/app/pages/checkout/partials/shipping-method-options.jsx +180 -0
  81. package/app/pages/checkout/partials/shipping-methods.jsx +403 -0
  82. package/app/pages/checkout/partials/shipping-methods.test.js +472 -0
  83. package/app/pages/checkout/partials/shipping-multi-address.jsx +259 -0
  84. package/app/pages/checkout/partials/shipping-multi-address.test.js +2088 -0
  85. package/app/pages/checkout/partials/shipping-product-cards.jsx +101 -0
  86. package/app/pages/checkout/util/checkout-context.js +25 -18
  87. package/app/pages/login/index.jsx +2 -6
  88. package/app/pages/product-detail/index.jsx +96 -81
  89. package/app/pages/product-detail/index.test.js +103 -19
  90. package/app/pages/product-list/index.jsx +3 -1
  91. package/app/pages/product-list/partials/inventory-filter.jsx +18 -21
  92. package/app/pages/product-list/partials/inventory-filter.test.js +15 -17
  93. package/app/pages/product-list/partials/selected-refinements.jsx +3 -1
  94. package/app/ssr.js +1 -1
  95. package/app/static/translations/compiled/en-GB.json +316 -30
  96. package/app/static/translations/compiled/en-US.json +316 -30
  97. package/app/static/translations/compiled/en-XA.json +673 -75
  98. package/app/utils/address-utils.js +112 -0
  99. package/app/utils/address-utils.test.js +484 -0
  100. package/app/utils/product-utils.js +17 -5
  101. package/app/utils/product-utils.test.js +17 -8
  102. package/app/utils/sfdc-user-agent-utils.js +32 -0
  103. package/app/utils/sfdc-user-agent-utils.test.js +82 -0
  104. package/app/utils/shipment-utils.js +196 -0
  105. package/app/utils/shipment-utils.test.js +458 -0
  106. package/app/utils/test-utils.js +4 -4
  107. package/app/utils/utils.js +6 -1
  108. package/config/default.js +4 -1
  109. package/config/mocks/default.js +3 -1
  110. package/package.json +9 -9
  111. package/translations/en-GB.json +127 -10
  112. package/translations/en-US.json +127 -10
  113. package/app/pages/checkout/partials/shipping-options.jsx +0 -269
@@ -44,6 +44,49 @@ jest.mock('@salesforce/retail-react-app/app/hooks/use-selected-store', () => ({
44
44
  useSelectedStore: () => mockUseSelectedStore()
45
45
  }))
46
46
 
47
+ // Mock useMultiship hook
48
+ const mockUseMultiship = {
49
+ updateDeliveryOption: jest.fn().mockResolvedValue(undefined),
50
+ updateShipmentsWithoutMethods: jest.fn().mockResolvedValue(undefined),
51
+ findOrCreatePickupShipment: jest.fn().mockResolvedValue({shipmentId: 'pickup-shipment-2'}),
52
+ moveItemsToPickupShipment: jest.fn().mockResolvedValue(undefined),
53
+ getItemsForShipment: jest.fn(() => [])
54
+ }
55
+ jest.mock('@salesforce/retail-react-app/app/hooks/use-multiship', () => ({
56
+ useMultiship: () => mockUseMultiship
57
+ }))
58
+
59
+ // Mock useStoreLocatorModal hook
60
+ const mockStoreLocatorModal = {
61
+ isOpen: false,
62
+ onOpen: jest.fn(),
63
+ onClose: jest.fn()
64
+ }
65
+ jest.mock('@salesforce/retail-react-app/app/hooks/use-store-locator', () => ({
66
+ useStoreLocatorModal: () => mockStoreLocatorModal
67
+ }))
68
+
69
+ // Mock getConfig to return test values
70
+ import mockConfig from '@salesforce/retail-react-app/config/mocks/default'
71
+ jest.mock('@salesforce/pwa-kit-runtime/utils/ssr-config', () => ({
72
+ getConfig: jest.fn(() => ({
73
+ ...mockConfig,
74
+ app: {
75
+ ...mockConfig.app,
76
+ storeLocatorEnabled: true,
77
+ multishipEnabled: true
78
+ }
79
+ }))
80
+ }))
81
+
82
+ jest.mock('@salesforce/retail-react-app/app/constants', () => {
83
+ const original = jest.requireActual('@salesforce/retail-react-app/app/constants')
84
+ return {
85
+ ...original,
86
+ STORE_LOCATOR_IS_ENABLED: true
87
+ }
88
+ })
89
+
47
90
  const mockProduct = {
48
91
  ...mockVariant,
49
92
  id: '750518699660M',
@@ -174,6 +217,12 @@ beforeEach(() => {
174
217
 
175
218
  rest.get('*/promotions', (req, res, ctx) => {
176
219
  return res(ctx.delay(0), ctx.status(200), ctx.json(mockPromotions))
220
+ }),
221
+ rest.get('*/shopper-stores/v1/organizations/:organizationId/stores', (req, res, ctx) => {
222
+ return res(ctx.delay(0), ctx.status(200), ctx.json({}))
223
+ }),
224
+ rest.patch('*/baskets/:basketId/items/:itemId', (req, res, ctx) => {
225
+ return res(ctx.delay(0), ctx.status(200), ctx.json({}))
177
226
  })
178
227
  )
179
228
  })
@@ -761,8 +810,10 @@ describe('Product bundles', () => {
761
810
  const quantityElement = screen.getByRole('spinbutton', {id: 'quantity'})
762
811
  expect(quantityElement).toBeInTheDocument()
763
812
  expect(quantityElement).toHaveValue('1')
764
- quantityElement.focus()
765
- fireEvent.change(quantityElement, {target: {value: '4'}})
813
+ act(() => {
814
+ quantityElement.focus()
815
+ fireEvent.change(quantityElement, {target: {value: '4'}})
816
+ })
766
817
 
767
818
  await waitFor(
768
819
  () => {
@@ -1259,6 +1310,196 @@ describe('Bonus products', () => {
1259
1310
  })
1260
1311
  })
1261
1312
 
1313
+ describe('Delivery options', () => {
1314
+ beforeEach(() => {
1315
+ jest.clearAllMocks()
1316
+ prependHandlersToServer([
1317
+ {path: '*/customers/:customerId/baskets', res: () => mockBaskets},
1318
+ {path: '*/products', res: () => mockProducts}
1319
+ ])
1320
+ mockUseMultiSite.mockReturnValue({
1321
+ site: {id: 'site-1'},
1322
+ buildUrl: (url) => url
1323
+ })
1324
+ mockUseSelectedStore.mockImplementation(() => ({
1325
+ selectedStore: null,
1326
+ isLoading: false,
1327
+ error: null,
1328
+ hasSelectedStore: false
1329
+ }))
1330
+ })
1331
+ test('should render delivery options for cart items', async () => {
1332
+ renderWithProviders(<Cart />)
1333
+ await waitFor(() => {
1334
+ expect(screen.getByTestId('sf-cart-container')).toBeInTheDocument()
1335
+ })
1336
+ const deliverySelects = await screen.findAllByTestId('delivery-option-select')
1337
+ expect(deliverySelects.length).toBeGreaterThan(0)
1338
+ })
1339
+ test('opens store locator modal when "Pick up at Store" is selected and no store is selected', async () => {
1340
+ renderWithProviders(<Cart />)
1341
+ await waitFor(() => {
1342
+ expect(screen.getByTestId('sf-cart-container')).toBeInTheDocument()
1343
+ })
1344
+ const deliverySelects = await screen.findAllByTestId('delivery-option-select')
1345
+ fireEvent.change(deliverySelects[0], {target: {value: 'pickup'}})
1346
+ expect(mockStoreLocatorModal.onOpen).toHaveBeenCalled()
1347
+ })
1348
+ test('should call handleDeliveryOptionChange when "Pick up at Store" is selected and a store is selected', async () => {
1349
+ const mockStore = {id: 'store-1', name: 'Test Store'}
1350
+ mockUseSelectedStore.mockImplementation(() => ({
1351
+ selectedStore: mockStore,
1352
+ hasSelectedStore: true
1353
+ }))
1354
+ renderWithProviders(<Cart />)
1355
+ await waitFor(() => {
1356
+ expect(screen.getByTestId('sf-cart-container')).toBeInTheDocument()
1357
+ })
1358
+ const deliverySelects = await screen.findAllByTestId('delivery-option-select')
1359
+ fireEvent.change(deliverySelects[0], {target: {value: 'pickup'}})
1360
+ expect(mockStoreLocatorModal.onOpen).not.toHaveBeenCalled()
1361
+ await waitFor(() => expect(mockUseMultiship.updateDeliveryOption).toHaveBeenCalled())
1362
+ const firstProductItem = mockBaskets.baskets[0].productItems[0]
1363
+ const productData = mockProducts.data.find((p) => p.id === firstProductItem.productId)
1364
+ expect(mockUseMultiship.updateDeliveryOption).toHaveBeenCalledWith(
1365
+ expect.objectContaining({productId: firstProductItem.productId}),
1366
+ true, // selectedPickup
1367
+ mockStore,
1368
+ productData.inventory.id
1369
+ )
1370
+ })
1371
+ test('should call handleDeliveryOptionChange when "Ship to Address" is selected', async () => {
1372
+ const basketWithPickup = {
1373
+ ...mockBaskets.baskets[0],
1374
+ productItems: [{...mockBaskets.baskets[0].productItems[0], shipmentId: 'bopis'}],
1375
+ shipments: [
1376
+ ...mockBaskets.baskets[0].shipments,
1377
+ {
1378
+ shipmentId: 'bopis',
1379
+ shippingMethod: {id: 'pickup-method', c_storePickupEnabled: true},
1380
+ c_fromStoreId: 'store-1'
1381
+ }
1382
+ ]
1383
+ }
1384
+ prependHandlersToServer([
1385
+ {
1386
+ path: '*/customers/:customerId/baskets',
1387
+ res: () => ({baskets: [basketWithPickup], total: 1})
1388
+ },
1389
+ {path: '*/products', res: () => mockProducts}
1390
+ ])
1391
+ renderWithProviders(<Cart />)
1392
+ await waitFor(() => {
1393
+ expect(screen.getByTestId('sf-cart-container')).toBeInTheDocument()
1394
+ })
1395
+ const deliverySelects = await screen.findAllByTestId('delivery-option-select')
1396
+ await userEvent.selectOptions(deliverySelects[0], 'delivery')
1397
+ expect(mockStoreLocatorModal.onOpen).not.toHaveBeenCalled()
1398
+ expect(mockUseMultiship.updateDeliveryOption).toHaveBeenCalled()
1399
+ const firstProductItem = basketWithPickup.productItems[0]
1400
+ const productData = mockProducts.data.find((p) => p.id === firstProductItem.productId)
1401
+ expect(mockUseMultiship.updateDeliveryOption).toHaveBeenCalledWith(
1402
+ expect.objectContaining({productId: firstProductItem.productId}),
1403
+ false, // selectedPickup
1404
+ null,
1405
+ productData.inventory.id
1406
+ )
1407
+ })
1408
+
1409
+ test('disables "Ship to Address" when item is for pickup and out of stock for shipping', async () => {
1410
+ const mockProductWithNoDefaultInventory = {
1411
+ ...mockProducts.data[0],
1412
+ id: 'product-out-of-stock-ship',
1413
+ inventory: {
1414
+ ...mockProducts.data[0].inventory,
1415
+ stockLevel: 0
1416
+ }
1417
+ }
1418
+
1419
+ const basketWithPickup = {
1420
+ ...mockBaskets.baskets[0],
1421
+ productItems: [
1422
+ {
1423
+ ...mockBaskets.baskets[0].productItems[0],
1424
+ productId: 'product-out-of-stock-ship',
1425
+ quantity: 1,
1426
+ shipmentId: 'bopis'
1427
+ }
1428
+ ],
1429
+ shipments: [
1430
+ ...mockBaskets.baskets[0].shipments,
1431
+ {
1432
+ shipmentId: 'bopis',
1433
+ shippingMethod: {id: 'pickup-method', c_storePickupEnabled: true},
1434
+ c_fromStoreId: 'store-1'
1435
+ }
1436
+ ]
1437
+ }
1438
+ prependHandlersToServer([
1439
+ {
1440
+ path: '*/customers/:customerId/baskets',
1441
+ res: () => ({baskets: [basketWithPickup], total: 1})
1442
+ },
1443
+ {path: '*/products', res: () => ({data: [mockProductWithNoDefaultInventory]})}
1444
+ ])
1445
+ renderWithProviders(<Cart />)
1446
+ await waitFor(() => {
1447
+ expect(screen.getByTestId('sf-cart-container')).toBeInTheDocument()
1448
+ })
1449
+ const deliverySelects = await screen.findAllByTestId('delivery-option-select')
1450
+ const shipOption = deliverySelects[0].querySelector('option[value="delivery"]')
1451
+ const pickupOption = deliverySelects[0].querySelector('option[value="pickup"]')
1452
+ expect(shipOption).toBeDisabled()
1453
+ expect(pickupOption).toBeEnabled()
1454
+ })
1455
+
1456
+ test('disables "Pick up at Store" when item is for shipping and out of stock for pickup', async () => {
1457
+ const selectedStoreId = 'store-1'
1458
+ const selectedInventoryId = 'inventory-1'
1459
+ const mockProductWithNoPickupInventory = {
1460
+ ...mockProducts.data[0],
1461
+ id: 'product-out-of-stock-pickup',
1462
+ inventories: [
1463
+ {
1464
+ id: selectedInventoryId,
1465
+ stockLevel: 0
1466
+ }
1467
+ ]
1468
+ }
1469
+ const basketForShipping = {
1470
+ ...mockBaskets.baskets[0],
1471
+ productItems: [
1472
+ {
1473
+ ...mockBaskets.baskets[0].productItems[0],
1474
+ productId: 'product-out-of-stock-pickup',
1475
+ quantity: 1,
1476
+ shipmentId: 'me'
1477
+ }
1478
+ ]
1479
+ }
1480
+ prependHandlersToServer([
1481
+ {
1482
+ path: '*/customers/:customerId/baskets',
1483
+ res: () => ({baskets: [basketForShipping], total: 1})
1484
+ },
1485
+ {path: '*/products', res: () => ({data: [mockProductWithNoPickupInventory]})}
1486
+ ])
1487
+ mockUseSelectedStore.mockImplementation(() => ({
1488
+ selectedStore: {id: selectedStoreId, inventoryId: selectedInventoryId},
1489
+ hasSelectedStore: true
1490
+ }))
1491
+ renderWithProviders(<Cart />)
1492
+ await waitFor(() => {
1493
+ expect(screen.getByTestId('sf-cart-container')).toBeInTheDocument()
1494
+ })
1495
+ const deliverySelects = await screen.findAllByTestId('delivery-option-select')
1496
+ const shipOption = deliverySelects[0].querySelector('option[value="delivery"]')
1497
+ const pickupOption = deliverySelects[0].querySelector('option[value="pickup"]')
1498
+ expect(shipOption).toBeEnabled()
1499
+ expect(pickupOption).toBeDisabled()
1500
+ })
1501
+ })
1502
+
1262
1503
  describe('Unavailable products tests', function () {
1263
1504
  test('Remove unavailable/out of stock/low stock products from cart', async () => {
1264
1505
  prependHandlersToServer([
@@ -1414,3 +1655,113 @@ describe('Selected inventory ID tests', function () {
1414
1655
  })
1415
1656
  })
1416
1657
  })
1658
+
1659
+ describe('Change store for pickup shipment', () => {
1660
+ const mockProduct = {id: 'product-1', name: 'Test Product'}
1661
+ const mockStore1 = {id: 'store-1', name: 'Old Store'}
1662
+ const mockStore2 = {id: 'store-2', name: 'New Store', inventoryId: 'inventory-2'}
1663
+ const mockBasketWithPickup = {
1664
+ basketId: 'basket-1',
1665
+ currency: 'USD',
1666
+ productItems: [
1667
+ {
1668
+ productId: 'product-1',
1669
+ productName: 'Test Product',
1670
+ itemId: 'item-1',
1671
+ quantity: 1,
1672
+ price: 10,
1673
+ shipmentId: 'pickup-shipment-1',
1674
+ inventoryId: mockStore2.inventoryId
1675
+ }
1676
+ ],
1677
+ shipments: [
1678
+ {
1679
+ shipmentId: 'pickup-shipment-1',
1680
+ shippingMethod: {
1681
+ id: 'pickup-method-1',
1682
+ c_storePickupEnabled: true
1683
+ },
1684
+ c_fromStoreId: 'store-1'
1685
+ }
1686
+ ],
1687
+ orderTotal: 10,
1688
+ productSubTotal: 10,
1689
+ taxTotal: 0
1690
+ }
1691
+
1692
+ beforeEach(() => {
1693
+ jest.clearAllMocks()
1694
+ const mockProductWithInventory = {
1695
+ ...mockProduct,
1696
+ inventories: [{id: mockStore2.inventoryId, stockLevel: 10}]
1697
+ }
1698
+ mockUseMultiship.getItemsForShipment.mockReturnValue(mockBasketWithPickup.productItems)
1699
+
1700
+ // Mock selectedStore to be mockStore2 for the change store functionality
1701
+ mockUseSelectedStore.mockImplementation(() => ({
1702
+ selectedStore: mockStore2,
1703
+ isLoading: false,
1704
+ error: null,
1705
+ hasSelectedStore: true
1706
+ }))
1707
+
1708
+ global.server.use(
1709
+ rest.get('*/customers/:customerId/baskets', (req, res, ctx) => {
1710
+ return res(ctx.delay(0), ctx.json({baskets: [mockBasketWithPickup], total: 1}))
1711
+ }),
1712
+ rest.get('*/products', (req, res, ctx) => {
1713
+ return res(ctx.delay(0), ctx.json({data: [mockProductWithInventory]}))
1714
+ }),
1715
+ rest.get('*/stores', (req, res, ctx) => {
1716
+ return res(ctx.delay(0), ctx.json({data: [mockStore1]}))
1717
+ })
1718
+ )
1719
+ })
1720
+
1721
+ test('should move items to new pickup shipment when store is changed via modal', async () => {
1722
+ renderWithProviders(<Cart />)
1723
+
1724
+ await waitFor(() => {
1725
+ expect(screen.getByTestId('change-store-button')).toBeInTheDocument()
1726
+ })
1727
+
1728
+ // Simulate clicking "Change Store"
1729
+ fireEvent.click(screen.getByTestId('change-store-button'))
1730
+
1731
+ // Verify that the shipment is updated with the new store.
1732
+ await waitFor(() => {
1733
+ expect(mockUseMultiship.findOrCreatePickupShipment).toHaveBeenCalledWith(mockStore2)
1734
+ const mockProductItem = mockBasketWithPickup.productItems[0]
1735
+ expect(mockUseMultiship.moveItemsToPickupShipment).toHaveBeenCalledWith(
1736
+ [expect.objectContaining({itemId: mockProductItem.itemId})],
1737
+ 'pickup-shipment-2',
1738
+ mockStore2.inventoryId
1739
+ )
1740
+ })
1741
+ })
1742
+
1743
+ test('should show error toast when moving items fails', async () => {
1744
+ // Suppress console.error for this test
1745
+ jest.spyOn(console, 'error').mockImplementation(jest.fn())
1746
+
1747
+ mockUseMultiship.moveItemsToPickupShipment.mockRejectedValue(new Error('Update failed'))
1748
+
1749
+ renderWithProviders(<Cart />)
1750
+
1751
+ await waitFor(() => {
1752
+ expect(screen.getByTestId('change-store-button')).toBeInTheDocument()
1753
+ })
1754
+
1755
+ // Simulate clicking "Change Store"
1756
+ fireEvent.click(screen.getByTestId('change-store-button'))
1757
+
1758
+ // Verify that an error toast is shown.
1759
+ await waitFor(() => {
1760
+ expect(mockUseMultiship.findOrCreatePickupShipment).toHaveBeenCalledWith(mockStore2)
1761
+ expect(screen.getByText(/something went wrong/i)).toBeInTheDocument()
1762
+ })
1763
+
1764
+ // Restore console.error
1765
+ console.error.mockRestore()
1766
+ })
1767
+ })
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import React from 'react'
8
8
  import {FormattedMessage} from 'react-intl'
9
- import {Heading} from '@salesforce/retail-react-app/app/components/shared/ui'
9
+ import {Box, Text} from '@salesforce/retail-react-app/app/components/shared/ui'
10
10
  import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket'
11
11
 
12
12
  const BonusProductsTitle = () => {
@@ -15,13 +15,15 @@ const BonusProductsTitle = () => {
15
15
  basket?.productItems?.filter((item) => item.bonusProductLineItem)?.length || 0
16
16
 
17
17
  return (
18
- <Heading as="h2" fontSize="xl">
19
- <FormattedMessage
20
- defaultMessage="Bonus Products ({itemCount, plural, =0 {0 items} one {# item} other {# items}})"
21
- values={{itemCount: bonusItemsCount}}
22
- id="bonus_products_title.title.num_of_items"
23
- />
24
- </Heading>
18
+ <Box layerStyle="cardBordered" p={3}>
19
+ <Text fontWeight="bold">
20
+ <FormattedMessage
21
+ defaultMessage="Bonus Products ({itemCount, plural, =0 {0 items} one {# item} other {# items}})"
22
+ values={{itemCount: bonusItemsCount}}
23
+ id="bonus_products_title.title.num_of_items"
24
+ />
25
+ </Text>
26
+ </Box>
25
27
  )
26
28
  }
27
29
 
@@ -171,7 +171,7 @@ describe('CartSecondaryButtonGroup Edit button conditional rendering', () => {
171
171
  })
172
172
 
173
173
  test('hides remove, wishlist, edit button and gift checkbox for bonus product', async () => {
174
- const {user} = renderWithProviders(<MockedComponent isBonusProduct={true} />)
174
+ renderWithProviders(<MockedComponent isBonusProduct={true} />)
175
175
 
176
176
  expect(screen.queryByRole('button', {name: /edit/i})).not.toBeInTheDocument()
177
177
  expect(screen.queryByRole('button', {name: /remove/i})).not.toBeInTheDocument()
@@ -0,0 +1,68 @@
1
+ /*
2
+ * Copyright (c) 2023, Salesforce, Inc.
3
+ * All rights reserved.
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6
+ */
7
+ import React from 'react'
8
+ import PropTypes from 'prop-types'
9
+ import {FormattedMessage} from 'react-intl'
10
+ import {Box, Text} from '@salesforce/retail-react-app/app/components/shared/ui'
11
+ import StoreDisplay from '@salesforce/retail-react-app/app/components/store-display'
12
+
13
+ const OrderTypeDisplay = ({
14
+ isPickupOrder,
15
+ store,
16
+ itemsInShipment,
17
+ totalItemsInCart,
18
+ onChangeStore
19
+ }) => {
20
+ return (
21
+ <Box>
22
+ {isPickupOrder ? (
23
+ <Box>
24
+ <Text fontWeight="bold" mb={2}>
25
+ <FormattedMessage
26
+ defaultMessage="Pick Up in Store - {itemsInShipment} out of {totalItemsInCart} items"
27
+ id="cart.order_type.pickup_in_store"
28
+ values={{
29
+ itemsInShipment,
30
+ totalItemsInCart
31
+ }}
32
+ />
33
+ </Text>
34
+ <Box layerStyle="cardBordered" p={4} borderRadius="md">
35
+ <StoreDisplay
36
+ store={store}
37
+ showDistance={true}
38
+ textSize="sm"
39
+ nameStyle={{fontSize: 'sm', fontWeight: 'semibold'}}
40
+ onChangeStore={onChangeStore}
41
+ />
42
+ </Box>
43
+ </Box>
44
+ ) : (
45
+ <Text fontWeight="bold">
46
+ <FormattedMessage
47
+ defaultMessage="Delivery - {itemsInShipment} out of {totalItemsInCart} items"
48
+ id="cart.order_type.delivery"
49
+ values={{
50
+ itemsInShipment,
51
+ totalItemsInCart
52
+ }}
53
+ />
54
+ </Text>
55
+ )}
56
+ </Box>
57
+ )
58
+ }
59
+
60
+ OrderTypeDisplay.propTypes = {
61
+ isPickupOrder: PropTypes.bool.isRequired,
62
+ store: PropTypes.object,
63
+ itemsInShipment: PropTypes.number.isRequired,
64
+ totalItemsInCart: PropTypes.number.isRequired,
65
+ onChangeStore: PropTypes.func
66
+ }
67
+
68
+ export default OrderTypeDisplay