@faststore/core 4.9.3-dev.0 → 4.9.3-dev.1

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.
@@ -1,9 +1,9 @@
1
1
 
2
- > @faststore/core@4.9.2 generate /home/runner/work/faststore/faststore/packages/core
2
+ > @faststore/core@4.9.3-dev.0 generate /home/runner/work/faststore/faststore/packages/core
3
3
  > pnpm run gen-types && pnpm run cache-graphql
4
4
 
5
5
 
6
- > @faststore/core@4.9.2 gen-types /home/runner/work/faststore/faststore/packages/core
6
+ > @faststore/core@4.9.3-dev.0 gen-types /home/runner/work/faststore/faststore/packages/core
7
7
  > node ../cli/bin/run generate-types .
8
8
 
9
9
  [STARTED] Parse Configuration
@@ -19,7 +19,7 @@
19
19
  [COMPLETED] Generate to @generated/
20
20
  [COMPLETED] Generate outputs
21
21
 
22
- > @faststore/core@4.9.2 cache-graphql /home/runner/work/faststore/faststore/packages/core
22
+ > @faststore/core@4.9.3-dev.0 cache-graphql /home/runner/work/faststore/faststore/packages/core
23
23
  > node ../cli/bin/run cache-graphql --config=./discovery.config.default.js --queries=./@generated/persisted-documents.json
24
24
 
25
25
  [Info] - Config file location: /home/runner/work/faststore/faststore/packages/core/discovery.config.default.js
@@ -1,5 +1,5 @@
1
1
 
2
- > @faststore/core@4.9.2 test /home/runner/work/faststore/faststore/packages/core
2
+ > @faststore/core@4.9.3-dev.0 test /home/runner/work/faststore/faststore/packages/core
3
3
  > vitest run
4
4
 
5
5
 
@@ -7,23 +7,33 @@
7
7
 
8
8
  Not implemented: navigation to another Document
9
9
  Not implemented: navigation to another Document
10
- ✓  browser  test/sdk/ui/useScrollRestoration.browser.test.ts (43 tests) 1025ms
10
+ ✓  browser  test/sdk/ui/useScrollRestoration.browser.test.ts (43 tests) 1198ms
11
11
  Browserslist: browsers data (caniuse-lite) is 8 months old. Please run:
12
12
  npx update-browserslist-db@latest
13
13
  Why you should do it regularly: https://github.com/browserslist/update-db#readme
14
- ✓  browser  test/components/account/cards/MyAccountListCards.browser.test.tsx (16 tests) 2299ms
15
- ✓ shows the tab bar and renders Personal cards by default  888ms
14
+ ✓  browser  test/components/account/ContractSwitcher.browser.test.tsx (22 tests) 4246ms
15
+ ✓ shows an error state when the list fails to load  715ms
16
+ ✓ requires selecting a contract before Confirm is enabled, then switches (REQ-03)  424ms
17
+ ✓ keeps Confirm disabled until a different contract is selected  371ms
18
+ ✓ keeps Confirm disabled when switching is unavailable  368ms
19
+ ✓ keeps Confirm disabled when the selected contract is filtered out  345ms
20
+ ✓ honors CMS-provided labels for the header, current session, search, footer, and available count  363ms
21
+ ✓  browser  test/components/account/cards/MyAccountListCards.browser.test.tsx (16 tests) 3043ms
22
+ ✓ shows the tab bar and renders Personal cards by default  1270ms
16
23
  ✓ paginates when there are more than 25 cards  355ms
17
- ✓  browser  test/components/account/ContractSwitcher.browser.test.tsx (22 tests) 2772ms
18
- ✓ shows an error state when the list fails to load  455ms
19
- ✓  browser  test/components/account/orders/BuyingPolicyAlert.browser.test.tsx (13 tests) 2283ms
20
- ✓ opens the reject confirmation modal with the default English copy  1093ms
21
- ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (10 tests) 1261ms
22
- ✓ renders the menu view with contract details and manage footer  697ms
23
- ✓  browser  test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx (13 tests) 286ms
24
- ✓  browser  test/sdk/localization/useBindingSelector.browser.test.tsx (9 tests) 126ms
25
- ✓  browser  test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx (8 tests) 107ms
26
- ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (5 tests) 17ms
24
+ ✓  browser  test/components/account/orders/BuyingPolicyAlert.browser.test.tsx (13 tests) 3166ms
25
+ ✓ opens the reject confirmation modal with the default English copy  1303ms
26
+ ✓ opens the reject confirmation modal with CMS-provided labels  418ms
27
+ ✓ shows the default English toast when a policy is rejected  314ms
28
+ ✓  browser  test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx (13 tests) 304ms
29
+ ✓  browser  test/sdk/localization/useBindingSelector.browser.test.tsx (9 tests) 108ms
30
+ ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (10 tests) 2015ms
31
+ ✓ renders the menu view with contract details and manage footer  994ms
32
+ ✓ opens the contract switcher and returns to the menu on back  367ms
33
+ ✓ resets to the menu when the drawer closes  329ms
34
+ ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (6 tests) 20ms
35
+ ✓  browser  test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx (8 tests) 99ms
36
+ ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 71ms
27
37
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
28
38
  Error in optimistic validation: TypeError: Failed to parse URL from /api/graphql?operationName=ValidateCartMutation&operationHash=3c71b0d3fc2bade14df68abfa271858beb361b12
29
39
   at node:internal/deps/undici/undici:16416:13
@@ -53,12 +63,11 @@ Browserslist: browsers data (caniuse-lite) is 8 months old. Please run:
53
63
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx > RecommendationShelf > renders nothing when there is an error
54
64
  Error fetching recommendations undefined boom for campaign vrn:recommendations:acc:rec-top-items-v2:campaign-1
55
65
 
56
- ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 95ms
57
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 42ms
58
- ✓  browser  test/components/CartSidebar/CartSidebar.browser.test.tsx (5 tests) 56ms
59
- ✓  browser  test/components/sections/Account/AccountOrderDetails.browser.test.tsx (4 tests) 113ms
60
- ✓  browser  test/components/navigation/Navbar.browser.test.tsx (3 tests) 51ms
61
- ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (6 tests) 7ms
66
+ ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 165ms
67
+ ✓  browser  test/components/CartSidebar/CartSidebar.browser.test.tsx (5 tests) 75ms
68
+ ✓  browser  test/components/sections/Account/AccountOrderDetails.browser.test.tsx (4 tests) 93ms
69
+ ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (7 tests) 11ms
70
+ ✓  browser  test/components/navigation/Navbar.browser.test.tsx (3 tests) 92ms
62
71
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > posts the password and redirects to the requested return path
63
72
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
64
73
 
@@ -240,6 +249,7 @@ act(() => {
240
249
  This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
241
250
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
242
251
 
252
+ ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (8 tests) 545ms
243
253
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > shows service unavailable when the login request fails
244
254
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
245
255
 
@@ -288,7 +298,8 @@ act(() => {
288
298
  This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
289
299
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
290
300
 
291
- ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 632ms
301
+ ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 997ms
302
+ ✓ renders the password protection form  459ms
292
303
  stderr | test/components/cms/RenderSections.browser.test.tsx
293
304
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
294
305
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -297,10 +308,9 @@ This ensures that you're testing the behavior the user would see in the browser.
297
308
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
298
309
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
299
310
 
300
- ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (8 tests) 298ms
301
- ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (5 tests) 31ms
302
- ✓  browser  test/components/cms/RenderSections.browser.test.tsx (5 tests) 62ms
303
- ✓  browser  test/components/navigation/NavbarSlider.browser.test.tsx (2 tests) 37ms
311
+ ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (5 tests) 54ms
312
+ ✓  browser  test/components/navigation/NavbarSlider.browser.test.tsx (2 tests) 45ms
313
+ ✓  browser  test/components/cms/RenderSections.browser.test.tsx (5 tests) 102ms
304
314
  stderr | test/sdk/localization/store-url.browser.test.ts
305
315
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
306
316
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -309,15 +319,15 @@ This ensures that you're testing the behavior the user would see in the browser.
309
319
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
310
320
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
311
321
 
312
- ✓  browser  test/components/account/Menu.browser.test.tsx (6 tests) 279ms
313
- ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 4ms
314
- ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 27ms
315
- ✓  browser  test/components/templates/SearchPage/SearchWrapper.browser.test.tsx (3 tests) 30ms
316
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx (2 tests) 124ms
317
- ✓  browser  test/components/sections/Account/AccountListQuotes.browser.test.tsx (4 tests) 95ms
318
- ✓  browser  test/components/navigation/NavbarAccountArea.browser.test.tsx (4 tests) 47ms
319
- ✓  browser  test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx (3 tests) 47ms
320
- ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 18ms
322
+ ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 6ms
323
+ ✓  browser  test/components/account/Menu.browser.test.tsx (6 tests) 403ms
324
+ ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 28ms
325
+ ✓  browser  test/components/templates/SearchPage/SearchWrapper.browser.test.tsx (3 tests) 42ms
326
+ ✓  browser  test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx (2 tests) 139ms
327
+ ✓  browser  test/components/sections/Account/AccountListQuotes.browser.test.tsx (4 tests) 104ms
328
+ ✓  browser  test/components/navigation/NavbarAccountArea.browser.test.tsx (4 tests) 72ms
329
+ ✓  browser  test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx (3 tests) 49ms
330
+ ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 20ms
321
331
  stderr | test/components/account/doLogout.browser.test.ts
322
332
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
323
333
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -326,9 +336,9 @@ This ensures that you're testing the behavior the user would see in the browser.
326
336
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
327
337
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
328
338
 
329
- ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 25ms
330
- ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (5 tests) 265ms
331
- ✓  browser  test/components/ui/LocalizationButton.browser.test.tsx (3 tests) 37ms
339
+ ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 35ms
340
+ ✓  browser  test/components/ui/LocalizationButton.browser.test.tsx (3 tests) 36ms
341
+ ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (5 tests) 383ms
332
342
  stderr | test/sdk/account/clearPersistedSessionState.browser.test.ts
333
343
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
334
344
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -338,13 +348,13 @@ This ensures that you're testing the behavior the user would see in the browser.
338
348
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
339
349
 
340
350
  ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (4 tests) 14ms
341
- ✓  browser  test/components/sections/Account/AccountOrderDelivery.browser.test.tsx (2 tests) 71ms
342
- ✓  browser  test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx (2 tests) 80ms
343
- ✓  browser  test/components/sections/Account/AccountOrderStatus.browser.test.tsx (3 tests) 77ms
344
- ✓  browser  test/components/sections/Account/AccountListCards.browser.test.tsx (3 tests) 67ms
345
- ✓  browser  test/components/region/useRegion.browser.test.ts (1 test) 20ms
346
- ✓  browser  test/components/sections/Account/AccountOrdersList.browser.test.tsx (2 tests) 22ms
347
- ✓  browser  test/components/account/orders/SummaryCard.browser.test.tsx (2 tests) 77ms
351
+ ✓  browser  test/components/sections/Account/AccountOrderDelivery.browser.test.tsx (2 tests) 103ms
352
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx (2 tests) 106ms
353
+ ✓  browser  test/components/sections/Account/AccountOrderStatus.browser.test.tsx (3 tests) 127ms
354
+ ✓  browser  test/components/region/useRegion.browser.test.ts (1 test) 18ms
355
+ ✓  browser  test/components/sections/Account/AccountListCards.browser.test.tsx (3 tests) 94ms
356
+ ✓  browser  test/components/sections/Account/AccountOrdersList.browser.test.tsx (2 tests) 31ms
357
+ ✓  browser  test/components/account/orders/SummaryCard.browser.test.tsx (2 tests) 107ms
348
358
  stderr | test/sdk/session/sessionStore.browser.test.ts
349
359
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
350
360
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -353,9 +363,9 @@ This ensures that you're testing the behavior the user would see in the browser.
353
363
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
354
364
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
355
365
 
356
- ✓  browser  test/sdk/session/sessionStore.browser.test.ts (2 tests) 9ms
357
- ✓  browser  test/components/account/OrganizationSignInButton.browser.test.tsx (2 tests) 186ms
358
- ✓  browser  test/components/account/orders/DeliveryOptionAccordionDeliveryInfo.browser.test.tsx (1 test) 26ms
366
+ ✓  browser  test/components/account/OrganizationSignInButton.browser.test.tsx (2 tests) 314ms
367
+ ✓  browser  test/sdk/session/sessionStore.browser.test.ts (2 tests) 6ms
368
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordionDeliveryInfo.browser.test.tsx (1 test) 44ms
359
369
  stderr | test/sdk/session/validateSession.browser.test.ts
360
370
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
361
371
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -364,14 +374,14 @@ This ensures that you're testing the behavior the user would see in the browser.
364
374
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
365
375
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
366
376
 
367
- ✓  browser  test/sdk/session/validateSession.browser.test.ts (1 test) 4ms
368
- ✓  browser  test/components/account/orders/StatusCard.browser.test.tsx (2 tests) 88ms
369
- ✓  browser  test/utils/isLocalHost.browser.test.ts (3 tests) 3ms
370
- ✓  browser  test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts (1 test) 3ms
371
- ✓  browser  test/components/account/orders/DeliveryCard.browser.test.tsx (1 test) 36ms
372
- ✓  browser  test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx (1 test) 37ms
377
+ ✓  browser  test/sdk/session/validateSession.browser.test.ts (1 test) 7ms
378
+ ✓  browser  test/components/account/orders/StatusCard.browser.test.tsx (2 tests) 123ms
379
+ ✓  browser  test/utils/isLocalHost.browser.test.ts (3 tests) 5ms
380
+ ✓  browser  test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts (1 test) 4ms
381
+ ✓  browser  test/components/account/orders/DeliveryCard.browser.test.tsx (1 test) 55ms
382
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx (1 test) 56ms
373
383
  ✓  browser  test/components/skeletons/ProductGridSkeleton.browser.test.tsx (2 tests) 30ms
374
- ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 19ms
384
+ ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 29ms
375
385
  stderr | test/sdk/account/refreshToken.browser.test.ts
376
386
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
377
387
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -380,13 +390,13 @@ This ensures that you're testing the behavior the user would see in the browser.
380
390
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
381
391
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
382
392
 
383
- ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 4ms
384
- ✓  node  test/server/password-protection-service.test.ts (22 tests) 27ms
385
- ✓  node  test/pages/api/switch-contract.test.ts (25 tests) 71ms
386
- ✓  browser  test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx (1 test) 156ms
387
- ✓  node  test/utils/match-url.test.ts (10 tests) 5ms
388
- ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 12ms
389
- ✓  node  test/pages/api/unlock.test.ts (15 tests) 124ms
393
+ ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 8ms
394
+ ✓  node  test/server/password-protection-service.test.ts (22 tests) 41ms
395
+ ✓  node  test/pages/api/switch-contract.test.ts (25 tests) 89ms
396
+ ✓  browser  test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx (1 test) 223ms
397
+ ✓  node  test/utils/match-url.test.ts (10 tests) 8ms
398
+ ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 15ms
399
+ ✓  node  test/pages/api/unlock.test.ts (15 tests) 209ms
390
400
  stderr | test/components/search/SearchInputComponent.test.tsx
391
401
  Warning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?
392
402
 
@@ -450,10 +460,11 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
450
460
  at Suspense
451
461
  at Wrapper (/home/runner/work/faststore/faststore/packages/core/test/components/search/SearchInputComponent.test.tsx:106:20)
452
462
 
453
- ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 8ms
454
- ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 63ms
455
- ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (15 tests) 319ms
456
- ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 32ms
463
+ ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (14 tests) 15ms
464
+ ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 72ms
465
+ ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 9ms
466
+ ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (15 tests) 500ms
467
+ ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 50ms
457
468
  stderr | test/pages/pvt/account/cards/index.test.ts > Cards page getServerSideProps > redirects to /pvt/account/403 when the query fails with 401/403
458
469
  { extensions: { status: 401 } }
459
470
 
@@ -461,13 +472,13 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
461
472
  { extensions: { status: 502 } }
462
473
 
463
474
  ✓  node  test/pages/pvt/account/cards/index.test.ts (8 tests) 8ms
464
- ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (11 tests) 800ms
465
- ✓ navigates with the search term on blur  358ms
466
- ✓ navigates without a label param when the search is cleared  356ms
467
- ✓  node  test/components/quotes/quoteFilters.test.ts (26 tests) 28ms
468
- ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 11ms
469
- ✓  node  test/utils/productJsonLd.test.ts (36 tests) 11ms
470
- ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 8ms
475
+ ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (11 tests) 854ms
476
+ ✓ navigates with the search term on blur  360ms
477
+ ✓ navigates without a label param when the search is cleared  360ms
478
+ ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 15ms
479
+ ✓  node  test/components/quotes/quoteFilters.test.ts (26 tests) 34ms
480
+ ✓  node  test/utils/productJsonLd.test.ts (36 tests) 16ms
481
+ ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 11ms
471
482
  stderr | test/pages/api/graphql.test.ts > /api/graphql error status propagation > propagates the upstream 400 from a wrapped BadRequestError instead of 500
472
483
  Graphql execution returned with error: [
473
484
  {
@@ -535,7 +546,7 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
535
546
 
536
547
  stderr | test/pages/api/graphql.test.ts > /api/graphql request handling > maps UnauthorizedError thrown by execute to its status
537
548
  Something unexpected occurred querying Graphql endpoint:
538
- ie [FastStoreError]: expired
549
+ ce [FastStoreError]: expired
539
550
  at /home/runner/work/faststore/faststore/packages/core/test/pages/api/graphql.test.ts:202:33
540
551
  at /home/runner/work/faststore/faststore/packages/core/test/pages/api/graphql.test.ts:207:39
541
552
  at file:///home/runner/work/faststore/faststore/node_modules/.pnpm/@vitest+runner@4.0.18/node_modules/@vitest/runner/dist/index.js:1024:60
@@ -577,71 +588,74 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
577
588
  at trace (file:///home/runner/work/faststore/faststore/node_modules/.pnpm/vitest@4.0.18_@opentelemetry+api@1.9.0_@types+node@20.19.13_jiti@2.6.1_jsdom@27.4.0_sas_13be941c57d55f08f06eeaf1c180ddfe/node_modules/vitest/dist/chunks/test.B8ej_ZHS.js:239:21)
578
589
  at runTest (file:///home/runner/work/faststore/faststore/node_modules/.pnpm/@vitest+runner@4.0.18/node_modules/@vitest/runner/dist/index.js:1653:12)
579
590
 
580
- ✓  node  test/pages/api/graphql.test.ts (19 tests) 28ms
581
- ✓  node  test/utils/clearCookies.test.ts (20 tests) 20ms
582
- ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 6ms
591
+ ✓  node  test/pages/api/graphql.test.ts (19 tests) 35ms
592
+ ✓  node  test/utils/clearCookies.test.ts (20 tests) 23ms
593
+ ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 8ms
583
594
  stderr | test/sdk/account/useSetPassword.test.ts > useSetPassword > maps wrongcredentials error responses to the existing message
584
595
  Set password request failed: Bad Request
585
596
 
586
- ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 38ms
587
- ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 30ms
588
- ✓  node  test/utils/localization/localizeRedirectDestination.test.ts (11 tests) 5ms
589
- ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (8 tests) 159ms
590
- ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (14 tests) 163ms
591
- ✓  node  test/utils/localization/hreflang.test.ts (8 tests) 4ms
592
- ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (13 tests) 7ms
593
- ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (10 tests) 85ms
594
- ✓  node  test/sdk/redirects/index.test.ts (7 tests) 37ms
595
- ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 36ms
596
- ✓  node  test/proxy.localization.test.ts (5 tests) 37ms
597
- ✓  node  test/components/account/orders/getDeliveryOptionLabel.test.ts (21 tests) 7ms
598
- ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 40ms
599
- ✓  node  test/sdk/graphql/retryPolicy.test.ts (18 tests) 22ms
600
- ✓  node  test/server/cms/myAccountDefaultSections.test.ts (9 tests) 6ms
601
- ✓  node  test/utils/productJsonLdRendering.test.tsx (5 tests) 13ms
602
- ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (6 tests) 5ms
603
- ✓  node  test/server/index.test.ts (7 tests) 786ms
604
- ✓  node  test/proxy.test.ts (4 tests) 13ms
605
- ✓  node  test/server/webops-api.test.ts (9 tests) 11ms
606
- ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 40ms
607
- ✓  node  test/components/account/orders/StatusCard.test.ts (3 tests) 25ms
608
- ✓  node  test/sdk/localization/store-url.test.ts (4 tests) 4ms
609
- ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 27ms
610
- ✓  node  test/sdk/cart/waitForSessionValidated.test.ts (5 tests) 9ms
611
- ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 10ms
612
- ✓  node  test/server/content/service.test.ts (5 tests) 4ms
613
- ✓  node  test/server/cms/global.test.ts (3 tests) 3ms
614
- ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 33ms
597
+ ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 47ms
598
+ ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 34ms
599
+ ✓  node  test/utils/localization/localizeRedirectDestination.test.ts (11 tests) 6ms
600
+ ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (8 tests) 170ms
601
+ ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (14 tests) 197ms
602
+ ✓  node  test/utils/localization/hreflang.test.ts (8 tests) 7ms
603
+ ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (13 tests) 11ms
604
+ ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (10 tests) 155ms
605
+ ✓  node  test/sdk/redirects/index.test.ts (7 tests) 45ms
606
+ ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 62ms
607
+ ✓  node  test/proxy.localization.test.ts (5 tests) 76ms
608
+ ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 50ms
609
+ ✓  node  test/components/account/orders/getDeliveryOptionLabel.test.ts (21 tests) 9ms
610
+ ✓  node  test/sdk/graphql/retryPolicy.test.ts (18 tests) 21ms
611
+ ✓  node  test/server/cms/myAccountDefaultSections.test.ts (9 tests) 7ms
612
+ ✓  node  test/utils/productJsonLdRendering.test.tsx (5 tests) 15ms
613
+ ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 33ms
614
+ ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 26ms
615
+ ✓  node  test/proxy.test.ts (4 tests) 18ms
616
+ ✓  node  test/server/webops-api.test.ts (9 tests) 12ms
617
+ ✓  node  test/components/account/orders/StatusCard.test.ts (3 tests) 19ms
618
+ ✓  node  test/sdk/localization/store-url.test.ts (4 tests) 5ms
619
+ ✓  node  test/sdk/cart/waitForSessionValidated.test.ts (5 tests) 12ms
620
+ ✓  node  test/server/content/service.test.ts (5 tests) 5ms
621
+ ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 6ms
622
+ ✓  node  test/server/cms/global.test.ts (3 tests) 4ms
623
+ ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 30ms
624
+ ✓  node  test/server/rootSpan.test.ts (4 tests) 41ms
615
625
  ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 5ms
616
- ✓  node  test/components/account/Layout.test.tsx (6 tests) 46ms
617
- ✓  node  test/server/rootSpan.test.ts (4 tests) 39ms
618
626
  ✓  node  test/pages/api/preview.test.ts (2 tests) 7ms
627
+ ✓  node  test/components/account/Layout.test.tsx (6 tests) 49ms
628
+ ✓  node  test/components/search/SearchInput.test.ts (6 tests) 7ms
619
629
  ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 4ms
620
- ✓  node  test/components/search/SearchInput.test.ts (6 tests) 6ms
621
- ✓  node  test/sdk/recommendations/vrn.test.ts (5 tests) 3ms
622
- ✓  node  test/server/cms/index.test.ts (2 tests) 4ms
623
- ✓  node  test/utils/retry.test.ts (5 tests) 12ms
624
- ✓  node  test/sdk/account/getB2BSessionClaims.test.ts (4 tests) 5ms
625
- ✓  node  test/utils/userOrderStatus.test.ts (4 tests) 4ms
626
- ✓  node  test/sdk/recommendations/useRecommendationUserId.test.ts (4 tests) 190ms
627
- ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 33ms
630
+ ✓  node  test/server/index.test.ts (7 tests) 5149ms
631
+ ✓ should return a valid merged GraphQL schema  302ms
632
+ ✓ should handle options and execute  4558ms
633
+ ✓  node  test/sdk/recommendations/vrn.test.ts (5 tests) 6ms
634
+ ✓  node  test/components/region/useRegion.test.ts (1 test) 29ms
635
+ ✓  node  test/utils/retry.test.ts (5 tests) 16ms
636
+ ✓  node  test/server/cms/index.test.ts (2 tests) 6ms
637
+ ✓  node  test/sdk/account/getB2BSessionClaims.test.ts (4 tests) 4ms
638
+ ✓  node  test/utils/userOrderStatus.test.ts (4 tests) 6ms
639
+ ✓  node  test/sdk/recommendations/useRecommendationUserId.test.ts (4 tests) 187ms
628
640
  ✓  node  test/utils/unlockResponse.test.ts (11 tests) 5ms
629
- ✓  node  test/sdk/recommendations/useRecommendations.test.ts (2 tests) 4ms
630
- ✓  node  test/utils/isLocalHost.test.ts (7 tests) 3ms
631
- ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (4 tests) 41ms
632
- ✓  node  test/sdk/session/toSessionInput.test.ts (3 tests) 3ms
633
- ✓  node  test/components/account/orders/getOrderTotalLabel.test.ts (6 tests) 4ms
641
+ ✓  node  test/sdk/recommendations/useRecommendations.test.ts (2 tests) 6ms
642
+ ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 49ms
643
+ ✓  node  test/utils/isLocalHost.test.ts (7 tests) 4ms
644
+ ✓  node  test/sdk/session/toSessionInput.test.ts (3 tests) 5ms
645
+ ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (4 tests) 37ms
646
+ ✓  node  test/components/account/orders/getOrderTotalLabel.test.ts (6 tests) 5ms
634
647
  ✓  node  test/components/account/orders/listOrdersLabels.test.ts (2 tests) 3ms
635
- ✓  node  test/utils/getCookie.test.ts (4 tests) 3ms
636
- ✓  node  test/sdk/session/isSignInAreaResolved.test.ts (3 tests) 3ms
637
- ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 3ms
638
- ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 25ms
639
- ✓  node  test/instrumentation.test.ts (3 tests) 5ms
640
- ✓  node  test/components/account/orders/orderDetailsLabels.test.ts (1 test) 3ms
641
- ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 8ms
642
-
643
-  Test Files  127 passed (127)
644
-  Tests  992 passed (992)
645
-  Start at  13:35:02
646
-  Duration  48.01s (transform 11.84s, setup 0ms, import 44.44s, tests 17.34s, environment 57.43s)
648
+ ✓  node  test/utils/getCookie.test.ts (4 tests) 5ms
649
+ ✓  node  test/sdk/session/isSignInAreaResolved.test.ts (3 tests) 5ms
650
+ ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 34ms
651
+ ✓  node  test/utils/filterChannel.test.ts (2 tests) 5ms
652
+ ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 5ms
653
+ ✓  node  test/instrumentation.test.ts (3 tests) 7ms
654
+ ✓  node  test/components/account/orders/orderDetailsLabels.test.ts (1 test) 4ms
655
+ ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 10ms
656
+
657
+  Test Files  129 passed (129)
658
+  Tests  1005 passed (1005)
659
+  Start at  19:28:12
660
+  Duration  64.24s (transform 13.31s, setup 0ms, import 54.17s, tests 28.07s, environment 78.66s)
647
661
 
package/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [4.9.3-dev.1](https://github.com/vtex/faststore/compare/v4.9.3-dev.0...v4.9.3-dev.1) (2026-10-06)
7
+
8
+ ### Bug Fixes
9
+
10
+ - keep empty carts from moving the session sales channel ([#3509](https://github.com/vtex/faststore/issues/3509)) ([600fb7d](https://github.com/vtex/faststore/commit/600fb7d9403553c12bd01d6b3955f18c48f91054)), closes [#3435](https://github.com/vtex/faststore/issues/3435) [#3435](https://github.com/vtex/faststore/issues/3435)
11
+
6
12
  ## [4.9.3-dev.0](https://github.com/vtex/faststore/compare/v4.9.1-dev.0...v4.9.3-dev.0) (2026-10-05)
7
13
 
8
14
  **Note:** Version bump only for package @faststore/core
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "4.9.3-dev.0",
3
+ "version": "4.9.3-dev.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -71,11 +71,11 @@
71
71
  "style-loader": "^3.3.1",
72
72
  "swr": "^2.2.5",
73
73
  "use-sync-external-store": "^1.6.0",
74
- "@faststore/api": "4.9.3-dev.0",
75
- "@faststore/lighthouse": "4.9.3-dev.0",
76
- "@faststore/diagnostics": "4.9.3-dev.0",
77
- "@faststore/ui": "4.9.3-dev.0",
78
- "@faststore/sdk": "4.9.3-dev.0"
74
+ "@faststore/api": "4.9.3-dev.1",
75
+ "@faststore/diagnostics": "4.9.3-dev.1",
76
+ "@faststore/lighthouse": "4.9.3-dev.1",
77
+ "@faststore/sdk": "4.9.3-dev.1",
78
+ "@faststore/ui": "4.9.3-dev.1"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@cypress/code-coverage": "^3.12.1",
@@ -41,8 +41,10 @@ export default function useRegion(): UseRegionValues {
41
41
 
42
42
  try {
43
43
  // `session` usually comes from `useSession()`, which also carries UI state
44
+ // and filters session-only channel keys (sales channel markers).
44
45
  const newSession = {
45
46
  ...toSessionInput(session),
47
+ channel: sessionStore.read()?.channel ?? session.channel,
46
48
  postalCode,
47
49
  geoCoordinates: null, // Revalidate geo coordinates in API when users set a new postal code
48
50
  } as Session
@@ -18,7 +18,7 @@ import {
18
18
  sessionStore,
19
19
  toSessionInput,
20
20
  } from '../session'
21
- import { syncSalesChannelFromOrderForm } from '../session/syncSalesChannelFromOrderForm'
21
+ import { syncSessionWithValidatedCart } from '../session/syncSalesChannelFromOrderForm'
22
22
  import { createValidationStore, useStore } from '../useStore'
23
23
  import { waitForSessionValidated } from './waitForSessionValidated'
24
24
 
@@ -162,14 +162,16 @@ const validateCart = async (cart: Cart): Promise<Cart | null> => {
162
162
  },
163
163
  })
164
164
 
165
- const adoptedSalesChannel = validated?.order?.salesChannel
166
- if (adoptedSalesChannel) {
167
- syncSalesChannelFromOrderForm(
168
- adoptedSalesChannel,
169
- () => sessionStore.read(),
170
- (session) => sessionStore.setSilent(session)
171
- )
172
- }
165
+ syncSessionWithValidatedCart(
166
+ {
167
+ adoptedSalesChannel: validated?.order?.salesChannel,
168
+ // `null` means the browser cart is already valid.
169
+ itemCount: (validated ? validated.order.acceptedOffer : cart.items)
170
+ .length,
171
+ },
172
+ () => sessionStore.read(),
173
+ (session) => sessionStore.setSilent(session)
174
+ )
173
175
 
174
176
  return (
175
177
  validated && {
@@ -47,6 +47,10 @@ export const useLocalizationConfig = (params?: { url?: string | URL }) => {
47
47
  const session = sessionRef.current
48
48
  const channel = JSON.parse(session.channel ?? '{}') ?? {}
49
49
  channel.salesChannel = settings.salesChannel
50
+ // The URL is the SC intent: validateCart never adopts over it.
51
+ channel.salesChannelSource = 'url'
52
+ // Adoption never applies on the URL SC, so a recorded rejection is moot.
53
+ delete channel.rejectedSalesChannel
50
54
 
51
55
  const newSession = {
52
56
  ...session,
@@ -46,6 +46,10 @@ export function getInitialSession(
46
46
  const settings = getSettings()
47
47
  const channel = JSON.parse(defaults.channel ?? '{}') ?? {}
48
48
  channel.salesChannel = settings.salesChannel
49
+ // The URL is the SC intent: validateCart never adopts over it.
50
+ channel.salesChannelSource = 'url'
51
+ // Adoption never applies on the URL SC, so a recorded rejection is moot.
52
+ delete channel.rejectedSalesChannel
49
53
 
50
54
  return {
51
55
  ...defaults,
@@ -81,6 +85,10 @@ export function reconcileSessionLocale(fromIDB: Session): Session {
81
85
  const settings = getSettings()
82
86
  const channel = safeParseChannel(fromIDB.channel)
83
87
  channel.salesChannel = settings.salesChannel
88
+ // The URL is the SC intent: validateCart never adopts over it.
89
+ channel.salesChannelSource = 'url'
90
+ // Adoption never applies on the URL SC, so a recorded rejection is moot.
91
+ delete channel.rejectedSalesChannel
84
92
 
85
93
  return {
86
94
  ...fromIDB,
@@ -35,8 +35,9 @@ function readSalesChannel(channel: Record<string, unknown>): string {
35
35
  * (e.g. after Quick Order) without revalidating the cart.
36
36
  *
37
37
  * Uses `sessionStore.setSilent` so we do not trigger `cartStore.set` mid-flight
38
- * (that race previously wiped SC-exclusive items). Marks the channel as
39
- * explicit (`hasOnlyDefaultSalesChannel: false`) so `validateSession` keeps it.
38
+ * (that race previously wiped SC-exclusive items). Marks the channel with
39
+ * `salesChannelSource: 'orderForm'` so `validateSession` keeps it until the
40
+ * cart is empty (see `releaseAdoptedSalesChannel`).
40
41
  */
41
42
  export function syncSalesChannelFromOrderForm(
42
43
  salesChannel: string | null | undefined,
@@ -61,8 +62,60 @@ export function syncSalesChannelFromOrderForm(
61
62
  ...channel,
62
63
  salesChannel: nextSalesChannel,
63
64
  hasOnlyDefaultSalesChannel: false,
65
+ salesChannelSource: 'orderForm',
64
66
  }),
65
67
  })
66
68
 
67
69
  return true
68
70
  }
71
+
72
+ /**
73
+ * Drops the orderForm adoption marker and any rejected SC once the cart has no
74
+ * items, so Session Manager can again reject or replace the sales channel on
75
+ * the next `validateSession`. The SC itself is kept: an empty `salesChannel`
76
+ * would reach Checkout and catalog queries.
77
+ */
78
+ export function releaseAdoptedSalesChannel(
79
+ readSession: () => Session,
80
+ setSilent: (session: Session) => void
81
+ ): boolean {
82
+ const session = readSession()
83
+ const { salesChannelSource, rejectedSalesChannel, ...channel } =
84
+ parseChannelRecord(session.channel)
85
+ const isAdopted = salesChannelSource === 'orderForm'
86
+
87
+ if (!isAdopted && rejectedSalesChannel === undefined) {
88
+ return false
89
+ }
90
+
91
+ setSilent({
92
+ ...session,
93
+ channel: JSON.stringify(
94
+ isAdopted || salesChannelSource === undefined
95
+ ? channel
96
+ : { ...channel, salesChannelSource }
97
+ ),
98
+ })
99
+
100
+ return true
101
+ }
102
+
103
+ /**
104
+ * Applies the sales channel outcome of a `validateCart` to the session: adopt
105
+ * the orderForm SC when the API returned one, release the adoption once the
106
+ * cart is empty.
107
+ */
108
+ export function syncSessionWithValidatedCart(
109
+ {
110
+ adoptedSalesChannel,
111
+ itemCount,
112
+ }: { adoptedSalesChannel: string | null | undefined; itemCount: number },
113
+ readSession: () => Session,
114
+ setSilent: (session: Session) => void
115
+ ) {
116
+ syncSalesChannelFromOrderForm(adoptedSalesChannel, readSession, setSilent)
117
+
118
+ if (itemCount === 0) {
119
+ releaseAdoptedSalesChannel(readSession, setSilent)
120
+ }
121
+ }
@@ -110,9 +110,9 @@ export const toArray = <T>(x: T[] | T | undefined) =>
110
110
  Array.isArray(x) ? x : x ? [x] : []
111
111
 
112
112
  /**
113
- * Filters the channel object by removing the hasOnlyDefaultSalesChannel key.
114
- * This key is used only in the useAuth hook and is only required to send on the ValidateSession mutation,
115
- * so we remove it from the session's channel object to avoid unnecessary cache invalidations and query executions
113
+ * Filters the channel object by removing the hasOnlyDefaultSalesChannel, salesChannelSource and rejectedSalesChannel keys.
114
+ * These keys are session-only (useAuth and the ValidateSession/ValidateCart mutations),
115
+ * so we remove them from the session's channel object to avoid unnecessary cache invalidations and query executions
116
116
  *
117
117
  * @param channel - The channel string to filter.
118
118
  * @returns The filtered channel string.
@@ -122,8 +122,12 @@ export function filterChannel(channel: string): string {
122
122
  return '{}'
123
123
  }
124
124
  try {
125
- const { hasOnlyDefaultSalesChannel, ...filteredChannel } =
126
- JSON.parse(channel)
125
+ const {
126
+ hasOnlyDefaultSalesChannel,
127
+ salesChannelSource,
128
+ rejectedSalesChannel,
129
+ ...filteredChannel
130
+ } = JSON.parse(channel)
127
131
  return JSON.stringify(filteredChannel)
128
132
  } catch {
129
133
  console.warn('[filterChannel] Invalid channel JSON:', channel)
@@ -7,7 +7,7 @@ const mockSessionSet = vi.hoisted(() => vi.fn())
7
7
 
8
8
  vi.mock('src/sdk/session', async () => ({
9
9
  validateSession: mockValidateSession,
10
- sessionStore: { set: mockSessionSet },
10
+ sessionStore: { set: mockSessionSet, read: vi.fn() },
11
11
  toSessionInput: (await import('src/sdk/session/toSessionInput'))
12
12
  .toSessionInput,
13
13
  }))
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import { act, renderHook } from '@testing-library/react'
6
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
7
+
8
+ const { mockRead, mockSet, mockValidateSession } = vi.hoisted(() => ({
9
+ mockRead: vi.fn(),
10
+ mockSet: vi.fn(),
11
+ mockValidateSession: vi.fn(),
12
+ }))
13
+
14
+ vi.mock('src/sdk/session', async () => ({
15
+ sessionStore: { read: mockRead, set: mockSet },
16
+ validateSession: mockValidateSession,
17
+ toSessionInput: (await import('src/sdk/session/toSessionInput'))
18
+ .toSessionInput,
19
+ }))
20
+ vi.mock('discovery.config', () => ({ deliveryPromise: { enabled: false } }))
21
+ vi.mock('src/sdk/product', () => ({ getProductCount: vi.fn() }))
22
+
23
+ import useRegion from '../../../src/components/region/RegionModal/useRegion'
24
+
25
+ // What `useSession()` hands to region components: `filterChannel` already
26
+ // stripped the session-only keys.
27
+ const filteredSession = {
28
+ locale: 'en-US',
29
+ currency: { code: 'USD', symbol: '$' },
30
+ country: 'USA',
31
+ channel: JSON.stringify({ salesChannel: '4', regionId: '' }),
32
+ postalCode: null,
33
+ } as any
34
+
35
+ const storedChannel = JSON.stringify({
36
+ salesChannel: '4',
37
+ regionId: '',
38
+ hasOnlyDefaultSalesChannel: false,
39
+ rejectedSalesChannel: '6',
40
+ })
41
+
42
+ describe('useRegion', () => {
43
+ beforeEach(() => {
44
+ vi.clearAllMocks()
45
+ mockRead.mockReturnValue({ ...filteredSession, channel: storedChannel })
46
+ mockValidateSession.mockImplementation(async (session) => session)
47
+ })
48
+
49
+ it('validates the new postal code with the stored channel, keeping session-only keys', async () => {
50
+ const { result } = renderHook(() => useRegion())
51
+
52
+ await act(() =>
53
+ result.current.setRegion({
54
+ session: filteredSession,
55
+ postalCode: '10001',
56
+ errorMessage: 'error',
57
+ })
58
+ )
59
+
60
+ expect(mockValidateSession).toHaveBeenCalledWith(
61
+ expect.objectContaining({ channel: storedChannel, postalCode: '10001' })
62
+ )
63
+ expect(mockSet).toHaveBeenCalledWith(
64
+ expect.objectContaining({ channel: storedChannel })
65
+ )
66
+ })
67
+ })
@@ -134,6 +134,14 @@ describe('getInitialSession (browser, localization enabled)', () => {
134
134
  expect(channel.regionId).toBe('region-abc')
135
135
  })
136
136
 
137
+ it('marks the URL-derived salesChannel as intentional', () => {
138
+ stubHref('https://store.example.com/en-US/some/path')
139
+
140
+ const result = getInitialSession(defaults)
141
+
142
+ expect(JSON.parse(result.channel ?? '{}').salesChannelSource).toBe('url')
143
+ })
144
+
137
145
  it('preserves non-locale defaults (country, person, etc.)', () => {
138
146
  stubHref('https://store.example.com/en-US/some/path')
139
147
 
@@ -165,6 +165,22 @@ describe('reconcileSessionLocale (browser, localization enabled)', () => {
165
165
  expect(channel.customKey).toBe('keep-me')
166
166
  })
167
167
 
168
+ it('marks the URL salesChannel as intentional, replacing an orderForm adoption', () => {
169
+ stubHref('https://store.example.com/pt-BR/some/path')
170
+
171
+ const payload: Session = {
172
+ ...STALE,
173
+ channel:
174
+ '{"salesChannel":"4","salesChannelSource":"orderForm","rejectedSalesChannel":"6"}',
175
+ }
176
+
177
+ const channel = JSON.parse(reconcileSessionLocale(payload).channel ?? '{}')
178
+
179
+ expect(channel.salesChannel).toBe('1')
180
+ expect(channel.salesChannelSource).toBe('url')
181
+ expect(channel).not.toHaveProperty('rejectedSalesChannel')
182
+ })
183
+
168
184
  it('survives a malformed channel JSON in the payload (does not throw, still reconciles)', () => {
169
185
  stubHref('https://store.example.com/pt-BR/some/path')
170
186
 
@@ -1,6 +1,10 @@
1
1
  import { describe, expect, it, vi } from 'vitest'
2
2
  import type { Session } from '@faststore/sdk'
3
- import { syncSalesChannelFromOrderForm } from '../../../src/sdk/session/syncSalesChannelFromOrderForm'
3
+ import {
4
+ releaseAdoptedSalesChannel,
5
+ syncSalesChannelFromOrderForm,
6
+ syncSessionWithValidatedCart,
7
+ } from '../../../src/sdk/session/syncSalesChannelFromOrderForm'
4
8
 
5
9
  const baseSession = (
6
10
  salesChannel: string,
@@ -44,6 +48,7 @@ describe('syncSalesChannelFromOrderForm', () => {
44
48
  expect(JSON.parse(next.channel ?? '{}')).toMatchObject({
45
49
  salesChannel: '2',
46
50
  hasOnlyDefaultSalesChannel: false,
51
+ salesChannelSource: 'orderForm',
47
52
  })
48
53
  })
49
54
 
@@ -123,3 +128,155 @@ describe('syncSalesChannelFromOrderForm', () => {
123
128
  )
124
129
  })
125
130
  })
131
+
132
+ describe('releaseAdoptedSalesChannel', () => {
133
+ const withChannel = (channel: Record<string, unknown>) =>
134
+ baseSession('', true, JSON.stringify(channel))
135
+
136
+ it('drops the orderForm marker and keeps the rest of the channel', () => {
137
+ const setSilent = vi.fn()
138
+ const released = releaseAdoptedSalesChannel(
139
+ () =>
140
+ withChannel({
141
+ salesChannel: '4',
142
+ regionId: 'r1',
143
+ hasOnlyDefaultSalesChannel: false,
144
+ salesChannelSource: 'orderForm',
145
+ }),
146
+ setSilent
147
+ )
148
+
149
+ expect(released).toBe(true)
150
+ expect(JSON.parse(setSilent.mock.calls[0][0].channel)).toEqual({
151
+ salesChannel: '4',
152
+ regionId: 'r1',
153
+ hasOnlyDefaultSalesChannel: false,
154
+ })
155
+ })
156
+
157
+ it('drops a rejected SC together with the adoption marker', () => {
158
+ const setSilent = vi.fn()
159
+
160
+ releaseAdoptedSalesChannel(
161
+ () =>
162
+ withChannel({
163
+ salesChannel: '4',
164
+ salesChannelSource: 'orderForm',
165
+ rejectedSalesChannel: '6',
166
+ }),
167
+ setSilent
168
+ )
169
+
170
+ expect(JSON.parse(setSilent.mock.calls[0][0].channel)).toEqual({
171
+ salesChannel: '4',
172
+ })
173
+ })
174
+
175
+ it('drops a rejected SC left after the adoption was dropped', () => {
176
+ const setSilent = vi.fn()
177
+
178
+ expect(
179
+ releaseAdoptedSalesChannel(
180
+ () => withChannel({ salesChannel: '4', rejectedSalesChannel: '6' }),
181
+ setSilent
182
+ )
183
+ ).toBe(true)
184
+ expect(JSON.parse(setSilent.mock.calls[0][0].channel)).toEqual({
185
+ salesChannel: '4',
186
+ })
187
+ })
188
+
189
+ it('keeps the URL marker when dropping a rejected SC', () => {
190
+ const setSilent = vi.fn()
191
+
192
+ releaseAdoptedSalesChannel(
193
+ () =>
194
+ withChannel({
195
+ salesChannel: '3',
196
+ salesChannelSource: 'url',
197
+ rejectedSalesChannel: '6',
198
+ }),
199
+ setSilent
200
+ )
201
+
202
+ expect(JSON.parse(setSilent.mock.calls[0][0].channel)).toEqual({
203
+ salesChannel: '3',
204
+ salesChannelSource: 'url',
205
+ })
206
+ })
207
+
208
+ it('is a no-op without the orderForm marker', () => {
209
+ const setSilent = vi.fn()
210
+
211
+ expect(
212
+ releaseAdoptedSalesChannel(
213
+ () => withChannel({ salesChannel: '4' }),
214
+ setSilent
215
+ )
216
+ ).toBe(false)
217
+ expect(
218
+ releaseAdoptedSalesChannel(
219
+ () => withChannel({ salesChannel: '3', salesChannelSource: 'url' }),
220
+ setSilent
221
+ )
222
+ ).toBe(false)
223
+ expect(
224
+ releaseAdoptedSalesChannel(() => baseSession('', true, '{'), setSilent)
225
+ ).toBe(false)
226
+ expect(setSilent).not.toHaveBeenCalled()
227
+ })
228
+ })
229
+
230
+ describe('syncSessionWithValidatedCart', () => {
231
+ const store = (channel: Record<string, unknown>) => {
232
+ let session = baseSession('', true, JSON.stringify(channel))
233
+ const setSilent = vi.fn((next: Session) => {
234
+ session = next
235
+ })
236
+
237
+ return {
238
+ read: () => session,
239
+ setSilent,
240
+ channel: () => JSON.parse(session.channel ?? '{}'),
241
+ }
242
+ }
243
+
244
+ it('adopts the returned SC and keeps the marker while the cart has items', () => {
245
+ const s = store({ salesChannel: '2' })
246
+
247
+ syncSessionWithValidatedCart(
248
+ { adoptedSalesChannel: '4', itemCount: 1 },
249
+ s.read,
250
+ s.setSilent
251
+ )
252
+
253
+ expect(s.channel()).toMatchObject({
254
+ salesChannel: '4',
255
+ salesChannelSource: 'orderForm',
256
+ })
257
+ })
258
+
259
+ it('releases the adoption once the validated cart is empty', () => {
260
+ const s = store({ salesChannel: '4', salesChannelSource: 'orderForm' })
261
+
262
+ syncSessionWithValidatedCart(
263
+ { adoptedSalesChannel: null, itemCount: 0 },
264
+ s.read,
265
+ s.setSilent
266
+ )
267
+
268
+ expect(s.channel()).toEqual({ salesChannel: '4' })
269
+ })
270
+
271
+ it('leaves the session untouched when nothing was adopted and the cart has items', () => {
272
+ const s = store({ salesChannel: '4', salesChannelSource: 'orderForm' })
273
+
274
+ syncSessionWithValidatedCart(
275
+ { adoptedSalesChannel: undefined, itemCount: 2 },
276
+ s.read,
277
+ s.setSilent
278
+ )
279
+
280
+ expect(s.setSilent).not.toHaveBeenCalled()
281
+ })
282
+ })
@@ -0,0 +1,25 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { filterChannel } from 'src/utils/utilities'
4
+
5
+ describe('filterChannel', () => {
6
+ it('strips session-only keys so they never reach queries or cache keys', () => {
7
+ expect(
8
+ JSON.parse(
9
+ filterChannel(
10
+ JSON.stringify({
11
+ salesChannel: '4',
12
+ regionId: 'r1',
13
+ hasOnlyDefaultSalesChannel: false,
14
+ salesChannelSource: 'orderForm',
15
+ rejectedSalesChannel: '6',
16
+ })
17
+ )
18
+ )
19
+ ).toEqual({ salesChannel: '4', regionId: 'r1' })
20
+ })
21
+
22
+ it('returns an empty object for an empty channel', () => {
23
+ expect(filterChannel('')).toBe('{}')
24
+ })
25
+ })