@faststore/core 4.10.1-dev.5 → 4.10.1-dev.6

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.10.1-dev.4 generate /home/runner/work/faststore/faststore/packages/core
2
+ > @faststore/core@4.10.1-dev.5 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.10.1-dev.4 gen-types /home/runner/work/faststore/faststore/packages/core
6
+ > @faststore/core@4.10.1-dev.5 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.10.1-dev.4 cache-graphql /home/runner/work/faststore/faststore/packages/core
22
+ > @faststore/core@4.10.1-dev.5 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.10.1-dev.4 test /home/runner/work/faststore/faststore/packages/core
2
+ > @faststore/core@4.10.1-dev.5 test /home/runner/work/faststore/faststore/packages/core
3
3
  > vitest run
4
4
 
5
5
 
@@ -7,27 +7,31 @@
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) 1277ms
10
+ ✓  browser  test/sdk/ui/useScrollRestoration.browser.test.ts (43 tests) 1091ms
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) 2676ms
15
- ✓ shows an error state when the list fails to load  421ms
16
- ✓ keeps Confirm disabled when the selected contract is filtered out  319ms
17
- ✓  browser  test/components/account/cards/MyAccountListCards.browser.test.tsx (16 tests) 2958ms
18
- ✓ shows the tab bar and renders Personal cards by default  1258ms
19
- ✓ paginates when there are more than 25 cards  429ms
20
- ✓  browser  test/components/account/orders/BuyingPolicyAlert.browser.test.tsx (13 tests) 2902ms
21
- ✓ opens the reject confirmation modal with the default English copy  1073ms
22
- ✓ opens the reject confirmation modal with CMS-provided labels  445ms
23
- ✓ shows the CMS-provided toast when a policy is rejected  308ms
24
- ✓  browser  test/components/ui/RichText/RichText.browser.test.tsx (8 tests) 454ms
25
- ✓  browser  test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx (13 tests) 191ms
26
- ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (10 tests) 1460ms
27
- ✓ renders the menu view with contract details and manage footer  856ms
28
- ✓  browser  test/sdk/localization/useBindingSelector.browser.test.tsx (9 tests) 49ms
29
- ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (6 tests) 34ms
30
- ✓  browser  test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx (8 tests) 129ms
14
+ ✓  browser  test/components/account/ContractSwitcher.browser.test.tsx (22 tests) 4449ms
15
+ ✓ shows an error state when the list fails to load  1009ms
16
+ ✓ requires selecting a contract before Confirm is enabled, then switches (REQ-03)  566ms
17
+ ✓ keeps Confirm disabled until a different contract is selected  389ms
18
+ ✓ keeps Confirm disabled when switching is unavailable  458ms
19
+ ✓  browser  test/components/account/cards/MyAccountListCards.browser.test.tsx (16 tests) 2611ms
20
+ ✓ shows the tab bar and renders Personal cards by default  1155ms
21
+ ✓ paginates when there are more than 25 cards  411ms
22
+ ✓  browser  test/components/account/orders/BuyingPolicyAlert.browser.test.tsx (13 tests) 3396ms
23
+ ✓ opens the reject confirmation modal with the default English copy  1312ms
24
+ ✓ opens the reject confirmation modal with CMS-provided labels  558ms
25
+ ✓ shows the default English toast when a policy is rejected  350ms
26
+ ✓  browser  test/components/ui/RichText/RichText.browser.test.tsx (8 tests) 559ms
27
+ ✓  browser  test/sdk/product/usePageProductsQuery.browser.test.ts (9 tests) 119ms
28
+ ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (10 tests) 1598ms
29
+ ✓ renders the menu view with contract details and manage footer  844ms
30
+ ✓  browser  test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx (13 tests) 251ms
31
+ ✓  browser  test/sdk/localization/useBindingSelector.browser.test.tsx (9 tests) 94ms
32
+ ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (6 tests) 32ms
33
+ ✓  browser  test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx (8 tests) 74ms
34
+ ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 56ms
31
35
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
32
36
  Error in optimistic validation: TypeError: Failed to parse URL from /api/graphql?operationName=ValidateCartMutation&operationHash=3c71b0d3fc2bade14df68abfa271858beb361b12
33
37
   at node:internal/deps/undici/undici:16416:13
@@ -54,15 +58,13 @@ Browserslist: browsers data (caniuse-lite) is 8 months old. Please run:
54
58
  }
55
59
  }
56
60
 
57
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 56ms
58
61
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx > RecommendationShelf > renders nothing when there is an error
59
62
  Error fetching recommendations undefined boom for campaign vrn:recommendations:acc:rec-top-items-v2:campaign-1
60
63
 
61
- ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 111ms
62
- ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (7 tests) 17ms
63
- ✓  browser  test/components/CartSidebar/CartSidebar.browser.test.tsx (5 tests) 99ms
64
- ✓  browser  test/components/sections/Account/AccountOrderDetails.browser.test.tsx (4 tests) 175ms
65
- ✓  browser  test/components/navigation/Navbar.browser.test.tsx (3 tests) 79ms
64
+ ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 114ms
65
+ ✓  browser  test/components/CartSidebar/CartSidebar.browser.test.tsx (5 tests) 115ms
66
+ ✓  browser  test/components/sections/Account/AccountOrderDetails.browser.test.tsx (4 tests) 127ms
67
+ ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (7 tests) 9ms
66
68
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > posts the password and redirects to the requested return path
67
69
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
68
70
 
@@ -244,7 +246,6 @@ act(() => {
244
246
  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
245
247
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
246
248
 
247
- ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (8 tests) 537ms
248
249
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > shows service unavailable when the login request fails
249
250
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
250
251
 
@@ -293,8 +294,10 @@ act(() => {
293
294
  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
294
295
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
295
296
 
296
- ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 747ms
297
- ✓ renders the password protection form  350ms
297
+ ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 678ms
298
+ ✓ renders the password protection form  349ms
299
+ ✓  browser  test/components/navigation/Navbar.browser.test.tsx (3 tests) 62ms
300
+ ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (8 tests) 371ms
298
301
  stderr | test/sdk/session/validateSession.browser.test.ts
299
302
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
300
303
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -303,8 +306,8 @@ This ensures that you're testing the behavior the user would see in the browser.
303
306
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
304
307
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
305
308
 
306
- ✓  browser  test/sdk/session/validateSession.browser.test.ts (9 tests) 14ms
307
- ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (5 tests) 43ms
309
+ ✓  browser  test/sdk/session/validateSession.browser.test.ts (9 tests) 13ms
310
+ ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (5 tests) 49ms
308
311
  stderr | test/components/cms/RenderSections.browser.test.tsx
309
312
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
310
313
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -313,8 +316,9 @@ This ensures that you're testing the behavior the user would see in the browser.
313
316
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
314
317
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
315
318
 
316
- ✓  browser  test/components/navigation/NavbarSlider.browser.test.tsx (2 tests) 43ms
317
- ✓  browser  test/components/cms/RenderSections.browser.test.tsx (5 tests) 89ms
319
+ ✓  browser  test/components/cms/RenderSections.browser.test.tsx (5 tests) 92ms
320
+ ✓  browser  test/components/navigation/NavbarSlider.browser.test.tsx (2 tests) 31ms
321
+ ✓  browser  test/sdk/product/usePageProductsQuery.deliveryPromise.browser.test.ts (3 tests) 25ms
318
322
  stderr | test/sdk/localization/store-url.browser.test.ts
319
323
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
320
324
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -323,15 +327,15 @@ This ensures that you're testing the behavior the user would see in the browser.
323
327
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
324
328
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
325
329
 
326
- ✓  browser  test/components/account/Menu.browser.test.tsx (6 tests) 307ms
327
- ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 5ms
328
- ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 40ms
329
- ✓  browser  test/components/templates/SearchPage/SearchWrapper.browser.test.tsx (3 tests) 28ms
330
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx (2 tests) 133ms
331
- ✓  browser  test/components/sections/Account/AccountListQuotes.browser.test.tsx (4 tests) 123ms
332
- ✓  browser  test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx (3 tests) 52ms
333
- ✓  browser  test/components/navigation/NavbarAccountArea.browser.test.tsx (4 tests) 55ms
334
- ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 16ms
330
+ ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 6ms
331
+ ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 29ms
332
+ ✓  browser  test/components/account/Menu.browser.test.tsx (6 tests) 352ms
333
+ ✓  browser  test/components/templates/SearchPage/SearchWrapper.browser.test.tsx (3 tests) 41ms
334
+ ✓  browser  test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx (2 tests) 111ms
335
+ ✓  browser  test/components/sections/Account/AccountListQuotes.browser.test.tsx (4 tests) 145ms
336
+ ✓  browser  test/components/navigation/NavbarAccountArea.browser.test.tsx (4 tests) 59ms
337
+ ✓  browser  test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx (3 tests) 54ms
338
+ ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 14ms
335
339
  stderr | test/components/account/doLogout.browser.test.ts
336
340
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
337
341
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -340,10 +344,10 @@ This ensures that you're testing the behavior the user would see in the browser.
340
344
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
341
345
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
342
346
 
343
- ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 15ms
344
- ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (5 tests) 366ms
345
- ✓  browser  test/sdk/account/useRefreshToken.browser.test.ts (4 tests) 151ms
346
- ✓  browser  test/components/ui/LocalizationButton.browser.test.tsx (3 tests) 43ms
347
+ ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 23ms
348
+ ✓  browser  test/sdk/account/useRefreshToken.browser.test.ts (4 tests) 143ms
349
+ ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (5 tests) 307ms
350
+ ✓  browser  test/components/ui/LocalizationButton.browser.test.tsx (3 tests) 50ms
347
351
  stderr | test/sdk/account/clearPersistedSessionState.browser.test.ts
348
352
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
349
353
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -352,15 +356,15 @@ This ensures that you're testing the behavior the user would see in the browser.
352
356
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
353
357
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
354
358
 
355
- ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (4 tests) 20ms
356
- ✓  browser  test/components/sections/Account/AccountOrderDelivery.browser.test.tsx (2 tests) 69ms
357
- ✓  browser  test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx (2 tests) 103ms
358
- ✓  browser  test/components/sections/Account/AccountOrderStatus.browser.test.tsx (3 tests) 135ms
359
- ✓  browser  test/components/sections/Account/AccountListCards.browser.test.tsx (3 tests) 95ms
360
- ✓  browser  test/components/region/useRegion.browser.test.ts (1 test) 25ms
361
- ✓  browser  test/components/sections/Account/AccountOrdersList.browser.test.tsx (2 tests) 30ms
362
- ✓  browser  test/components/account/orders/SummaryCard.browser.test.tsx (2 tests) 85ms
363
- ✓  browser  test/components/account/OrganizationSignInButton.browser.test.tsx (2 tests) 253ms
359
+ ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (4 tests) 34ms
360
+ ✓  browser  test/components/sections/Account/AccountOrderDelivery.browser.test.tsx (2 tests) 84ms
361
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx (2 tests) 99ms
362
+ ✓  browser  test/components/sections/Account/AccountOrderStatus.browser.test.tsx (3 tests) 94ms
363
+ ✓  browser  test/components/sections/Account/AccountListCards.browser.test.tsx (3 tests) 93ms
364
+ ✓  browser  test/components/region/useRegion.browser.test.ts (1 test) 26ms
365
+ ✓  browser  test/components/sections/Account/AccountOrdersList.browser.test.tsx (2 tests) 29ms
366
+ ✓  browser  test/components/account/orders/SummaryCard.browser.test.tsx (2 tests) 98ms
367
+ ✓  browser  test/components/account/OrganizationSignInButton.browser.test.tsx (2 tests) 260ms
364
368
  stderr | test/sdk/session/sessionStore.browser.test.ts
365
369
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
366
370
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -369,14 +373,14 @@ This ensures that you're testing the behavior the user would see in the browser.
369
373
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
370
374
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
371
375
 
372
- ✓  browser  test/sdk/session/sessionStore.browser.test.ts (2 tests) 5ms
373
- ✓  browser  test/components/account/orders/DeliveryOptionAccordionDeliveryInfo.browser.test.tsx (1 test) 33ms
376
+ ✓  browser  test/sdk/session/sessionStore.browser.test.ts (2 tests) 7ms
377
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordionDeliveryInfo.browser.test.tsx (1 test) 46ms
374
378
  ✓  browser  test/utils/shouldBypassRefreshToken.browser.test.ts (5 tests) 6ms
375
- ✓  browser  test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts (1 test) 3ms
376
- ✓  browser  test/components/account/orders/StatusCard.browser.test.tsx (2 tests) 100ms
377
- ✓  browser  test/components/account/orders/DeliveryCard.browser.test.tsx (1 test) 46ms
378
- ✓  browser  test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx (1 test) 31ms
379
- ✓  browser  test/components/skeletons/ProductGridSkeleton.browser.test.tsx (2 tests) 41ms
379
+ ✓  browser  test/components/account/orders/StatusCard.browser.test.tsx (2 tests) 94ms
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) 53ms
382
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx (1 test) 54ms
383
+ ✓  browser  test/components/skeletons/ProductGridSkeleton.browser.test.tsx (2 tests) 43ms
380
384
  stderr | test/sdk/account/refreshToken.browser.test.ts
381
385
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
382
386
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -385,12 +389,12 @@ This ensures that you're testing the behavior the user would see in the browser.
385
389
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
386
390
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
387
391
 
388
- ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 30ms
389
- ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 5ms
390
- ✓  node  test/server/password-protection-service.test.ts (22 tests) 37ms
391
- ✓  browser  test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx (1 test) 232ms
392
- ✓  node  test/server/content/service.test.ts (27 tests) 39ms
393
- ✓  node  test/pages/api/switch-contract.test.ts (25 tests) 98ms
392
+ ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 31ms
393
+ ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 6ms
394
+ ✓  node  test/server/password-protection-service.test.ts (22 tests) 36ms
395
+ ✓  browser  test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx (1 test) 226ms
396
+ ✓  node  test/server/content/service.test.ts (27 tests) 40ms
397
+ ✓  node  test/pages/api/switch-contract.test.ts (25 tests) 84ms
394
398
  stderr | test/sdk/deliveryPromise/provider.effect.test.tsx > DeliveryPromiseProvider pickup fetch > clears the loading flag after a failed request and retries on the next update
395
399
  Failed to fetch pickup points: Error: network
396
400
  at /home/runner/work/faststore/faststore/packages/core/test/sdk/deliveryPromise/provider.effect.test.tsx:250:30
@@ -417,9 +421,9 @@ This ensures that you're testing the behavior the user would see in the browser.
417
421
  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)
418
422
  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)
419
423
 
420
- ✓  node  test/sdk/deliveryPromise/provider.effect.test.tsx (9 tests) 98ms
421
- ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 16ms
422
- ✓  node  test/utils/match-url.test.ts (10 tests) 10ms
424
+ ✓  node  test/sdk/deliveryPromise/provider.effect.test.tsx (9 tests) 80ms
425
+ ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 15ms
426
+ ✓  node  test/utils/match-url.test.ts (10 tests) 11ms
423
427
  stderr | test/pages/api/graphql.test.ts > /api/graphql error status propagation > propagates the upstream 400 from a wrapped BadRequestError instead of 500
424
428
  Graphql execution returned with error: [
425
429
  {
@@ -561,13 +565,13 @@ This ensures that you're testing the behavior the user would see in the browser.
561
565
  extensions: { status: 401, type: 'UnauthorizedError' }
562
566
  }
563
567
 
564
- ✓  node  test/pages/api/graphql.test.ts (30 tests) 42ms
565
- ✓  node  test/pages/api/unlock.test.ts (15 tests) 150ms
566
- ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (14 tests) 14ms
568
+ ✓  node  test/pages/api/graphql.test.ts (30 tests) 51ms
569
+ ✓  node  test/pages/api/unlock.test.ts (15 tests) 185ms
570
+ ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (14 tests) 12ms
567
571
  stderr | test/components/search/SearchInputComponent.test.tsx
568
572
  Warning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?
569
573
 
570
- ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 9ms
574
+ ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 12ms
571
575
  stderr | test/components/search/SearchInputComponent.test.tsx > SearchInput (OES integration) > renders without crashing
572
576
  Warning: React does not recognize the `visibleDropdown` prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase `visibledropdown` instead. If you accidentally passed it from a parent component, remove it from the DOM element.
573
577
  at div
@@ -628,95 +632,94 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
628
632
  at Suspense
629
633
  at Wrapper (/home/runner/work/faststore/faststore/packages/core/test/components/search/SearchInputComponent.test.tsx:106:20)
630
634
 
631
- ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 82ms
632
- ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (15 tests) 412ms
633
- ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 44ms
634
- ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (11 tests) 825ms
635
- ✓ navigates with the search term on blur  360ms
636
- ✓ navigates without a label param when the search is cleared  361ms
635
+ ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 62ms
636
+ ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (15 tests) 410ms
637
+ ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 52ms
637
638
  stderr | test/pages/pvt/account/cards/index.test.ts > Cards page getServerSideProps > redirects to /pvt/account/403 when the query fails with 401/403
638
639
  { extensions: { status: 401 } }
639
640
 
640
641
  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
641
642
  { extensions: { status: 502 } }
642
643
 
643
- ✓  node  test/pages/pvt/account/cards/index.test.ts (8 tests) 11ms
644
- ✓  node  test/components/quotes/quoteFilters.test.ts (26 tests) 25ms
645
- ✓  node  test/utils/productJsonLd.test.ts (36 tests) 15ms
646
- ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 13ms
647
- ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 10ms
644
+ ✓  node  test/pages/pvt/account/cards/index.test.ts (8 tests) 8ms
645
+ ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (11 tests) 869ms
646
+ ✓ navigates with the search term on blur  363ms
647
+ ✓ navigates without a label param when the search is cleared  361ms
648
+ ✓  node  test/components/quotes/quoteFilters.test.ts (26 tests) 28ms
649
+ ✓  node  test/utils/productJsonLd.test.ts (36 tests) 13ms
650
+ ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 7ms
651
+ ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 16ms
648
652
  ✓  node  test/utils/clearCookies.test.ts (20 tests) 28ms
649
- ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 7ms
653
+ ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 8ms
650
654
  stderr | test/sdk/account/useSetPassword.test.ts > useSetPassword > maps wrongcredentials error responses to the existing message
651
655
  Set password request failed: Bad Request
652
656
 
653
- ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 52ms
654
- ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 55ms
655
- ✓  node  test/utils/localization/localizeRedirectDestination.test.ts (11 tests) 7ms
656
- ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (8 tests) 158ms
657
- ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (14 tests) 184ms
658
- ✓  node  test/utils/localization/hreflang.test.ts (8 tests) 9ms
659
- ✓  node  test/server/cms/index.test.ts (7 tests) 10ms
660
- ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (10 tests) 121ms
657
+ ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 46ms
658
+ ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 42ms
659
+ ✓  node  test/utils/localization/localizeRedirectDestination.test.ts (11 tests) 5ms
660
+ ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (8 tests) 182ms
661
+ ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (14 tests) 178ms
662
+ ✓  node  test/utils/localization/hreflang.test.ts (8 tests) 6ms
663
+ ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (10 tests) 132ms
664
+ ✓  node  test/server/cms/index.test.ts (7 tests) 12ms
661
665
  ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (13 tests) 10ms
662
- ✓  node  test/server/content/promiseCache.test.ts (10 tests) 12ms
663
- ✓  node  test/sdk/redirects/index.test.ts (7 tests) 53ms
664
- ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 57ms
665
- ✓  node  test/proxy.localization.test.ts (5 tests) 31ms
666
- ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 44ms
667
- ✓  node  test/components/account/orders/getDeliveryOptionLabel.test.ts (21 tests) 7ms
668
- ✓  node  test/sdk/graphql/retryPolicy.test.ts (18 tests) 25ms
669
- ✓  node  test/server/cms/myAccountDefaultSections.test.ts (9 tests) 10ms
670
- ✓  node  test/utils/productJsonLdRendering.test.tsx (5 tests) 16ms
671
- ✓  node  test/utils/trustedForwardedHost.test.ts (27 tests) 12ms
672
- ✓  node  test/server/index.test.ts (7 tests) 845ms
673
- ✓ should handle options and execute  315ms
674
- ✓  node  test/proxy.test.ts (4 tests) 31ms
675
- ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 46ms
676
- ✓  node  test/utils/shouldBypassRefreshToken.test.ts (14 tests) 5ms
677
- ✓  node  test/server/webops-api.test.ts (9 tests) 11ms
678
- ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 46ms
679
- ✓  node  test/components/account/orders/StatusCard.test.ts (3 tests) 31ms
666
+ ✓  node  test/server/content/promiseCache.test.ts (10 tests) 16ms
667
+ ✓  node  test/sdk/redirects/index.test.ts (7 tests) 49ms
668
+ ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 48ms
669
+ ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 57ms
670
+ ✓  node  test/proxy.localization.test.ts (5 tests) 113ms
671
+ ✓  node  test/components/account/orders/getDeliveryOptionLabel.test.ts (21 tests) 9ms
672
+ ✓  node  test/sdk/graphql/retryPolicy.test.ts (18 tests) 26ms
673
+ ✓  node  test/server/cms/myAccountDefaultSections.test.ts (9 tests) 8ms
674
+ ✓  node  test/utils/productJsonLdRendering.test.tsx (5 tests) 28ms
675
+ ✓  node  test/server/index.test.ts (7 tests) 667ms
676
+ ✓  node  test/utils/trustedForwardedHost.test.ts (27 tests) 8ms
677
+ ✓  node  test/proxy.test.ts (4 tests) 16ms
678
+ ✓  node  test/utils/shouldBypassRefreshToken.test.ts (14 tests) 8ms
679
+ ✓  node  test/server/webops-api.test.ts (9 tests) 16ms
680
+ ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 45ms
681
+ ✓  node  test/components/account/orders/StatusCard.test.ts (3 tests) 22ms
682
+ ✓  node  test/sdk/localization/store-url.test.ts (4 tests) 5ms
683
+ ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 51ms
680
684
  ✓  node  test/sdk/deliveryPromise/queries.test.ts (4 tests) 10ms
681
- ✓  node  test/sdk/localization/store-url.test.ts (4 tests) 6ms
682
- ✓  node  test/sdk/cart/waitForSessionValidated.test.ts (5 tests) 11ms
683
- ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 8ms
684
- ✓  node  test/server/cms/global.test.ts (3 tests) 5ms
685
- ✓  node  test/sdk/deliveryPromise/provider.test.ts (11 tests) 6ms
686
- ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 29ms
687
- ✓  node  test/components/account/Layout.test.tsx (6 tests) 64ms
688
- ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 7ms
689
- ✓  node  test/server/rootSpan.test.ts (4 tests) 41ms
690
- ✓  node  test/pages/api/preview.test.ts (2 tests) 8ms
691
- ✓  node  test/components/search/SearchInput.test.ts (6 tests) 7ms
685
+ ✓  node  test/sdk/cart/waitForSessionValidated.test.ts (5 tests) 12ms
686
+ ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 9ms
687
+ ✓  node  test/server/cms/global.test.ts (3 tests) 4ms
688
+ ✓  node  test/sdk/deliveryPromise/provider.test.ts (11 tests) 7ms
689
+ ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 32ms
690
+ ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 8ms
691
+ ✓  node  test/components/account/Layout.test.tsx (6 tests) 65ms
692
+ ✓  node  test/server/rootSpan.test.ts (4 tests) 46ms
693
+ ✓  node  test/pages/api/preview.test.ts (2 tests) 9ms
694
+ ✓  node  test/components/search/SearchInput.test.ts (6 tests) 8ms
692
695
  ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 4ms
693
696
  ✓  node  test/pages/pvt/account/403.test.ts (5 tests) 6ms
694
- ✓  node  test/sdk/recommendations/vrn.test.ts (5 tests) 6ms
695
- ✓  node  test/components/region/useRegion.test.ts (1 test) 25ms
696
- ✓  node  test/utils/retry.test.ts (5 tests) 14ms
697
- ✓  node  test/sdk/account/getB2BSessionClaims.test.ts (4 tests) 4ms
698
- ✓  node  test/utils/userOrderStatus.test.ts (4 tests) 4ms
699
- ✓  node  test/sdk/recommendations/useRecommendationUserId.test.ts (4 tests) 192ms
700
- ✓  node  test/utils/unlockResponse.test.ts (11 tests) 7ms
701
- ✓  node  test/sdk/recommendations/useRecommendations.test.ts (2 tests) 4ms
702
- ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 54ms
703
- ✓  node  test/sdk/deliveryPromise/reducer.test.ts (2 tests) 5ms
704
- ✓  node  test/utils/isLocalHost.test.ts (7 tests) 6ms
705
- ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (4 tests) 32ms
697
+ ✓  node  test/components/region/useRegion.test.ts (1 test) 27ms
698
+ ✓  node  test/utils/retry.test.ts (5 tests) 15ms
699
+ ✓  node  test/sdk/recommendations/vrn.test.ts (5 tests) 5ms
700
+ ✓  node  test/sdk/account/getB2BSessionClaims.test.ts (4 tests) 5ms
701
+ ✓  node  test/utils/userOrderStatus.test.ts (4 tests) 5ms
702
+ ✓  node  test/sdk/recommendations/useRecommendationUserId.test.ts (4 tests) 193ms
703
+ ✓  node  test/utils/unlockResponse.test.ts (11 tests) 6ms
704
+ ✓  node  test/sdk/recommendations/useRecommendations.test.ts (2 tests) 7ms
705
+ ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 49ms
706
+ ✓  node  test/sdk/deliveryPromise/reducer.test.ts (2 tests) 6ms
707
+ ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (4 tests) 45ms
708
+ ✓  node  test/utils/isLocalHost.test.ts (7 tests) 4ms
706
709
  ✓  node  test/sdk/session/toSessionInput.test.ts (3 tests) 5ms
707
710
  ✓  node  test/components/account/orders/getOrderTotalLabel.test.ts (6 tests) 5ms
708
- ✓  node  test/components/account/orders/listOrdersLabels.test.ts (2 tests) 4ms
709
- ✓  node  test/utils/getCookie.test.ts (4 tests) 5ms
710
- ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 42ms
711
- ✓  node  test/sdk/session/isSignInAreaResolved.test.ts (3 tests) 2ms
711
+ ✓  node  test/components/account/orders/listOrdersLabels.test.ts (2 tests) 3ms
712
+ ✓  node  test/utils/getCookie.test.ts (4 tests) 7ms
713
+ ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 40ms
714
+ ✓  node  test/sdk/session/isSignInAreaResolved.test.ts (3 tests) 4ms
712
715
  ✓  node  test/utils/filterChannel.test.ts (2 tests) 4ms
713
- ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 4ms
714
- ✓  node  test/instrumentation.test.ts (3 tests) 6ms
715
- ✓  node  test/components/account/orders/orderDetailsLabels.test.ts (1 test) 3ms
716
- ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 8ms
717
-
718
-  Test Files  139 passed (139)
719
-  Tests  1147 passed (1147)
720
-  Start at  21:11:55
721
-  Duration  61.46s (transform 15.20s, setup 0ms, import 56.17s, tests 21.35s, environment 74.47s)
716
+ ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 3ms
717
+ ✓  node  test/instrumentation.test.ts (3 tests) 5ms
718
+ ✓  node  test/components/account/orders/orderDetailsLabels.test.ts (1 test) 4ms
719
+ ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 11ms
720
+
721
+  Test Files  141 passed (141)
722
+  Tests  1159 passed (1159)
723
+  Start at  22:57:57
724
+  Duration  65.40s (transform 15.23s, setup 0ms, import 57.33s, tests 23.25s, environment 80.48s)
722
725
 
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.10.1-dev.6](https://github.com/vtex/faststore/compare/v4.10.1-dev.5...v4.10.1-dev.6) (2026-10-28)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **core:** document that gallery scope keys reach the request URL ([dc61bdf](https://github.com/vtex/faststore/commit/dc61bdf2411b88ac15d29066a668a26f521785de))
11
+ - **core:** hash the B2B contract scope sent with gallery queries ([f8bfac9](https://github.com/vtex/faststore/commit/f8bfac9f5dd4fbda848982a701e54eb1f3d2c14b))
12
+ - **core:** refetch PLP first page for B2B buyers so the contract assortment applies ([72ddfdf](https://github.com/vtex/faststore/commit/72ddfdf06b7cc7a74aca7036cce37c400fef2cc0))
13
+ - **core:** tell B2B buyers apart by org unit in the gallery cache key ([c7fe237](https://github.com/vtex/faststore/commit/c7fe237088515c40a49f34b46297022279c57f93))
14
+ - **core:** widen the B2B contract scope hash to 53 bits ([f05f556](https://github.com/vtex/faststore/commit/f05f556c135b914af7c1058633ca50144a41669c))
15
+
6
16
  ## [4.10.1-dev.5](https://github.com/vtex/faststore/compare/v4.10.1-dev.4...v4.10.1-dev.5) (2026-10-28)
7
17
 
8
18
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "4.10.1-dev.5",
3
+ "version": "4.10.1-dev.6",
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.10.1-dev.5",
75
- "@faststore/diagnostics": "4.10.1-dev.5",
76
- "@faststore/lighthouse": "4.10.1-dev.5",
77
- "@faststore/ui": "4.10.1-dev.5",
78
- "@faststore/sdk": "4.10.1-dev.5"
74
+ "@faststore/api": "4.10.1-dev.6",
75
+ "@faststore/lighthouse": "4.10.1-dev.6",
76
+ "@faststore/diagnostics": "4.10.1-dev.6",
77
+ "@faststore/ui": "4.10.1-dev.6",
78
+ "@faststore/sdk": "4.10.1-dev.6"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@cypress/code-coverage": "^3.12.1",
@@ -69,6 +69,69 @@ export const query = gql(`
69
69
 
70
70
  const getKey = (object: any) => JSON.stringify(object)
71
71
 
72
+ const isDeliveryPromiseEnabled = storeConfig.deliveryPromise?.enabled ?? false
73
+
74
+ /**
75
+ * Cache-only keys that scope gallery pages to the shopper context. They make
76
+ * the local cache comparison (and the SWR key) miss when a page was loaded for
77
+ * another context (e.g. the anonymous SSG page). They are not declared by the
78
+ * operation, so the API ignores them, but they are still sent with the
79
+ * variables, which go in the URL because this query is a GET operation.
80
+ * - `_postalCode`: pages are invalidated on region change. Only applied when
81
+ * deliveryPromise is enabled to avoid unnecessary cache fragmentation.
82
+ * - `_contract`: the B2B contract assortment is applied server-side from the
83
+ * session cookie, so it never shows up in the query variables. Keyed off
84
+ * the presence of a `b2b` session (not `customerId`'s value) because
85
+ * `customerId` can resolve to an empty string for a legitimate B2B buyer
86
+ * (see `buildB2bSession` in `@faststore/api`). The fallback chain still
87
+ * forces the re-fetch for them: the real `customerId`, when present, keeps
88
+ * invalidating the cache on contract switch; otherwise the organizational
89
+ * unit (`unitId`) tells buyers of different units apart; a generic marker is
90
+ * the last resort. Since the value ends up in the URL, it is hashed so the
91
+ * raw contract or unit id is not written to access, CDN or RUM logs as is.
92
+ * These ids are not secrets (the session already hands them to the
93
+ * browser), so the hash is cosmetic, not a privacy control: it is unkeyed
94
+ * and a guessable id can be recovered from it. The URL still differs per
95
+ * contract, so a contract page is never served from the anonymous page's
96
+ * cache entry.
97
+ */
98
+ const getShopperScope = ({
99
+ postalCode,
100
+ contract,
101
+ }: {
102
+ postalCode?: string | null
103
+ contract?: string | null
104
+ }) => ({
105
+ ...(isDeliveryPromiseEnabled && { _postalCode: postalCode ?? '' }),
106
+ ...(contract && { _contract: hashContractScope(contract) }),
107
+ })
108
+
109
+ /** The id a B2B session is scoped by, or `null` outside a B2B session. */
110
+ const getContractScope = (
111
+ b2b?: { customerId?: string | null; unitId?: string | null } | null
112
+ ) => (b2b ? b2b.customerId || b2b.unitId || 'b2b' : null)
113
+
114
+ /**
115
+ * 53-bit string hash (cyrb53): wide enough that two contracts in the same
116
+ * browser never share a cache key in practice, without exposing the raw id.
117
+ */
118
+ export function hashContractScope(value: string) {
119
+ let h1 = 0xdeadbeef
120
+ let h2 = 0x41c6ce57
121
+ for (const char of value) {
122
+ const code = char.codePointAt(0) ?? 0
123
+ h1 = Math.imul(h1 ^ code, 2654435761)
124
+ h2 = Math.imul(h2 ^ code, 1597334677)
125
+ }
126
+ h1 =
127
+ Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^
128
+ Math.imul(h2 ^ (h2 >>> 13), 3266489909)
129
+ h2 =
130
+ Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^
131
+ Math.imul(h1 ^ (h1 >>> 13), 3266489909)
132
+ return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36)
133
+ }
134
+
72
135
  interface UseCreateUseGalleryPageProps {
73
136
  initialPages: ClientManyProductsQueryWithSearchIdQuery
74
137
  serverManyProductsVariables: ClientManyProductsQueryWithSearchIdQueryVariables
@@ -82,21 +145,17 @@ export const useCreateUseGalleryPage = (
82
145
  ) => {
83
146
  const initialPages = params?.initialPages?.search ? [params.initialPages] : []
84
147
 
85
- // Seed the cache with the same region-aware key shape that useGalleryPage uses,
86
- // so hasSameVariables correctly matches on first mount and avoids an unnecessary
87
- // re-fetch of the SSR-loaded page.
88
- const isDeliveryPromiseEnabled = storeConfig.deliveryPromise?.enabled ?? false
148
+ // Seed the cache with the same key shape that useGalleryPage uses, so
149
+ // hasSameVariables matches on first mount and avoids an unnecessary re-fetch
150
+ // of the SSR-loaded page. The SSG page is built without a buyer, so it is
151
+ // seeded without a contract: B2B buyers re-fetch it with their assortment.
89
152
  const { postalCode } = useSession()
90
153
  const initialVariables = params?.serverManyProductsVariables
91
154
  ? [
92
- getKey(
93
- isDeliveryPromiseEnabled
94
- ? {
95
- ...params.serverManyProductsVariables,
96
- _postalCode: postalCode ?? '',
97
- }
98
- : params.serverManyProductsVariables
99
- ),
155
+ getKey({
156
+ ...params.serverManyProductsVariables,
157
+ ...getShopperScope({ postalCode }),
158
+ }),
100
159
  ]
101
160
  : []
102
161
 
@@ -113,7 +172,7 @@ export const useCreateUseGalleryPage = (
113
172
  itemsPerPage,
114
173
  } = useSearch()
115
174
 
116
- const { postalCode, isValidating: isSessionValidating } = useSession()
175
+ const { postalCode, b2b, isValidating: isSessionValidating } = useSession()
117
176
 
118
177
  const localizedVariables = useLocalizedVariables({
119
178
  first: itemsPerPage,
@@ -123,15 +182,15 @@ export const useCreateUseGalleryPage = (
123
182
  selectedFacets,
124
183
  })
125
184
 
126
- // Include postalCode in the cache key so pages are invalidated on region change.
127
- // _postalCode is not sent to the API — it only affects the local cache comparison.
128
- // Only applied when deliveryPromise is enabled to avoid unnecessary cache fragmentation.
185
+ // Keyed on the contract id, not the `b2b` object, which is rebuilt on
186
+ // every session update.
187
+ const contract = getContractScope(b2b)
129
188
  const localizedVariablesWithRegion = useMemo(
130
- () =>
131
- isDeliveryPromiseEnabled
132
- ? { ...localizedVariables, _postalCode: postalCode ?? '' }
133
- : localizedVariables,
134
- [localizedVariables, postalCode]
189
+ () => ({
190
+ ...localizedVariables,
191
+ ...getShopperScope({ postalCode, contract }),
192
+ }),
193
+ [localizedVariables, postalCode, contract]
135
194
  )
136
195
 
137
196
  const hasSameVariables = deepEquals(
@@ -0,0 +1,260 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import { renderHook } from '@testing-library/react'
6
+ import { afterEach, describe, expect, it, vi } from 'vitest'
7
+
8
+ const mockUseSession = vi.hoisted(() => vi.fn())
9
+ const mockUseQuery = vi.hoisted(() => vi.fn())
10
+ const mockUseLocalizedVariables = vi.hoisted(() => vi.fn())
11
+
12
+ vi.mock('@generated', () => ({ gql: (query: unknown) => query }))
13
+ vi.mock('discovery.config', () => ({
14
+ default: { deliveryPromise: { enabled: false } },
15
+ }))
16
+ vi.mock('@faststore/sdk', () => ({
17
+ useSearch: () => ({
18
+ state: { sort: 'score_desc', term: '', selectedFacets: [] },
19
+ itemsPerPage: 12,
20
+ }),
21
+ }))
22
+ vi.mock('src/sdk/session', () => ({ useSession: mockUseSession }))
23
+ vi.mock('src/sdk/graphql/useQuery', () => ({ useQuery: mockUseQuery }))
24
+ vi.mock('src/sdk/product/useLocalizedVariables', () => ({
25
+ useLocalizedVariables: mockUseLocalizedVariables,
26
+ }))
27
+
28
+ import {
29
+ hashContractScope,
30
+ useCreateUseGalleryPage,
31
+ } from '../../../src/sdk/product/usePageProductsQuery'
32
+
33
+ const serverManyProductsVariables = {
34
+ first: 12,
35
+ after: '0',
36
+ sort: 'score_desc' as const,
37
+ term: '',
38
+ selectedFacets: [
39
+ { key: 'category-1', value: 'computers---tablets' },
40
+ { key: 'channel', value: '{"salesChannel":"1"}' },
41
+ { key: 'locale', value: 'en-CA' },
42
+ ],
43
+ sponsoredCount: 3,
44
+ }
45
+
46
+ type GalleryProps = NonNullable<Parameters<typeof useCreateUseGalleryPage>[0]>
47
+
48
+ const initialPages = {
49
+ search: { products: { pageInfo: { totalCount: 2 }, edges: [] } },
50
+ } as unknown as GalleryProps['initialPages']
51
+
52
+ const renderFirstGalleryPage = () => {
53
+ const { result: created } = renderHook(() =>
54
+ useCreateUseGalleryPage({ initialPages, serverManyProductsVariables })
55
+ )
56
+
57
+ return renderHook(() => created.current.useGalleryPage(0))
58
+ }
59
+
60
+ describe('useCreateUseGalleryPage', () => {
61
+ afterEach(() => {
62
+ vi.clearAllMocks()
63
+ })
64
+
65
+ it('reuses the SSG first page for anonymous shoppers', () => {
66
+ mockUseSession.mockReturnValue({
67
+ postalCode: null,
68
+ b2b: null,
69
+ isValidating: false,
70
+ })
71
+ mockUseLocalizedVariables.mockReturnValue(serverManyProductsVariables)
72
+ mockUseQuery.mockReturnValue({ data: null })
73
+
74
+ const { result } = renderFirstGalleryPage()
75
+
76
+ expect(mockUseQuery).toHaveBeenCalledWith(
77
+ expect.anything(),
78
+ serverManyProductsVariables,
79
+ expect.objectContaining({ doNotRun: true })
80
+ )
81
+ expect(result.current.data).toBe(initialPages)
82
+ })
83
+
84
+ it('re-fetches the first page for B2B buyers so the contract assortment applies', () => {
85
+ mockUseSession.mockReturnValue({
86
+ postalCode: null,
87
+ b2b: { customerId: 'contract-137' },
88
+ isValidating: false,
89
+ })
90
+ mockUseLocalizedVariables.mockReturnValue(serverManyProductsVariables)
91
+ mockUseQuery.mockReturnValue({ data: null })
92
+
93
+ renderFirstGalleryPage()
94
+
95
+ expect(mockUseQuery).toHaveBeenCalledWith(
96
+ expect.anything(),
97
+ {
98
+ ...serverManyProductsVariables,
99
+ _contract: hashContractScope('contract-137'),
100
+ },
101
+ expect.objectContaining({ doNotRun: false })
102
+ )
103
+ })
104
+
105
+ it('still re-fetches for B2B buyers whose customerId resolves to an empty string', () => {
106
+ // `buildB2bSession` in @faststore/api can resolve `customerId` to `''`
107
+ // (see validateSessionHelpers.ts) even for a representative with an
108
+ // active contract. The fix must not depend on the value being truthy —
109
+ // only on the `b2b` session existing — or it silently falls back to the
110
+ // anonymous behavior (reusing the unfiltered SSG page).
111
+ mockUseSession.mockReturnValue({
112
+ postalCode: null,
113
+ b2b: { customerId: '' },
114
+ isValidating: false,
115
+ })
116
+ mockUseLocalizedVariables.mockReturnValue(serverManyProductsVariables)
117
+ mockUseQuery.mockReturnValue({ data: null })
118
+
119
+ renderFirstGalleryPage()
120
+
121
+ expect(mockUseQuery).toHaveBeenCalledWith(
122
+ expect.anything(),
123
+ { ...serverManyProductsVariables, _contract: hashContractScope('b2b') },
124
+ expect.objectContaining({ doNotRun: false })
125
+ )
126
+ })
127
+
128
+ it('hashes the contract scope to a stable value', () => {
129
+ // Golden value: catches a change to the hash itself, which the other
130
+ // tests miss because they derive their expectations from it.
131
+ expect(hashContractScope('contract-137')).toBe('185k3l4sd4t')
132
+ })
133
+
134
+ it('keeps the raw contract id out of the query variables', () => {
135
+ mockUseSession.mockReturnValue({
136
+ postalCode: null,
137
+ b2b: { customerId: 'contract-137', unitId: 'unit-42' },
138
+ isValidating: false,
139
+ })
140
+ mockUseLocalizedVariables.mockReturnValue(serverManyProductsVariables)
141
+ mockUseQuery.mockReturnValue({ data: null })
142
+
143
+ renderFirstGalleryPage()
144
+
145
+ // The variables go in the GET URL, so they must not carry the raw ids.
146
+ const variables = JSON.stringify(mockUseQuery.mock.calls.at(-1)?.[1])
147
+ expect(variables).not.toContain('contract-137')
148
+ expect(variables).not.toContain('unit-42')
149
+ expect(hashContractScope('contract-a')).not.toBe(
150
+ hashContractScope('contract-b')
151
+ )
152
+ })
153
+
154
+ it('tells B2B buyers apart by organizational unit when customerId is empty', () => {
155
+ mockUseSession.mockReturnValue({
156
+ postalCode: null,
157
+ b2b: { customerId: '', unitId: 'unit-42' },
158
+ isValidating: false,
159
+ })
160
+ mockUseLocalizedVariables.mockReturnValue(serverManyProductsVariables)
161
+ mockUseQuery.mockReturnValue({ data: null })
162
+
163
+ renderFirstGalleryPage()
164
+
165
+ expect(mockUseQuery).toHaveBeenCalledWith(
166
+ expect.anything(),
167
+ {
168
+ ...serverManyProductsVariables,
169
+ _contract: hashContractScope('unit-42'),
170
+ },
171
+ expect.objectContaining({ doNotRun: false })
172
+ )
173
+ })
174
+
175
+ it('re-fetches the first page when the buyer switches contract', () => {
176
+ const contractPage = {
177
+ search: { products: { pageInfo: { totalCount: 1 }, edges: [] } },
178
+ }
179
+
180
+ mockUseSession.mockReturnValue({
181
+ postalCode: null,
182
+ b2b: { customerId: 'contract-a' },
183
+ isValidating: false,
184
+ })
185
+ mockUseLocalizedVariables.mockReturnValue(serverManyProductsVariables)
186
+ mockUseQuery.mockReturnValue({ data: contractPage })
187
+
188
+ const { result: created } = renderHook(() =>
189
+ useCreateUseGalleryPage({ initialPages, serverManyProductsVariables })
190
+ )
191
+ const { rerender } = renderHook(() => created.current.useGalleryPage(0))
192
+
193
+ mockUseSession.mockReturnValue({
194
+ postalCode: null,
195
+ b2b: { customerId: 'contract-b' },
196
+ isValidating: false,
197
+ })
198
+ rerender()
199
+
200
+ expect(mockUseQuery).toHaveBeenLastCalledWith(
201
+ expect.anything(),
202
+ {
203
+ ...serverManyProductsVariables,
204
+ _contract: hashContractScope('contract-b'),
205
+ },
206
+ expect.objectContaining({ doNotRun: false })
207
+ )
208
+ })
209
+
210
+ it('stops scoping the first page once the B2B session ends', () => {
211
+ const contractPage = {
212
+ search: { products: { pageInfo: { totalCount: 1 }, edges: [] } },
213
+ }
214
+
215
+ mockUseSession.mockReturnValue({
216
+ postalCode: null,
217
+ b2b: { customerId: 'contract-a' },
218
+ isValidating: false,
219
+ })
220
+ mockUseLocalizedVariables.mockReturnValue(serverManyProductsVariables)
221
+ mockUseQuery.mockReturnValue({ data: contractPage })
222
+
223
+ const { result: created } = renderHook(() =>
224
+ useCreateUseGalleryPage({ initialPages, serverManyProductsVariables })
225
+ )
226
+ const { rerender } = renderHook(() => created.current.useGalleryPage(0))
227
+
228
+ mockUseSession.mockReturnValue({
229
+ postalCode: null,
230
+ b2b: null,
231
+ isValidating: false,
232
+ })
233
+ rerender()
234
+
235
+ expect(mockUseQuery).toHaveBeenLastCalledWith(
236
+ expect.anything(),
237
+ serverManyProductsVariables,
238
+ expect.objectContaining({ doNotRun: false })
239
+ )
240
+ })
241
+
242
+ it('ignores the postal code while deliveryPromise is disabled', () => {
243
+ mockUseSession.mockReturnValue({
244
+ postalCode: '01310-100',
245
+ b2b: null,
246
+ isValidating: false,
247
+ })
248
+ mockUseLocalizedVariables.mockReturnValue(serverManyProductsVariables)
249
+ mockUseQuery.mockReturnValue({ data: null })
250
+
251
+ const { result } = renderFirstGalleryPage()
252
+
253
+ expect(mockUseQuery).toHaveBeenCalledWith(
254
+ expect.anything(),
255
+ serverManyProductsVariables,
256
+ expect.objectContaining({ doNotRun: true })
257
+ )
258
+ expect(result.current.data).toBe(initialPages)
259
+ })
260
+ })
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import { renderHook } from '@testing-library/react'
6
+ import { afterEach, describe, expect, it, vi } from 'vitest'
7
+
8
+ const mockUseSession = vi.hoisted(() => vi.fn())
9
+ const mockUseQuery = vi.hoisted(() => vi.fn())
10
+ const mockUseLocalizedVariables = vi.hoisted(() => vi.fn())
11
+
12
+ vi.mock('@generated', () => ({ gql: (query: unknown) => query }))
13
+ vi.mock('discovery.config', () => ({
14
+ default: { deliveryPromise: { enabled: true } },
15
+ }))
16
+ vi.mock('@faststore/sdk', () => ({
17
+ useSearch: () => ({
18
+ state: { sort: 'score_desc', term: '', selectedFacets: [] },
19
+ itemsPerPage: 12,
20
+ }),
21
+ }))
22
+ vi.mock('src/sdk/session', () => ({ useSession: mockUseSession }))
23
+ vi.mock('src/sdk/graphql/useQuery', () => ({ useQuery: mockUseQuery }))
24
+ vi.mock('src/sdk/product/useLocalizedVariables', () => ({
25
+ useLocalizedVariables: mockUseLocalizedVariables,
26
+ }))
27
+
28
+ import {
29
+ hashContractScope,
30
+ useCreateUseGalleryPage,
31
+ } from '../../../src/sdk/product/usePageProductsQuery'
32
+
33
+ const serverManyProductsVariables = {
34
+ first: 12,
35
+ after: '0',
36
+ sort: 'score_desc' as const,
37
+ term: '',
38
+ selectedFacets: [
39
+ { key: 'category-1', value: 'computers---tablets' },
40
+ { key: 'channel', value: '{"salesChannel":"1"}' },
41
+ { key: 'locale', value: 'en-CA' },
42
+ ],
43
+ sponsoredCount: 3,
44
+ }
45
+
46
+ type GalleryProps = NonNullable<Parameters<typeof useCreateUseGalleryPage>[0]>
47
+
48
+ const initialPages = {
49
+ search: { products: { pageInfo: { totalCount: 2 }, edges: [] } },
50
+ } as unknown as GalleryProps['initialPages']
51
+
52
+ const renderFirstGalleryPage = () => {
53
+ const { result: created } = renderHook(() =>
54
+ useCreateUseGalleryPage({ initialPages, serverManyProductsVariables })
55
+ )
56
+
57
+ return renderHook(() => created.current.useGalleryPage(0))
58
+ }
59
+
60
+ // `isDeliveryPromiseEnabled` is read when the module loads, so this scenario
61
+ // needs its own file with the flag mocked on.
62
+ describe('useCreateUseGalleryPage with deliveryPromise enabled', () => {
63
+ afterEach(() => {
64
+ vi.clearAllMocks()
65
+ })
66
+
67
+ it('scopes the page by postal code and contract together for B2B buyers', () => {
68
+ mockUseSession.mockReturnValue({
69
+ postalCode: '01310-100',
70
+ b2b: { customerId: 'contract-137' },
71
+ isValidating: false,
72
+ })
73
+ mockUseLocalizedVariables.mockReturnValue(serverManyProductsVariables)
74
+ mockUseQuery.mockReturnValue({ data: null })
75
+
76
+ renderFirstGalleryPage()
77
+
78
+ expect(mockUseQuery).toHaveBeenCalledWith(
79
+ expect.anything(),
80
+ {
81
+ ...serverManyProductsVariables,
82
+ _postalCode: '01310-100',
83
+ _contract: hashContractScope('contract-137'),
84
+ },
85
+ expect.objectContaining({ doNotRun: false })
86
+ )
87
+ })
88
+
89
+ it('still reuses the SSG page for an anonymous shopper with a postal code', () => {
90
+ mockUseSession.mockReturnValue({
91
+ postalCode: '01310-100',
92
+ b2b: null,
93
+ isValidating: false,
94
+ })
95
+ mockUseLocalizedVariables.mockReturnValue(serverManyProductsVariables)
96
+ mockUseQuery.mockReturnValue({ data: null })
97
+
98
+ const { result } = renderFirstGalleryPage()
99
+
100
+ expect(mockUseQuery).toHaveBeenCalledWith(
101
+ expect.anything(),
102
+ { ...serverManyProductsVariables, _postalCode: '01310-100' },
103
+ expect.objectContaining({ doNotRun: true })
104
+ )
105
+ expect(result.current.data).toBe(initialPages)
106
+ })
107
+
108
+ it('re-fetches when the postal code changed after the SSG page was seeded', () => {
109
+ mockUseSession.mockReturnValueOnce({
110
+ postalCode: null,
111
+ b2b: null,
112
+ isValidating: false,
113
+ })
114
+ mockUseSession.mockReturnValue({
115
+ postalCode: '01310-100',
116
+ b2b: null,
117
+ isValidating: false,
118
+ })
119
+ mockUseLocalizedVariables.mockReturnValue(serverManyProductsVariables)
120
+ mockUseQuery.mockReturnValue({ data: null })
121
+
122
+ renderFirstGalleryPage()
123
+
124
+ expect(mockUseQuery).toHaveBeenCalledWith(
125
+ expect.anything(),
126
+ { ...serverManyProductsVariables, _postalCode: '01310-100' },
127
+ expect.objectContaining({ doNotRun: false })
128
+ )
129
+ })
130
+ })