@salesforce/retail-react-app 8.1.0 → 8.2.0-nightly-20250930080219
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/components/shopper-agent/index.jsx +6 -2
- package/app/components/shopper-agent/index.test.js +90 -49
- 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)
|
|
@@ -259,6 +259,11 @@ const ShopperAgentWindow = ({commerceAgentConfiguration, domainUrl}) => {
|
|
|
259
259
|
conversationContext
|
|
260
260
|
})
|
|
261
261
|
}
|
|
262
|
+
} else if (event.data.type === 'lwc.getDomainUrl') {
|
|
263
|
+
// Handle domain URL request
|
|
264
|
+
sendConversationContext('conversational.domainUrl', {
|
|
265
|
+
domainUrl
|
|
266
|
+
})
|
|
262
267
|
}
|
|
263
268
|
} catch (error) {
|
|
264
269
|
console.error('Error handling Miaw event:', error)
|
|
@@ -421,7 +426,6 @@ const ShopperAgent = ({commerceAgentConfiguration, basketDoneLoading}) => {
|
|
|
421
426
|
const {enabled} = commerceAgentConfiguration
|
|
422
427
|
|
|
423
428
|
// Get current location and app origin for domain URL
|
|
424
|
-
const location = useLocation()
|
|
425
429
|
const appOrigin = useAppOrigin()
|
|
426
430
|
const {buildUrl} = useMultiSite()
|
|
427
431
|
|
|
@@ -429,7 +433,7 @@ const ShopperAgent = ({commerceAgentConfiguration, basketDoneLoading}) => {
|
|
|
429
433
|
const isShopperAgentEnabled = isEnabled(enabled)
|
|
430
434
|
|
|
431
435
|
// Build the current domain URL
|
|
432
|
-
const domainUrl = `${appOrigin}${buildUrl(
|
|
436
|
+
const domainUrl = `${appOrigin}${buildUrl('')}`
|
|
433
437
|
|
|
434
438
|
// Conditional rendering: only render when all conditions are met
|
|
435
439
|
// 1. Agent is enabled and running on client
|
|
@@ -139,7 +139,7 @@ describe('ShopperAgent Component', () => {
|
|
|
139
139
|
// Mock useMultiSite hook with proper structure
|
|
140
140
|
mockedUseMultiSite.mockReturnValue({
|
|
141
141
|
locale: {id: 'en-US', preferredCurrency: 'USD'},
|
|
142
|
-
buildUrl: jest.fn((
|
|
142
|
+
buildUrl: jest.fn(() => '/us/en-US')
|
|
143
143
|
})
|
|
144
144
|
|
|
145
145
|
// Mock useTheme hook
|
|
@@ -239,7 +239,7 @@ describe('ShopperAgent Component', () => {
|
|
|
239
239
|
RefreshToken: 'test-refresh-token',
|
|
240
240
|
Currency: 'USD',
|
|
241
241
|
Language: 'en_US',
|
|
242
|
-
DomainUrl: 'https://example.com/
|
|
242
|
+
DomainUrl: 'https://example.com/us/en-US'
|
|
243
243
|
})
|
|
244
244
|
})
|
|
245
245
|
|
|
@@ -263,7 +263,7 @@ describe('ShopperAgent Component', () => {
|
|
|
263
263
|
RefreshToken: 'initial-token',
|
|
264
264
|
Currency: 'USD',
|
|
265
265
|
Language: 'en_US',
|
|
266
|
-
DomainUrl: 'https://example.com/
|
|
266
|
+
DomainUrl: 'https://example.com/us/en-US'
|
|
267
267
|
})
|
|
268
268
|
|
|
269
269
|
// Clear mock and change refresh token
|
|
@@ -287,7 +287,7 @@ describe('ShopperAgent Component', () => {
|
|
|
287
287
|
RefreshToken: 'updated-token',
|
|
288
288
|
Currency: 'USD',
|
|
289
289
|
Language: 'en_US',
|
|
290
|
-
DomainUrl: 'https://example.com/
|
|
290
|
+
DomainUrl: 'https://example.com/us/en-US'
|
|
291
291
|
})
|
|
292
292
|
})
|
|
293
293
|
|
|
@@ -310,7 +310,7 @@ describe('ShopperAgent Component', () => {
|
|
|
310
310
|
RefreshToken: null,
|
|
311
311
|
Currency: 'USD',
|
|
312
312
|
Language: 'en_US',
|
|
313
|
-
DomainUrl: 'https://example.com/
|
|
313
|
+
DomainUrl: 'https://example.com/us/en-US'
|
|
314
314
|
})
|
|
315
315
|
})
|
|
316
316
|
|
|
@@ -318,7 +318,7 @@ describe('ShopperAgent Component', () => {
|
|
|
318
318
|
// Mock useMultiSite to return different currency values
|
|
319
319
|
mockedUseMultiSite.mockReturnValue({
|
|
320
320
|
locale: {id: 'en-US', preferredCurrency: 'USD'},
|
|
321
|
-
buildUrl: jest.fn((
|
|
321
|
+
buildUrl: jest.fn(() => '/us/en-US')
|
|
322
322
|
})
|
|
323
323
|
|
|
324
324
|
render(<ShopperAgent {...defaultProps} />)
|
|
@@ -337,14 +337,14 @@ describe('ShopperAgent Component', () => {
|
|
|
337
337
|
RefreshToken: 'test-refresh-token',
|
|
338
338
|
Currency: 'USD',
|
|
339
339
|
Language: 'en_US',
|
|
340
|
-
DomainUrl: 'https://example.com/
|
|
340
|
+
DomainUrl: 'https://example.com/us/en-US'
|
|
341
341
|
})
|
|
342
342
|
|
|
343
343
|
// Clear mock and change currency to EUR
|
|
344
344
|
mockEmbeddedService.prechatAPI.setHiddenPrechatFields.mockClear()
|
|
345
345
|
mockedUseMultiSite.mockReturnValue({
|
|
346
346
|
locale: {id: 'en-US', preferredCurrency: 'EUR'},
|
|
347
|
-
buildUrl: jest.fn((
|
|
347
|
+
buildUrl: jest.fn(() => '/us/en-US')
|
|
348
348
|
})
|
|
349
349
|
|
|
350
350
|
// Re-render with new currency
|
|
@@ -364,7 +364,7 @@ describe('ShopperAgent Component', () => {
|
|
|
364
364
|
RefreshToken: 'test-refresh-token',
|
|
365
365
|
Currency: 'EUR',
|
|
366
366
|
Language: 'en_US',
|
|
367
|
-
DomainUrl: 'https://example.com/
|
|
367
|
+
DomainUrl: 'https://example.com/us/en-US'
|
|
368
368
|
})
|
|
369
369
|
})
|
|
370
370
|
|
|
@@ -372,7 +372,7 @@ describe('ShopperAgent Component', () => {
|
|
|
372
372
|
// Mock useMultiSite to return different locale values
|
|
373
373
|
mockedUseMultiSite.mockReturnValue({
|
|
374
374
|
locale: {id: 'en-US', preferredCurrency: 'USD'},
|
|
375
|
-
buildUrl: jest.fn((
|
|
375
|
+
buildUrl: jest.fn(() => '/us/en-US')
|
|
376
376
|
})
|
|
377
377
|
|
|
378
378
|
render(<ShopperAgent {...defaultProps} />)
|
|
@@ -391,14 +391,14 @@ describe('ShopperAgent Component', () => {
|
|
|
391
391
|
RefreshToken: 'test-refresh-token',
|
|
392
392
|
Currency: 'USD',
|
|
393
393
|
Language: 'en_US',
|
|
394
|
-
DomainUrl: 'https://example.com/
|
|
394
|
+
DomainUrl: 'https://example.com/us/en-US'
|
|
395
395
|
})
|
|
396
396
|
|
|
397
397
|
// Clear mock and change locale to en-GB
|
|
398
398
|
mockEmbeddedService.prechatAPI.setHiddenPrechatFields.mockClear()
|
|
399
399
|
mockedUseMultiSite.mockReturnValue({
|
|
400
400
|
locale: {id: 'en-GB', preferredCurrency: 'GBP'},
|
|
401
|
-
buildUrl: jest.fn((
|
|
401
|
+
buildUrl: jest.fn(() => '/us/en-US')
|
|
402
402
|
})
|
|
403
403
|
|
|
404
404
|
// Re-render with new locale
|
|
@@ -418,7 +418,7 @@ describe('ShopperAgent Component', () => {
|
|
|
418
418
|
RefreshToken: 'test-refresh-token',
|
|
419
419
|
Currency: 'GBP',
|
|
420
420
|
Language: 'en_GB',
|
|
421
|
-
DomainUrl: 'https://example.com/
|
|
421
|
+
DomainUrl: 'https://example.com/us/en-US'
|
|
422
422
|
})
|
|
423
423
|
})
|
|
424
424
|
|
|
@@ -449,7 +449,7 @@ describe('ShopperAgent Component', () => {
|
|
|
449
449
|
RefreshToken: 'test-refresh-token',
|
|
450
450
|
Currency: 'USD',
|
|
451
451
|
Language: 'en_US',
|
|
452
|
-
DomainUrl: 'https://example.com/
|
|
452
|
+
DomainUrl: 'https://example.com/us/en-US'
|
|
453
453
|
})
|
|
454
454
|
})
|
|
455
455
|
|
|
@@ -560,7 +560,7 @@ describe('ShopperAgent Component', () => {
|
|
|
560
560
|
RefreshToken: 'test-refresh-token',
|
|
561
561
|
Currency: 'USD',
|
|
562
562
|
Language: 'en_US',
|
|
563
|
-
DomainUrl: 'https://example.com/
|
|
563
|
+
DomainUrl: 'https://example.com/us/en-US'
|
|
564
564
|
})
|
|
565
565
|
})
|
|
566
566
|
|
|
@@ -585,7 +585,7 @@ describe('ShopperAgent Component', () => {
|
|
|
585
585
|
RefreshToken: 'test-refresh-token',
|
|
586
586
|
Currency: 'USD',
|
|
587
587
|
Language: 'en_US',
|
|
588
|
-
DomainUrl: 'https://example.com/
|
|
588
|
+
DomainUrl: 'https://example.com/us/en-US'
|
|
589
589
|
})
|
|
590
590
|
})
|
|
591
591
|
|
|
@@ -597,39 +597,6 @@ describe('ShopperAgent Component', () => {
|
|
|
597
597
|
render(<ShopperAgent {...props} />)
|
|
598
598
|
expect(screen.queryByTestId('shopper-agent')).toBeInTheDocument()
|
|
599
599
|
})
|
|
600
|
-
|
|
601
|
-
test('should handle complex domainUrl with query parameters and fragments', async () => {
|
|
602
|
-
// Mock complex location data
|
|
603
|
-
mockUseLocation.mockReturnValue({
|
|
604
|
-
pathname: '/products/shoes',
|
|
605
|
-
search: '?color=red&size=10',
|
|
606
|
-
hash: '#reviews'
|
|
607
|
-
})
|
|
608
|
-
mockUseAppOrigin.mockReturnValue('https://test-store.com')
|
|
609
|
-
|
|
610
|
-
const props = {
|
|
611
|
-
...defaultProps
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
render(<ShopperAgent {...props} />)
|
|
615
|
-
|
|
616
|
-
// Trigger the onEmbeddedMessagingReady event
|
|
617
|
-
await act(async () => {
|
|
618
|
-
window.dispatchEvent(new Event('onEmbeddedMessagingReady'))
|
|
619
|
-
})
|
|
620
|
-
|
|
621
|
-
expect(mockEmbeddedService.prechatAPI.setHiddenPrechatFields).toHaveBeenCalledWith({
|
|
622
|
-
SiteId: 'RefArchGlobal',
|
|
623
|
-
Locale: 'en-US',
|
|
624
|
-
OrganizationId: 'test-commerce-org-id',
|
|
625
|
-
UsId: 'test-usid',
|
|
626
|
-
IsCartMgmtSupported: 'true',
|
|
627
|
-
RefreshToken: 'test-refresh-token',
|
|
628
|
-
Currency: 'USD',
|
|
629
|
-
Language: 'en_US',
|
|
630
|
-
DomainUrl: 'https://test-store.com/products/shoes'
|
|
631
|
-
})
|
|
632
|
-
})
|
|
633
600
|
})
|
|
634
601
|
|
|
635
602
|
describe('Conversation Context Functionality', () => {
|
|
@@ -987,4 +954,78 @@ describe('ShopperAgent Component', () => {
|
|
|
987
954
|
expect(() => render(<ShopperAgent {...props} />)).not.toThrow()
|
|
988
955
|
})
|
|
989
956
|
})
|
|
957
|
+
|
|
958
|
+
describe('Domain URL Event Functionality', () => {
|
|
959
|
+
beforeEach(() => {
|
|
960
|
+
// Mock postMessage for iframe communication
|
|
961
|
+
global.postMessage = jest.fn()
|
|
962
|
+
|
|
963
|
+
// Mock document.querySelector for iframe
|
|
964
|
+
const mockIframe = {
|
|
965
|
+
src: 'https://test.salesforce.com/iframe',
|
|
966
|
+
contentWindow: {
|
|
967
|
+
postMessage: jest.fn()
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
jest.spyOn(document, 'querySelector').mockReturnValue(mockIframe)
|
|
971
|
+
})
|
|
972
|
+
|
|
973
|
+
afterEach(() => {
|
|
974
|
+
jest.restoreAllMocks()
|
|
975
|
+
})
|
|
976
|
+
|
|
977
|
+
test('should handle lwc.getDomainUrl event and send domain URL', async () => {
|
|
978
|
+
render(<ShopperAgent {...defaultProps} />)
|
|
979
|
+
|
|
980
|
+
// Mock iframe for postMessage
|
|
981
|
+
const mockIframe = {
|
|
982
|
+
src: 'https://test.salesforce.com/iframe',
|
|
983
|
+
contentWindow: {
|
|
984
|
+
postMessage: jest.fn()
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
jest.spyOn(document, 'querySelector').mockReturnValue(mockIframe)
|
|
988
|
+
|
|
989
|
+
// Simulate MIAW event requesting domain URL
|
|
990
|
+
const mockEvent = {
|
|
991
|
+
source: {postMessage: jest.fn()},
|
|
992
|
+
data: {type: 'lwc.getDomainUrl'}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
await act(async () => {
|
|
996
|
+
window.dispatchEvent(new MessageEvent('message', mockEvent))
|
|
997
|
+
})
|
|
998
|
+
|
|
999
|
+
// Verify postMessage was called with domain URL
|
|
1000
|
+
expect(mockIframe.contentWindow.postMessage).toHaveBeenCalledWith(
|
|
1001
|
+
{
|
|
1002
|
+
type: 'conversational.domainUrl',
|
|
1003
|
+
payload: {
|
|
1004
|
+
domainUrl: 'https://example.com/us/en-US'
|
|
1005
|
+
}
|
|
1006
|
+
},
|
|
1007
|
+
'https://test.salesforce.com'
|
|
1008
|
+
)
|
|
1009
|
+
})
|
|
1010
|
+
|
|
1011
|
+
test('should handle lwc.getDomainUrl event properly when iframe not found', async () => {
|
|
1012
|
+
render(<ShopperAgent {...defaultProps} />)
|
|
1013
|
+
|
|
1014
|
+
// Mock querySelector to return null (iframe not found)
|
|
1015
|
+
jest.spyOn(document, 'querySelector').mockReturnValue(null)
|
|
1016
|
+
|
|
1017
|
+
// Simulate MIAW event
|
|
1018
|
+
const mockEvent = {
|
|
1019
|
+
source: {postMessage: jest.fn()},
|
|
1020
|
+
data: {type: 'lwc.getDomainUrl'}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
await act(async () => {
|
|
1024
|
+
window.dispatchEvent(new MessageEvent('message', mockEvent))
|
|
1025
|
+
})
|
|
1026
|
+
|
|
1027
|
+
// Should handle missing iframe gracefully without throwing
|
|
1028
|
+
expect(() => render(<ShopperAgent {...defaultProps} />)).not.toThrow()
|
|
1029
|
+
})
|
|
1030
|
+
})
|
|
990
1031
|
})
|
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-20250930080219",
|
|
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-20250930080219",
|
|
50
|
+
"@salesforce/pwa-kit-dev": "3.14.0-nightly-20250930080219",
|
|
51
|
+
"@salesforce/pwa-kit-react-sdk": "3.14.0-nightly-20250930080219",
|
|
52
|
+
"@salesforce/pwa-kit-runtime": "3.14.0-nightly-20250930080219",
|
|
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": "390c30614d4655289dfe4380c0e26e8b0e5cc9ef"
|
|
111
111
|
}
|