@faststore/core 4.9.2 → 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.1 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.1 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.1 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.1 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,26 +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) 973ms
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/ContractSwitcher.browser.test.tsx (22 tests) 3517ms
15
- ✓ shows an error state when the list fails to load  634ms
16
- ✓ requires selecting a contract before Confirm is enabled, then switches (REQ-03)  522ms
17
- ✓ keeps Confirm disabled until a different contract is selected  322ms
18
- ✓ keeps Confirm disabled when the selected contract is filtered out  306ms
19
- ✓  browser  test/components/account/cards/MyAccountListCards.browser.test.tsx (16 tests) 2424ms
20
- ✓ shows the tab bar and renders Personal cards by default  1049ms
21
- ✓ paginates when there are more than 25 cards  401ms
22
- ✓  browser  test/components/account/orders/BuyingPolicyAlert.browser.test.tsx (13 tests) 1899ms
23
- ✓ opens the reject confirmation modal with the default English copy  763ms
24
- ✓  browser  test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx (13 tests) 184ms
25
- ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (10 tests) 1217ms
26
- ✓ renders the menu view with contract details and manage footer  683ms
27
- ✓  browser  test/sdk/localization/useBindingSelector.browser.test.tsx (9 tests) 74ms
28
- ✓  browser  test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx (8 tests) 110ms
29
- ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (5 tests) 11ms
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
23
+ ✓ paginates when there are more than 25 cards  355ms
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
30
37
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
31
38
  Error in optimistic validation: TypeError: Failed to parse URL from /api/graphql?operationName=ValidateCartMutation&operationHash=3c71b0d3fc2bade14df68abfa271858beb361b12
32
39
   at node:internal/deps/undici/undici:16416:13
@@ -56,11 +63,11 @@ Browserslist: browsers data (caniuse-lite) is 8 months old. Please run:
56
63
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx > RecommendationShelf > renders nothing when there is an error
57
64
  Error fetching recommendations undefined boom for campaign vrn:recommendations:acc:rec-top-items-v2:campaign-1
58
65
 
59
- ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 83ms
60
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 53ms
61
- ✓  browser  test/components/CartSidebar/CartSidebar.browser.test.tsx (5 tests) 94ms
62
- ✓  browser  test/components/sections/Account/AccountOrderDetails.browser.test.tsx (4 tests) 104ms
63
- ✓  browser  test/components/navigation/Navbar.browser.test.tsx (3 tests) 78ms
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
64
71
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > posts the password and redirects to the requested return path
65
72
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
66
73
 
@@ -242,6 +249,7 @@ act(() => {
242
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
243
250
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
244
251
 
252
+ ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (8 tests) 545ms
245
253
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > shows service unavailable when the login request fails
246
254
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
247
255
 
@@ -290,10 +298,8 @@ act(() => {
290
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
291
299
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
292
300
 
293
- ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 474ms
294
- ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (6 tests) 7ms
295
- ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (8 tests) 281ms
296
- ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (5 tests) 36ms
301
+ ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 997ms
302
+ ✓ renders the password protection form  459ms
297
303
  stderr | test/components/cms/RenderSections.browser.test.tsx
298
304
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
299
305
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -302,9 +308,9 @@ This ensures that you're testing the behavior the user would see in the browser.
302
308
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
303
309
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
304
310
 
305
- ✓  browser  test/components/cms/RenderSections.browser.test.tsx (5 tests) 60ms
306
- ✓  browser  test/components/navigation/NavbarSlider.browser.test.tsx (2 tests) 39ms
307
- ✓  browser  test/components/account/Menu.browser.test.tsx (6 tests) 215ms
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
308
314
  stderr | test/sdk/localization/store-url.browser.test.ts
309
315
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
310
316
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -313,14 +319,15 @@ This ensures that you're testing the behavior the user would see in the browser.
313
319
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
314
320
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
315
321
 
316
- ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 4ms
317
- ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 20ms
318
- ✓  browser  test/components/templates/SearchPage/SearchWrapper.browser.test.tsx (3 tests) 30ms
319
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx (2 tests) 125ms
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
320
327
  ✓  browser  test/components/sections/Account/AccountListQuotes.browser.test.tsx (4 tests) 104ms
321
- ✓  browser  test/components/navigation/NavbarAccountArea.browser.test.tsx (4 tests) 38ms
322
- ✓  browser  test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx (3 tests) 34ms
323
- ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 17ms
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
324
331
  stderr | test/components/account/doLogout.browser.test.ts
325
332
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
326
333
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -329,9 +336,9 @@ This ensures that you're testing the behavior the user would see in the browser.
329
336
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
330
337
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
331
338
 
332
- ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 18ms
333
- ✓  browser  test/components/ui/LocalizationButton.browser.test.tsx (3 tests) 40ms
334
- ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (5 tests) 260ms
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
335
342
  stderr | test/sdk/account/clearPersistedSessionState.browser.test.ts
336
343
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
337
344
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -340,14 +347,14 @@ This ensures that you're testing the behavior the user would see in the browser.
340
347
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
341
348
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
342
349
 
343
- ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (4 tests) 15ms
344
- ✓  browser  test/components/sections/Account/AccountOrderDelivery.browser.test.tsx (2 tests) 62ms
345
- ✓  browser  test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx (2 tests) 71ms
346
- ✓  browser  test/components/sections/Account/AccountOrderStatus.browser.test.tsx (3 tests) 89ms
347
- ✓  browser  test/components/region/useRegion.browser.test.ts (1 test) 17ms
348
- ✓  browser  test/components/sections/Account/AccountListCards.browser.test.tsx (3 tests) 42ms
349
- ✓  browser  test/components/sections/Account/AccountOrdersList.browser.test.tsx (2 tests) 21ms
350
- ✓  browser  test/components/account/orders/SummaryCard.browser.test.tsx (2 tests) 62ms
350
+ ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (4 tests) 14ms
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
351
358
  stderr | test/sdk/session/sessionStore.browser.test.ts
352
359
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
353
360
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -356,9 +363,9 @@ This ensures that you're testing the behavior the user would see in the browser.
356
363
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
357
364
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
358
365
 
359
- ✓  browser  test/components/account/OrganizationSignInButton.browser.test.tsx (2 tests) 185ms
360
- ✓  browser  test/sdk/session/sessionStore.browser.test.ts (2 tests) 5ms
361
- ✓  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
362
369
  stderr | test/sdk/session/validateSession.browser.test.ts
363
370
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
364
371
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -367,14 +374,14 @@ This ensures that you're testing the behavior the user would see in the browser.
367
374
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
368
375
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
369
376
 
370
- ✓  browser  test/sdk/session/validateSession.browser.test.ts (1 test) 4ms
371
- ✓  browser  test/components/account/orders/StatusCard.browser.test.tsx (2 tests) 74ms
372
- ✓  browser  test/utils/isLocalHost.browser.test.ts (3 tests) 4ms
373
- ✓  browser  test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts (1 test) 3ms
374
- ✓  browser  test/components/account/orders/DeliveryCard.browser.test.tsx (1 test) 33ms
375
- ✓  browser  test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx (1 test) 34ms
376
- ✓  browser  test/components/skeletons/ProductGridSkeleton.browser.test.tsx (2 tests) 22ms
377
- ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 20ms
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
383
+ ✓  browser  test/components/skeletons/ProductGridSkeleton.browser.test.tsx (2 tests) 30ms
384
+ ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 29ms
378
385
  stderr | test/sdk/account/refreshToken.browser.test.ts
379
386
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
380
387
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -383,14 +390,13 @@ This ensures that you're testing the behavior the user would see in the browser.
383
390
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
384
391
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
385
392
 
386
- ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 3ms
387
- ✓  node  test/server/password-protection-service.test.ts (22 tests) 19ms
388
- ✓  node  test/pages/api/switch-contract.test.ts (25 tests) 71ms
389
- ✓  browser  test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx (1 test) 169ms
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
390
397
  ✓  node  test/utils/match-url.test.ts (10 tests) 8ms
391
- ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 8ms
392
- ✓  node  test/pages/api/unlock.test.ts (15 tests) 86ms
393
- ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 9ms
398
+ ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 15ms
399
+ ✓  node  test/pages/api/unlock.test.ts (15 tests) 209ms
394
400
  stderr | test/components/search/SearchInputComponent.test.tsx
395
401
  Warning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?
396
402
 
@@ -454,22 +460,25 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
454
460
  at Suspense
455
461
  at Wrapper (/home/runner/work/faststore/faststore/packages/core/test/components/search/SearchInputComponent.test.tsx:106:20)
456
462
 
457
- ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 59ms
458
- ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (15 tests) 298ms
459
- ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 30ms
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
460
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
461
469
  { extensions: { status: 401 } }
462
470
 
463
471
  stderr | test/pages/pvt/account/cards/index.test.ts > Cards page getServerSideProps > renders the error state (no redirect) when the query fails with a non-auth status
464
472
  { extensions: { status: 502 } }
465
473
 
466
- ✓  node  test/pages/pvt/account/cards/index.test.ts (8 tests) 6ms
467
- ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (11 tests) 812ms
468
- ✓ navigates with the search term on blur  358ms
469
- ✓ navigates without a label param when the search is cleared  362ms
470
- ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 11ms
471
- ✓  node  test/components/quotes/quoteFilters.test.ts (26 tests) 26ms
472
- ✓  node  test/utils/productJsonLd.test.ts (36 tests) 11ms
474
+ ✓  node  test/pages/pvt/account/cards/index.test.ts (8 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
473
482
  stderr | test/pages/api/graphql.test.ts > /api/graphql error status propagation > propagates the upstream 400 from a wrapped BadRequestError instead of 500
474
483
  Graphql execution returned with error: [
475
484
  {
@@ -537,7 +546,7 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
537
546
 
538
547
  stderr | test/pages/api/graphql.test.ts > /api/graphql request handling > maps UnauthorizedError thrown by execute to its status
539
548
  Something unexpected occurred querying Graphql endpoint:
540
- ie [FastStoreError]: expired
549
+ ce [FastStoreError]: expired
541
550
  at /home/runner/work/faststore/faststore/packages/core/test/pages/api/graphql.test.ts:202:33
542
551
  at /home/runner/work/faststore/faststore/packages/core/test/pages/api/graphql.test.ts:207:39
543
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
@@ -579,72 +588,74 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
579
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)
580
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)
581
590
 
582
- ✓  node  test/pages/api/graphql.test.ts (19 tests) 25ms
583
- ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 7ms
584
- ✓  node  test/utils/clearCookies.test.ts (20 tests) 19ms
585
- ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 5ms
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
586
594
  stderr | test/sdk/account/useSetPassword.test.ts > useSetPassword > maps wrongcredentials error responses to the existing message
587
595
  Set password request failed: Bad Request
588
596
 
589
- ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 36ms
590
- ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 35ms
591
- ✓  node  test/utils/localization/localizeRedirectDestination.test.ts (11 tests) 5ms
592
- ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (8 tests) 121ms
593
- ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (14 tests) 125ms
594
- ✓  node  test/utils/localization/hreflang.test.ts (8 tests) 4ms
595
- ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (13 tests) 7ms
596
- ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (10 tests) 69ms
597
- ✓  node  test/sdk/redirects/index.test.ts (7 tests) 28ms
598
- ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 38ms
599
- ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 34ms
600
- ✓  node  test/proxy.localization.test.ts (5 tests) 34ms
601
- ✓  node  test/components/account/orders/getDeliveryOptionLabel.test.ts (21 tests) 6ms
602
- ✓  node  test/sdk/graphql/retryPolicy.test.ts (18 tests) 18ms
603
- ✓  node  test/server/cms/myAccountDefaultSections.test.ts (9 tests) 5ms
604
- ✓  node  test/utils/productJsonLdRendering.test.tsx (5 tests) 14ms
605
- ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (6 tests) 6ms
606
- ✓  node  test/server/index.test.ts (7 tests) 676ms
607
- ✓  node  test/proxy.test.ts (4 tests) 15ms
608
- ✓  node  test/server/webops-api.test.ts (9 tests) 10ms
609
- ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 36ms
610
- ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 20ms
611
- ✓  node  test/components/account/orders/StatusCard.test.ts (3 tests) 25ms
612
- ✓  node  test/sdk/localization/store-url.test.ts (4 tests) 3ms
613
- ✓  node  test/sdk/cart/waitForSessionValidated.test.ts (5 tests) 8ms
614
- ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 5ms
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
615
622
  ✓  node  test/server/cms/global.test.ts (3 tests) 4ms
616
- ✓  node  test/server/content/service.test.ts (5 tests) 4ms
617
- ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 23ms
623
+ ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 30ms
624
+ ✓  node  test/server/rootSpan.test.ts (4 tests) 41ms
618
625
  ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 5ms
619
- ✓  node  test/components/account/Layout.test.tsx (6 tests) 54ms
620
- ✓  node  test/server/rootSpan.test.ts (4 tests) 25ms
621
- ✓  node  test/pages/api/preview.test.ts (2 tests) 6ms
622
- ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 3ms
623
- ✓  node  test/components/search/SearchInput.test.ts (6 tests) 6ms
624
- ✓  node  test/sdk/recommendations/vrn.test.ts (5 tests) 4ms
625
- ✓  node  test/utils/retry.test.ts (5 tests) 11ms
626
- ✓  node  test/server/cms/index.test.ts (2 tests) 4ms
627
- ✓  node  test/sdk/account/getB2BSessionClaims.test.ts (4 tests) 6ms
628
- ✓  node  test/utils/userOrderStatus.test.ts (4 tests) 4ms
629
- ✓  node  test/sdk/recommendations/useRecommendationUserId.test.ts (4 tests) 184ms
630
- ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 37ms
631
- ✓  node  test/utils/unlockResponse.test.ts (11 tests) 4ms
632
- ✓  node  test/sdk/recommendations/useRecommendations.test.ts (2 tests) 4ms
633
- ✓  node  test/utils/isLocalHost.test.ts (7 tests) 3ms
634
- ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (4 tests) 29ms
635
- ✓  node  test/sdk/session/toSessionInput.test.ts (3 tests) 4ms
636
- ✓  node  test/components/account/orders/getOrderTotalLabel.test.ts (6 tests) 3ms
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
629
+ ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 4ms
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
640
+ ✓  node  test/utils/unlockResponse.test.ts (11 tests) 5ms
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
637
647
  ✓  node  test/components/account/orders/listOrdersLabels.test.ts (2 tests) 3ms
638
- ✓  node  test/utils/getCookie.test.ts (4 tests) 3ms
639
- ✓  node  test/sdk/session/isSignInAreaResolved.test.ts (3 tests) 2ms
640
- ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 3ms
641
- ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 31ms
642
- ✓  node  test/instrumentation.test.ts (3 tests) 5ms
643
- ✓  node  test/components/account/orders/orderDetailsLabels.test.ts (1 test) 3ms
644
- ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 8ms
645
-
646
-  Test Files  127 passed (127)
647
-  Tests  992 passed (992)
648
-  Start at  23:13:01
649
-  Duration  45.70s (transform 10.32s, setup 0ms, import 40.02s, tests 16.96s, environment 56.91s)
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)
650
661
 
package/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
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
+
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)
13
+
14
+ **Note:** Version bump only for package @faststore/core
15
+
6
16
  ## [4.9.2](https://github.com/vtex/faststore/compare/v4.9.1...v4.9.2) (2026-10-02)
7
17
 
8
18
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "4.9.2",
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.2",
75
- "@faststore/diagnostics": "4.9.2",
76
- "@faststore/lighthouse": "4.9.2",
77
- "@faststore/ui": "4.9.2",
78
- "@faststore/sdk": "4.9.2"
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
+ })