@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.
- package/CHANGELOG.md +8 -2
- package/app/components/_app/index.jsx +9 -7
- package/app/components/_app/index.test.js +2 -2
- package/app/components/_app-config/index.jsx +9 -3
- package/app/components/drawer-menu/drawer-menu.jsx +3 -1
- package/app/components/footer/index.jsx +3 -1
- package/app/components/header/index.jsx +3 -1
- package/app/components/header/index.test.js +2 -2
- package/app/components/island/README.md +1 -1
- package/app/components/island/index.jsx +3 -1
- package/app/components/island/index.test.js +94 -5
- package/app/components/item-variant/item-attributes.jsx +12 -3
- package/app/components/multiship/multiship-order-summary.jsx +137 -0
- package/app/components/multiship/multiship-order-summary.test.js +121 -0
- package/app/components/order-summary/index.jsx +2 -4
- package/app/components/pickup-or-delivery/index.jsx +80 -0
- package/app/components/pickup-or-delivery/index.test.jsx +182 -0
- package/app/components/product-item/index.jsx +26 -16
- package/app/components/product-item/index.test.js +29 -2
- package/app/components/product-item-list/index.jsx +10 -0
- package/app/components/product-item-list/index.test.jsx +14 -0
- package/app/components/product-view/index.jsx +9 -6
- package/app/components/product-view/index.test.js +25 -21
- package/app/components/quantity-picker/index.test.jsx +12 -12
- package/app/components/reset-password/index.test.js +1 -1
- package/app/components/shared/ui/AlertDescription/index.jsx +8 -0
- package/app/components/shared/ui/index.jsx +1 -0
- package/app/components/store-display/index.jsx +28 -4
- package/app/components/store-display/index.test.js +71 -0
- package/app/components/store-locator/form.test.jsx +16 -4
- package/app/components/store-locator/list.jsx +9 -4
- package/app/components/toggle-card/index.jsx +14 -0
- package/app/components/unavailable-product-confirmation-modal/index.jsx +19 -5
- package/app/components/unavailable-product-confirmation-modal/index.test.js +122 -1
- package/app/constants.js +20 -6
- package/app/contexts/store-locator-provider.jsx +7 -1
- package/app/contexts/store-locator-provider.test.jsx +36 -1
- package/app/hooks/use-address-form.js +155 -0
- package/app/hooks/use-address-form.test.js +501 -0
- package/app/hooks/use-auth-modal.js +2 -6
- package/app/hooks/use-current-basket.js +71 -2
- package/app/hooks/use-current-basket.test.js +37 -1
- package/app/hooks/use-dnt-notification.js +4 -4
- package/app/hooks/use-dnt-notification.test.js +5 -5
- package/app/hooks/use-item-shipment-management.js +233 -0
- package/app/hooks/use-item-shipment-management.test.js +696 -0
- package/app/hooks/use-multiship.js +589 -0
- package/app/hooks/use-multiship.test.js +776 -0
- package/app/hooks/use-pickup-shipment.js +70 -106
- package/app/hooks/use-pickup-shipment.test.js +345 -209
- package/app/hooks/use-product-address-assignment.js +280 -0
- package/app/hooks/use-product-address-assignment.test.js +414 -0
- package/app/hooks/use-product-inventory.js +100 -0
- package/app/hooks/use-product-inventory.test.js +254 -0
- package/app/hooks/use-shipment-operations.js +168 -0
- package/app/hooks/use-shipment-operations.test.js +385 -0
- package/app/hooks/use-store-locator.js +24 -2
- package/app/hooks/use-store-locator.test.jsx +109 -1
- package/app/pages/account/index.test.js +1 -1
- package/app/pages/account/profile.test.js +0 -2
- package/app/pages/cart/index.jsx +397 -157
- package/app/pages/cart/index.test.js +353 -2
- package/app/pages/cart/partials/bonus-products-title.jsx +10 -8
- package/app/pages/cart/partials/cart-secondary-button-group.test.js +1 -1
- package/app/pages/cart/partials/order-type-display.jsx +68 -0
- package/app/pages/cart/partials/order-type-display.test.js +241 -0
- package/app/pages/checkout/confirmation.jsx +79 -158
- package/app/pages/checkout/index.jsx +34 -9
- package/app/pages/checkout/index.test.js +245 -118
- package/app/pages/checkout/partials/contact-info.jsx +2 -6
- package/app/pages/checkout/partials/contact-info.test.js +93 -7
- package/app/pages/checkout/partials/payment.jsx +19 -5
- package/app/pages/checkout/partials/pickup-address.jsx +340 -70
- package/app/pages/checkout/partials/pickup-address.test.js +1075 -82
- package/app/pages/checkout/partials/product-shipping-address-card.jsx +382 -0
- package/app/pages/checkout/partials/shipment-details.jsx +209 -0
- package/app/pages/checkout/partials/shipment-details.test.js +246 -0
- package/app/pages/checkout/partials/shipping-address.jsx +156 -68
- package/app/pages/checkout/partials/shipping-address.test.js +673 -0
- package/app/pages/checkout/partials/shipping-method-options.jsx +180 -0
- package/app/pages/checkout/partials/shipping-methods.jsx +403 -0
- package/app/pages/checkout/partials/shipping-methods.test.js +472 -0
- package/app/pages/checkout/partials/shipping-multi-address.jsx +259 -0
- package/app/pages/checkout/partials/shipping-multi-address.test.js +2088 -0
- package/app/pages/checkout/partials/shipping-product-cards.jsx +101 -0
- package/app/pages/checkout/util/checkout-context.js +25 -18
- package/app/pages/login/index.jsx +2 -6
- package/app/pages/product-detail/index.jsx +96 -81
- package/app/pages/product-detail/index.test.js +103 -19
- package/app/pages/product-list/index.jsx +3 -1
- package/app/pages/product-list/partials/inventory-filter.jsx +18 -21
- package/app/pages/product-list/partials/inventory-filter.test.js +15 -17
- package/app/pages/product-list/partials/selected-refinements.jsx +3 -1
- package/app/ssr.js +1 -1
- package/app/static/translations/compiled/en-GB.json +316 -30
- package/app/static/translations/compiled/en-US.json +316 -30
- package/app/static/translations/compiled/en-XA.json +673 -75
- package/app/utils/address-utils.js +112 -0
- package/app/utils/address-utils.test.js +484 -0
- package/app/utils/product-utils.js +17 -5
- package/app/utils/product-utils.test.js +17 -8
- package/app/utils/sfdc-user-agent-utils.js +32 -0
- package/app/utils/sfdc-user-agent-utils.test.js +82 -0
- package/app/utils/shipment-utils.js +196 -0
- package/app/utils/shipment-utils.test.js +458 -0
- package/app/utils/test-utils.js +4 -4
- package/app/utils/utils.js +6 -1
- package/config/default.js +4 -1
- package/config/mocks/default.js +3 -1
- package/package.json +9 -9
- package/translations/en-GB.json +127 -10
- package/translations/en-US.json +127 -10
- 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
|
-
|
|
765
|
-
|
|
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 {
|
|
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
|
-
<
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|