@salesforce/retail-react-app 8.1.0 → 8.2.0-nightly-20250929080228
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 +2 -0
- package/app/pages/cart/index.jsx +0 -3
- package/app/pages/checkout/index.test.js +2 -1
- package/app/pages/checkout/partials/shipping-methods.jsx +28 -0
- package/app/pages/checkout/partials/shipping-methods.test.js +163 -1
- package/app/utils/bonus-product/cart.test.js +0 -2
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
## v8.2.0-dev (Sep 26, 2025)
|
|
2
|
+
|
|
1
3
|
## v8.1.0 (Sep 25, 2025)
|
|
2
4
|
- Updated search UX - prices, images, suggestions new layout [#3271](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3271)
|
|
3
5
|
- Updated the UI for StoreDisplay component which displays pickup in-store information on different pages. [#3248](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3248)
|
package/app/pages/cart/index.jsx
CHANGED
|
@@ -1146,7 +1146,6 @@ const Cart = () => {
|
|
|
1146
1146
|
/>
|
|
1147
1147
|
)
|
|
1148
1148
|
)}
|
|
1149
|
-
|
|
1150
1149
|
{/* Render grouped bonus products from this shipment */}
|
|
1151
1150
|
{shipmentInfo.categorizedProducts.bonusProducts
|
|
1152
1151
|
?.filter(
|
|
@@ -1191,7 +1190,6 @@ const Cart = () => {
|
|
|
1191
1190
|
}}
|
|
1192
1191
|
/>
|
|
1193
1192
|
))}
|
|
1194
|
-
|
|
1195
1193
|
{/* Render SelectBonusProductsCard for each bonusDiscountLineItem */}
|
|
1196
1194
|
{basket.bonusDiscountLineItems?.map(
|
|
1197
1195
|
(bonusDiscountLineItem) => {
|
|
@@ -1241,7 +1239,6 @@ const Cart = () => {
|
|
|
1241
1239
|
)
|
|
1242
1240
|
}
|
|
1243
1241
|
)}
|
|
1244
|
-
|
|
1245
1242
|
{/* Render orphaned bonus products (bonus products without bonusDiscountLineItemId) */}
|
|
1246
1243
|
{(() => {
|
|
1247
1244
|
const orphanedBonusProducts =
|
|
@@ -552,7 +552,8 @@ test('Can edit address during checkout as a registered customer', async () => {
|
|
|
552
552
|
expect(screen.getByTestId('sf-toggle-card-step-2-content')).not.toBeEmptyDOMElement()
|
|
553
553
|
})
|
|
554
554
|
|
|
555
|
-
|
|
555
|
+
const shippingAddressCard = screen.getByTestId('sf-toggle-card-step-1-content')
|
|
556
|
+
expect(within(shippingAddressCard).getByText('369 Main Street')).toBeInTheDocument()
|
|
556
557
|
})
|
|
557
558
|
|
|
558
559
|
test('Can add address during checkout as a registered customer', async () => {
|
|
@@ -192,6 +192,34 @@ export default function ShippingMethods() {
|
|
|
192
192
|
)
|
|
193
193
|
if (hasNewFields) {
|
|
194
194
|
form.reset(newDefaults)
|
|
195
|
+
deliveryShipments.forEach(async (shipment) => {
|
|
196
|
+
const methodId = newDefaults[`shippingMethodId_${shipment.shipmentId}`]
|
|
197
|
+
const hasMethodInBasket = shipment.shippingMethod && shipment.shippingMethod.id
|
|
198
|
+
|
|
199
|
+
// auto-submit if;
|
|
200
|
+
// - default method to submit present
|
|
201
|
+
// - the shipment doesn't already have a method in basket
|
|
202
|
+
// - user hasn't manually selected
|
|
203
|
+
if (
|
|
204
|
+
methodId &&
|
|
205
|
+
!hasMethodInBasket &&
|
|
206
|
+
methodId === shippingMethods?.defaultShippingMethodId
|
|
207
|
+
) {
|
|
208
|
+
try {
|
|
209
|
+
await updateShippingMethod.mutateAsync({
|
|
210
|
+
parameters: {
|
|
211
|
+
basketId: basket.basketId,
|
|
212
|
+
shipmentId: shipment.shipmentId
|
|
213
|
+
},
|
|
214
|
+
body: {
|
|
215
|
+
id: methodId
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.warn(error)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
})
|
|
195
223
|
}
|
|
196
224
|
}, [deliveryShipments.length, shippingMethods?.defaultShippingMethodId])
|
|
197
225
|
|
|
@@ -12,7 +12,11 @@ import ShippingMethods from '@salesforce/retail-react-app/app/pages/checkout/par
|
|
|
12
12
|
import {useCheckout} from '@salesforce/retail-react-app/app/pages/checkout/util/checkout-context'
|
|
13
13
|
import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket'
|
|
14
14
|
import {useCurrency} from '@salesforce/retail-react-app/app/hooks'
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
useShippingMethodsForShipment,
|
|
17
|
+
useProducts,
|
|
18
|
+
useShopperBasketsMutation
|
|
19
|
+
} from '@salesforce/commerce-sdk-react'
|
|
16
20
|
|
|
17
21
|
// Mock the hooks
|
|
18
22
|
jest.mock('@salesforce/retail-react-app/app/pages/checkout/util/checkout-context')
|
|
@@ -25,6 +29,7 @@ const mockUseCurrentBasket = useCurrentBasket
|
|
|
25
29
|
const mockUseCurrency = useCurrency
|
|
26
30
|
const mockUseShippingMethodsForShipment = useShippingMethodsForShipment
|
|
27
31
|
const mockUseProducts = useProducts
|
|
32
|
+
const mockUseShopperBasketsMutation = useShopperBasketsMutation
|
|
28
33
|
|
|
29
34
|
// Mock data
|
|
30
35
|
const mockBasket = {
|
|
@@ -169,6 +174,7 @@ describe('ShippingMethods', () => {
|
|
|
169
174
|
data: mockProductsMap,
|
|
170
175
|
isLoading: false
|
|
171
176
|
})
|
|
177
|
+
mockUseShopperBasketsMutation.mockReturnValue(jest.fn().mockResolvedValue({}))
|
|
172
178
|
})
|
|
173
179
|
|
|
174
180
|
afterEach(() => {
|
|
@@ -538,4 +544,160 @@ describe('ShippingMethods', () => {
|
|
|
538
544
|
expect(screen.getByText('Standard Shipping')).toBeInTheDocument()
|
|
539
545
|
})
|
|
540
546
|
})
|
|
547
|
+
|
|
548
|
+
describe('auto-submit functionality', () => {
|
|
549
|
+
test('should auto-submit default shipping method when available', async () => {
|
|
550
|
+
const basketWithoutMethods = {
|
|
551
|
+
...mockBasket,
|
|
552
|
+
shipments: [
|
|
553
|
+
{
|
|
554
|
+
...mockBasket.shipments[0],
|
|
555
|
+
shippingMethod: null
|
|
556
|
+
}
|
|
557
|
+
]
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const mockShippingMethods = {
|
|
561
|
+
defaultShippingMethodId: 'default-shipping-method',
|
|
562
|
+
applicableShippingMethods: [
|
|
563
|
+
{
|
|
564
|
+
id: 'default-shipping-method',
|
|
565
|
+
name: 'Default Shipping'
|
|
566
|
+
}
|
|
567
|
+
]
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const mockMutateAsync = jest.fn().mockResolvedValue({})
|
|
571
|
+
mockUseShopperBasketsMutation.mockReturnValue({
|
|
572
|
+
updateShippingMethod: {mutateAsync: mockMutateAsync}
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
// after auto-submit, step should advance to PAYMENT (summary mode)
|
|
576
|
+
mockUseCheckout.mockReturnValue({
|
|
577
|
+
step: 'PAYMENT',
|
|
578
|
+
STEPS: {SHIPPING_OPTIONS: 'SHIPPING_OPTIONS', PAYMENT: 'PAYMENT'},
|
|
579
|
+
goToStep: jest.fn(),
|
|
580
|
+
goToNextStep: jest.fn()
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
mockUseCurrentBasket.mockReturnValue({
|
|
584
|
+
data: basketWithoutMethods,
|
|
585
|
+
derivedData: {
|
|
586
|
+
totalShippingCost: 5.99,
|
|
587
|
+
isMissingShippingMethod: false
|
|
588
|
+
},
|
|
589
|
+
isLoading: false
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
mockUseShippingMethodsForShipment.mockReturnValue({
|
|
593
|
+
data: mockShippingMethods,
|
|
594
|
+
isLoading: false
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
renderWithIntl(<ShippingMethods />)
|
|
598
|
+
|
|
599
|
+
// component is in SUMMARY mode (collapsed) after auto-submit
|
|
600
|
+
expect(screen.getByRole('button', {name: 'Edit Shipping Options'})).toBeInTheDocument()
|
|
601
|
+
expect(
|
|
602
|
+
screen.queryByRole('radio', {name: 'Default Shipping $5.99'})
|
|
603
|
+
).not.toBeInTheDocument()
|
|
604
|
+
expect(
|
|
605
|
+
screen.queryByRole('button', {name: 'Continue to Payment'})
|
|
606
|
+
).not.toBeInTheDocument()
|
|
607
|
+
})
|
|
608
|
+
|
|
609
|
+
test('should not auto-submit if shipment already has a method', async () => {
|
|
610
|
+
// Mock basket that already has a shipping method
|
|
611
|
+
const basketWithMethod = {
|
|
612
|
+
...mockBasket,
|
|
613
|
+
shipments: [
|
|
614
|
+
{
|
|
615
|
+
...mockBasket.shipments[0],
|
|
616
|
+
shippingMethod: {
|
|
617
|
+
id: 'existing-method',
|
|
618
|
+
name: 'Existing Shipping'
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
]
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const mockUpdateShippingMethod = jest.fn().mockResolvedValue({})
|
|
625
|
+
mockUpdateShippingMethod.mutateAsync = jest.fn().mockResolvedValue({})
|
|
626
|
+
|
|
627
|
+
// Mock the mutation hook
|
|
628
|
+
mockUseShopperBasketsMutation.mockReturnValue(mockUpdateShippingMethod)
|
|
629
|
+
|
|
630
|
+
mockUseCurrentBasket.mockReturnValue({
|
|
631
|
+
data: basketWithMethod,
|
|
632
|
+
derivedData: {totalShippingCost: 0},
|
|
633
|
+
isLoading: false
|
|
634
|
+
})
|
|
635
|
+
|
|
636
|
+
mockUseShippingMethodsForShipment.mockReturnValue({
|
|
637
|
+
data: {
|
|
638
|
+
defaultShippingMethodId: 'default-method',
|
|
639
|
+
applicableShippingMethods: []
|
|
640
|
+
},
|
|
641
|
+
isLoading: false
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
renderWithIntl(<ShippingMethods />)
|
|
645
|
+
|
|
646
|
+
// no auto-submit happens
|
|
647
|
+
await waitFor(() => {
|
|
648
|
+
expect(mockUpdateShippingMethod).not.toHaveBeenCalled()
|
|
649
|
+
})
|
|
650
|
+
})
|
|
651
|
+
|
|
652
|
+
test('should not auto-submit if user has manually selected a different method', async () => {
|
|
653
|
+
const basketWithoutMethods = {
|
|
654
|
+
...mockBasket,
|
|
655
|
+
shipments: [
|
|
656
|
+
{
|
|
657
|
+
...mockBasket.shipments[0],
|
|
658
|
+
shippingMethod: null
|
|
659
|
+
}
|
|
660
|
+
]
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Mock shipping methods with default
|
|
664
|
+
const mockShippingMethods = {
|
|
665
|
+
defaultShippingMethodId: 'default-method',
|
|
666
|
+
applicableShippingMethods: [
|
|
667
|
+
{
|
|
668
|
+
id: 'default-method',
|
|
669
|
+
name: 'Default Shipping'
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
id: 'user-selected-method',
|
|
673
|
+
name: 'User Selected Shipping'
|
|
674
|
+
}
|
|
675
|
+
]
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const mockUpdateShippingMethod = jest.fn().mockResolvedValue({})
|
|
679
|
+
mockUpdateShippingMethod.mutateAsync = jest.fn().mockResolvedValue({})
|
|
680
|
+
|
|
681
|
+
// Mock the mutation hook
|
|
682
|
+
mockUseShopperBasketsMutation.mockReturnValue(mockUpdateShippingMethod)
|
|
683
|
+
|
|
684
|
+
mockUseCurrentBasket.mockReturnValue({
|
|
685
|
+
data: basketWithoutMethods,
|
|
686
|
+
derivedData: {totalShippingCost: 0},
|
|
687
|
+
isLoading: false
|
|
688
|
+
})
|
|
689
|
+
|
|
690
|
+
mockUseShippingMethodsForShipment.mockReturnValue({
|
|
691
|
+
data: mockShippingMethods,
|
|
692
|
+
isLoading: false
|
|
693
|
+
})
|
|
694
|
+
|
|
695
|
+
renderWithIntl(<ShippingMethods />)
|
|
696
|
+
|
|
697
|
+
// no auto-submit happens because the form would have user-selected-method, not default-method)
|
|
698
|
+
await waitFor(() => {
|
|
699
|
+
expect(mockUpdateShippingMethod).not.toHaveBeenCalled()
|
|
700
|
+
})
|
|
701
|
+
})
|
|
702
|
+
})
|
|
541
703
|
})
|
|
@@ -58,7 +58,6 @@ describe('Bonus Product Cart Utilities', () => {
|
|
|
58
58
|
expect(result[0].quantity).toBe(2)
|
|
59
59
|
})
|
|
60
60
|
})
|
|
61
|
-
|
|
62
61
|
describe('getBonusProductsForSpecificCartItem', () => {
|
|
63
62
|
const extendedBasket = {
|
|
64
63
|
bonusDiscountLineItems: [
|
|
@@ -1543,7 +1542,6 @@ describe('Bonus Product Cart Utilities', () => {
|
|
|
1543
1542
|
})
|
|
1544
1543
|
})
|
|
1545
1544
|
})
|
|
1546
|
-
|
|
1547
1545
|
describe('findAllBonusProductItemsToRemove', () => {
|
|
1548
1546
|
test('finds all bonus products with same productId and promotionId', () => {
|
|
1549
1547
|
const targetBonusProduct = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/retail-react-app",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.2.0-nightly-20250929080228",
|
|
4
4
|
"license": "See license in LICENSE",
|
|
5
5
|
"author": "cc-pwa-kit@salesforce.com",
|
|
6
6
|
"ccExtensibility": {
|
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
"@loadable/component": "^5.15.3",
|
|
47
47
|
"@peculiar/webcrypto": "^1.4.2",
|
|
48
48
|
"@salesforce/cc-datacloud-typescript": "1.1.2",
|
|
49
|
-
"@salesforce/commerce-sdk-react": "4.
|
|
50
|
-
"@salesforce/pwa-kit-dev": "3.
|
|
51
|
-
"@salesforce/pwa-kit-react-sdk": "3.
|
|
52
|
-
"@salesforce/pwa-kit-runtime": "3.
|
|
49
|
+
"@salesforce/commerce-sdk-react": "4.2.0-nightly-20250929080228",
|
|
50
|
+
"@salesforce/pwa-kit-dev": "3.14.0-nightly-20250929080228",
|
|
51
|
+
"@salesforce/pwa-kit-react-sdk": "3.14.0-nightly-20250929080228",
|
|
52
|
+
"@salesforce/pwa-kit-runtime": "3.14.0-nightly-20250929080228",
|
|
53
53
|
"@tanstack/react-query": "^4.28.0",
|
|
54
54
|
"@tanstack/react-query-devtools": "^4.29.1",
|
|
55
55
|
"@testing-library/dom": "^9.0.1",
|
|
@@ -107,5 +107,5 @@
|
|
|
107
107
|
"maxSize": "335 kB"
|
|
108
108
|
}
|
|
109
109
|
],
|
|
110
|
-
"gitHead": "
|
|
110
|
+
"gitHead": "92d2291c0f1ffeccd9e43cc0e40eb9d8bca643e7"
|
|
111
111
|
}
|