@faststore/core 4.10.1-dev.0 → 4.10.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.
@@ -7,26 +7,34 @@
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) 1018ms
10
+ ✓  browser  test/sdk/ui/useScrollRestoration.browser.test.ts (43 tests) 1184ms
11
11
  Browserslist: browsers data (caniuse-lite) is 8 months old. Please run:
12
12
  npx update-browserslist-db@latest
13
13
  Why you should do it regularly: https://github.com/browserslist/update-db#readme
14
- ✓  browser  test/components/account/cards/MyAccountListCards.browser.test.tsx (16 tests) 1812ms
15
- ✓ shows the tab bar and renders Personal cards by default  613ms
16
- ✓ paginates when there are more than 25 cards  313ms
17
- ✓  browser  test/components/account/ContractSwitcher.browser.test.tsx (22 tests) 2160ms
18
- ✓ shows an error state when the list fails to load  446ms
19
- ✓  browser  test/components/account/orders/BuyingPolicyAlert.browser.test.tsx (13 tests) 2240ms
20
- ✓ opens the reject confirmation modal with the default English copy  841ms
21
- ✓ opens the reject confirmation modal with CMS-provided labels  425ms
22
- ✓  browser  test/components/ui/RichText/RichText.browser.test.tsx (8 tests) 306ms
23
- ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (10 tests) 1178ms
24
- ✓ renders the menu view with contract details and manage footer  432ms
25
- ✓  browser  test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx (13 tests) 227ms
26
- ✓  browser  test/sdk/localization/useBindingSelector.browser.test.tsx (9 tests) 72ms
27
- ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (6 tests) 8ms
28
- ✓  browser  test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx (8 tests) 54ms
29
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 44ms
14
+ ✓  browser  test/components/account/cards/MyAccountListCards.browser.test.tsx (16 tests) 2807ms
15
+ ✓ shows the tab bar and renders Personal cards by default  1154ms
16
+ ✓ paginates when there are more than 25 cards  432ms
17
+ ✓  browser  test/components/account/ContractSwitcher.browser.test.tsx (22 tests) 4137ms
18
+ ✓ shows a loading state while contracts load  332ms
19
+ ✓ shows an error state when the list fails to load  944ms
20
+ ✓ requires selecting a contract before Confirm is enabled, then switches (REQ-03)  521ms
21
+ ✓ keeps Confirm disabled until a different contract is selected  319ms
22
+ ✓ keeps Confirm disabled when switching is unavailable  388ms
23
+ ✓ keeps Confirm disabled when the selected contract is filtered out  365ms
24
+ ✓  browser  test/components/account/orders/BuyingPolicyAlert.browser.test.tsx (13 tests) 2904ms
25
+ ✓ opens the reject confirmation modal with the default English copy  1018ms
26
+ ✓ opens the reject confirmation modal with CMS-provided labels  307ms
27
+ ✓ shows the default English toast when a policy is rejected  369ms
28
+ ✓ shows the CMS-provided error toast when rejecting a policy fails  338ms
29
+ ✓  browser  test/components/ui/RichText/RichText.browser.test.tsx (8 tests) 389ms
30
+ ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (10 tests) 1914ms
31
+ ✓ renders the menu view with contract details and manage footer  1093ms
32
+ ✓ opens the contract switcher and returns to the menu on back  338ms
33
+ ✓  browser  test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx (13 tests) 301ms
34
+ ✓  browser  test/sdk/localization/useBindingSelector.browser.test.tsx (9 tests) 66ms
35
+ ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (6 tests) 11ms
36
+ ✓  browser  test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx (8 tests) 80ms
37
+ ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 79ms
30
38
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
31
39
  Error in optimistic validation: TypeError: Failed to parse URL from /api/graphql?operationName=ValidateCartMutation&operationHash=3c71b0d3fc2bade14df68abfa271858beb361b12
32
40
   at node:internal/deps/undici/undici:16416:13
@@ -56,10 +64,10 @@ Browserslist: browsers data (caniuse-lite) is 8 months old. Please run:
56
64
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx > RecommendationShelf > renders nothing when there is an error
57
65
  Error fetching recommendations undefined boom for campaign vrn:recommendations:acc:rec-top-items-v2:campaign-1
58
66
 
59
- ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 103ms
60
- ✓  browser  test/components/CartSidebar/CartSidebar.browser.test.tsx (5 tests) 51ms
61
- ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (7 tests) 9ms
62
- ✓  browser  test/components/sections/Account/AccountOrderDetails.browser.test.tsx (4 tests) 78ms
67
+ ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 121ms
68
+ ✓  browser  test/components/CartSidebar/CartSidebar.browser.test.tsx (5 tests) 79ms
69
+ ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (7 tests) 21ms
70
+ ✓  browser  test/components/sections/Account/AccountOrderDetails.browser.test.tsx (4 tests) 72ms
63
71
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > posts the password and redirects to the requested return path
64
72
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
65
73
 
@@ -97,7 +105,6 @@ act(() => {
97
105
  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
98
106
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
99
107
 
100
- ✓  browser  test/components/navigation/Navbar.browser.test.tsx (3 tests) 44ms
101
108
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > defaults returnTo to root and shows API validation errors
102
109
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
103
110
 
@@ -290,10 +297,10 @@ act(() => {
290
297
  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
298
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
292
299
 
293
- ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 647ms
294
- ✓ renders the password protection form  346ms
295
- ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (8 tests) 399ms
296
- ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (5 tests) 33ms
300
+ ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 757ms
301
+ ✓ renders the password protection form  318ms
302
+ ✓  browser  test/components/navigation/Navbar.browser.test.tsx (3 tests) 64ms
303
+ ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (8 tests) 513ms
297
304
  stderr | test/components/cms/RenderSections.browser.test.tsx
298
305
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
299
306
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -302,8 +309,10 @@ This ensures that you're testing the behavior the user would see in the browser.
302
309
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
303
310
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
304
311
 
305
- ✓  browser  test/components/navigation/NavbarSlider.browser.test.tsx (2 tests) 34ms
306
- ✓  browser  test/components/cms/RenderSections.browser.test.tsx (5 tests) 65ms
312
+ ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (5 tests) 40ms
313
+ ✓  browser  test/components/navigation/NavbarSlider.browser.test.tsx (2 tests) 43ms
314
+ ✓  browser  test/components/cms/RenderSections.browser.test.tsx (5 tests) 70ms
315
+ ✓  browser  test/components/account/Menu.browser.test.tsx (6 tests) 334ms
307
316
  stderr | test/sdk/localization/store-url.browser.test.ts
308
317
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
309
318
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -312,15 +321,14 @@ This ensures that you're testing the behavior the user would see in the browser.
312
321
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
313
322
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
314
323
 
315
- ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 4ms
316
- ✓  browser  test/components/account/Menu.browser.test.tsx (6 tests) 250ms
317
- ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 20ms
318
- ✓  browser  test/components/templates/SearchPage/SearchWrapper.browser.test.tsx (3 tests) 24ms
319
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx (2 tests) 127ms
320
- ✓  browser  test/components/sections/Account/AccountListQuotes.browser.test.tsx (4 tests) 79ms
321
- ✓  browser  test/components/navigation/NavbarAccountArea.browser.test.tsx (4 tests) 41ms
322
- ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 16ms
323
- ✓  browser  test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx (3 tests) 46ms
324
+ ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 27ms
325
+ ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 5ms
326
+ ✓  browser  test/components/templates/SearchPage/SearchWrapper.browser.test.tsx (3 tests) 30ms
327
+ ✓  browser  test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx (2 tests) 135ms
328
+ ✓  browser  test/components/sections/Account/AccountListQuotes.browser.test.tsx (4 tests) 144ms
329
+ ✓  browser  test/components/navigation/NavbarAccountArea.browser.test.tsx (4 tests) 65ms
330
+ ✓  browser  test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx (3 tests) 49ms
331
+ ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 24ms
324
332
  stderr | test/components/account/doLogout.browser.test.ts
325
333
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
326
334
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -329,9 +337,9 @@ This ensures that you're testing the behavior the user would see in the browser.
329
337
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
330
338
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
331
339
 
332
- ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 17ms
333
- ✓  browser  test/components/ui/LocalizationButton.browser.test.tsx (3 tests) 44ms
334
- ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (5 tests) 237ms
340
+ ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 24ms
341
+ ✓  browser  test/components/ui/LocalizationButton.browser.test.tsx (3 tests) 71ms
342
+ ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (5 tests) 408ms
335
343
  stderr | test/sdk/account/clearPersistedSessionState.browser.test.ts
336
344
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
337
345
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -340,14 +348,15 @@ This ensures that you're testing the behavior the user would see in the browser.
340
348
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
341
349
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
342
350
 
343
- ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (4 tests) 11ms
344
- ✓  browser  test/components/sections/Account/AccountOrderDelivery.browser.test.tsx (2 tests) 76ms
345
- ✓  browser  test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx (2 tests) 72ms
346
- ✓  browser  test/components/sections/Account/AccountOrderStatus.browser.test.tsx (3 tests) 81ms
347
- ✓  browser  test/components/region/useRegion.browser.test.ts (1 test) 27ms
348
- ✓  browser  test/components/sections/Account/AccountListCards.browser.test.tsx (3 tests) 65ms
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) 70ms
351
+ ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (4 tests) 24ms
352
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx (2 tests) 97ms
353
+ ✓  browser  test/components/sections/Account/AccountOrderDelivery.browser.test.tsx (2 tests) 110ms
354
+ ✓  browser  test/components/sections/Account/AccountOrderStatus.browser.test.tsx (3 tests) 133ms
355
+ ✓  browser  test/components/region/useRegion.browser.test.ts (1 test) 26ms
356
+ ✓  browser  test/components/sections/Account/AccountListCards.browser.test.tsx (3 tests) 88ms
357
+ ✓  browser  test/components/sections/Account/AccountOrdersList.browser.test.tsx (2 tests) 24ms
358
+ ✓  browser  test/components/account/orders/SummaryCard.browser.test.tsx (2 tests) 117ms
359
+ ✓  browser  test/components/account/OrganizationSignInButton.browser.test.tsx (2 tests) 213ms
351
360
  stderr | test/sdk/session/sessionStore.browser.test.ts
352
361
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
353
362
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -356,9 +365,8 @@ This ensures that you're testing the behavior the user would see in the browser.
356
365
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
357
366
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
358
367
 
359
- ✓  browser  test/components/account/OrganizationSignInButton.browser.test.tsx (2 tests) 219ms
360
- ✓  browser  test/sdk/session/sessionStore.browser.test.ts (2 tests) 5ms
361
- ✓  browser  test/components/account/orders/DeliveryOptionAccordionDeliveryInfo.browser.test.tsx (1 test) 31ms
368
+ ✓  browser  test/sdk/session/sessionStore.browser.test.ts (2 tests) 7ms
369
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordionDeliveryInfo.browser.test.tsx (1 test) 39ms
362
370
  stderr | test/sdk/session/validateSession.browser.test.ts
363
371
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
364
372
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -367,13 +375,14 @@ This ensures that you're testing the behavior the user would see in the browser.
367
375
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
368
376
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
369
377
 
370
- ✓  browser  test/sdk/session/validateSession.browser.test.ts (1 test) 3ms
371
- ✓  browser  test/components/account/orders/StatusCard.browser.test.tsx (2 tests) 87ms
372
- ✓  browser  test/utils/isLocalHost.browser.test.ts (3 tests) 2ms
373
- ✓  browser  test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts (1 test) 2ms
374
- ✓  browser  test/components/account/orders/DeliveryCard.browser.test.tsx (1 test) 41ms
375
- ✓  browser  test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx (1 test) 32ms
376
- ✓  browser  test/components/skeletons/ProductGridSkeleton.browser.test.tsx (2 tests) 32ms
378
+ ✓  browser  test/sdk/session/validateSession.browser.test.ts (1 test) 6ms
379
+ ✓  browser  test/components/account/orders/StatusCard.browser.test.tsx (2 tests) 120ms
380
+ ✓  browser  test/utils/isLocalHost.browser.test.ts (3 tests) 6ms
381
+ ✓  browser  test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts (1 test) 3ms
382
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx (1 test) 36ms
383
+ ✓  browser  test/components/account/orders/DeliveryCard.browser.test.tsx (1 test) 53ms
384
+ ✓  browser  test/components/skeletons/ProductGridSkeleton.browser.test.tsx (2 tests) 34ms
385
+ ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 30ms
377
386
  stderr | test/sdk/account/refreshToken.browser.test.ts
378
387
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
379
388
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -382,12 +391,13 @@ This ensures that you're testing the behavior the user would see in the browser.
382
391
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
383
392
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
384
393
 
385
- ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 20ms
386
- ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 4ms
387
- ✓  node  test/server/password-protection-service.test.ts (22 tests) 30ms
388
- ✓  node  test/pages/api/switch-contract.test.ts (25 tests) 65ms
389
- ✓  browser  test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx (1 test) 153ms
390
- ✓  node  test/utils/match-url.test.ts (10 tests) 7ms
394
+ ✓  node  test/server/password-protection-service.test.ts (22 tests) 40ms
395
+ ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 6ms
396
+ ✓  browser  test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx (1 test) 248ms
397
+ ✓  node  test/pages/api/switch-contract.test.ts (25 tests) 72ms
398
+ ✓  node  test/server/content/service.test.ts (15 tests) 28ms
399
+ ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 12ms
400
+ ✓  node  test/utils/match-url.test.ts (10 tests) 10ms
391
401
  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
392
402
  Failed to fetch pickup points: Error: network
393
403
  at /home/runner/work/faststore/faststore/packages/core/test/sdk/deliveryPromise/provider.effect.test.tsx:250:30
@@ -414,13 +424,13 @@ This ensures that you're testing the behavior the user would see in the browser.
414
424
  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)
415
425
  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)
416
426
 
417
- ✓  node  test/sdk/deliveryPromise/provider.effect.test.tsx (9 tests) 64ms
418
- ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 11ms
419
- ✓  node  test/pages/api/unlock.test.ts (15 tests) 126ms
427
+ ✓  node  test/sdk/deliveryPromise/provider.effect.test.tsx (9 tests) 84ms
428
+ ✓  node  test/pages/api/unlock.test.ts (15 tests) 147ms
429
+ ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (14 tests) 10ms
430
+ ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 11ms
420
431
  stderr | test/components/search/SearchInputComponent.test.tsx
421
432
  Warning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?
422
433
 
423
- ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (14 tests) 10ms
424
434
  stderr | test/components/search/SearchInputComponent.test.tsx > SearchInput (OES integration) > renders without crashing
425
435
  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.
426
436
  at div
@@ -481,24 +491,23 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
481
491
  at Suspense
482
492
  at Wrapper (/home/runner/work/faststore/faststore/packages/core/test/components/search/SearchInputComponent.test.tsx:106:20)
483
493
 
484
- ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 57ms
485
- ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 9ms
486
- ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (15 tests) 325ms
487
- ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 32ms
494
+ ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 74ms
495
+ ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (15 tests) 416ms
488
496
  stderr | test/pages/pvt/account/cards/index.test.ts > Cards page getServerSideProps > redirects to /pvt/account/403 when the query fails with 401/403
489
497
  { extensions: { status: 401 } }
490
498
 
491
499
  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
492
500
  { extensions: { status: 502 } }
493
501
 
494
- ✓  node  test/pages/pvt/account/cards/index.test.ts (8 tests) 6ms
495
- ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (11 tests) 802ms
496
- ✓ navigates with the search term on blur  358ms
497
- ✓ navigates without a label param when the search is cleared  356ms
498
- ✓  node  test/components/quotes/quoteFilters.test.ts (26 tests) 27ms
499
- ✓  node  test/utils/productJsonLd.test.ts (36 tests) 11ms
500
- ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 9ms
501
- ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 7ms
502
+ ✓  node  test/pages/pvt/account/cards/index.test.ts (8 tests) 12ms
503
+ ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 40ms
504
+ ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (11 tests) 839ms
505
+ ✓ navigates with the search term on blur  361ms
506
+ ✓ navigates without a label param when the search is cleared  361ms
507
+ ✓  node  test/components/quotes/quoteFilters.test.ts (26 tests) 42ms
508
+ ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 16ms
509
+ ✓  node  test/utils/productJsonLd.test.ts (36 tests) 17ms
510
+ ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 9ms
502
511
  stderr | test/pages/api/graphql.test.ts > /api/graphql error status propagation > propagates the upstream 400 from a wrapped BadRequestError instead of 500
503
512
  Graphql execution returned with error: [
504
513
  {
@@ -608,77 +617,76 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
608
617
  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)
609
618
  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)
610
619
 
611
- ✓  node  test/pages/api/graphql.test.ts (19 tests) 34ms
612
- ✓  node  test/utils/clearCookies.test.ts (20 tests) 21ms
613
- ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 4ms
620
+ ✓  node  test/pages/api/graphql.test.ts (19 tests) 29ms
621
+ ✓  node  test/utils/clearCookies.test.ts (20 tests) 30ms
622
+ ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 7ms
614
623
  stderr | test/sdk/account/useSetPassword.test.ts > useSetPassword > maps wrongcredentials error responses to the existing message
615
624
  Set password request failed: Bad Request
616
625
 
617
- ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 38ms
618
- ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 34ms
626
+ ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 49ms
627
+ ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 45ms
619
628
  ✓  node  test/utils/localization/localizeRedirectDestination.test.ts (11 tests) 5ms
620
- ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (8 tests) 149ms
621
- ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (14 tests) 116ms
622
- ✓  node  test/utils/localization/hreflang.test.ts (8 tests) 5ms
623
- ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (13 tests) 5ms
624
- ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (10 tests) 79ms
625
- ✓  node  test/sdk/redirects/index.test.ts (7 tests) 34ms
626
- ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 32ms
627
- ✓  node  test/proxy.localization.test.ts (5 tests) 37ms
628
- ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 33ms
629
- ✓  node  test/components/account/orders/getDeliveryOptionLabel.test.ts (21 tests) 6ms
630
- ✓  node  test/sdk/graphql/retryPolicy.test.ts (18 tests) 14ms
631
- ✓  node  test/server/cms/myAccountDefaultSections.test.ts (9 tests) 5ms
632
- ✓  node  test/utils/productJsonLdRendering.test.tsx (5 tests) 12ms
633
- ✓  node  test/utils/trustedForwardedHost.test.ts (27 tests) 5ms
634
- ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 32ms
635
- ✓  node  test/proxy.test.ts (4 tests) 13ms
636
- ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 20ms
637
- ✓  node  test/server/webops-api.test.ts (9 tests) 9ms
638
- ✓  node  test/sdk/localization/store-url.test.ts (4 tests) 3ms
639
- ✓  node  test/components/account/orders/StatusCard.test.ts (3 tests) 20ms
640
- ✓  node  test/sdk/deliveryPromise/queries.test.ts (4 tests) 5ms
641
- ✓  node  test/sdk/cart/waitForSessionValidated.test.ts (5 tests) 8ms
642
- ✓  node  test/server/content/service.test.ts (5 tests) 4ms
643
- ✓  node  test/server/index.test.ts (7 tests) 1839ms
644
- ✓ should handle options and execute  1416ms
645
- ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 5ms
646
- ✓  node  test/server/cms/global.test.ts (3 tests) 4ms
647
- ✓  node  test/sdk/deliveryPromise/provider.test.ts (11 tests) 5ms
648
- ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 26ms
649
- ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 4ms
650
- ✓  node  test/components/account/Layout.test.tsx (6 tests) 53ms
651
- ✓  node  test/server/rootSpan.test.ts (4 tests) 36ms
652
- ✓  node  test/pages/api/preview.test.ts (2 tests) 5ms
629
+ ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (14 tests) 130ms
630
+ ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (8 tests) 191ms
631
+ ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (13 tests) 8ms
632
+ ✓  node  test/utils/localization/hreflang.test.ts (8 tests) 8ms
633
+ ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (10 tests) 125ms
634
+ ✓  node  test/sdk/redirects/index.test.ts (7 tests) 46ms
635
+ ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 50ms
636
+ ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 51ms
637
+ ✓  node  test/proxy.localization.test.ts (5 tests) 73ms
638
+ ✓  node  test/components/account/orders/getDeliveryOptionLabel.test.ts (21 tests) 9ms
639
+ ✓  node  test/sdk/graphql/retryPolicy.test.ts (18 tests) 24ms
640
+ ✓  node  test/server/cms/myAccountDefaultSections.test.ts (9 tests) 7ms
641
+ ✓  node  test/utils/productJsonLdRendering.test.tsx (5 tests) 15ms
642
+ ✓  node  test/utils/trustedForwardedHost.test.ts (27 tests) 6ms
643
+ ✓  node  test/server/index.test.ts (7 tests) 1067ms
644
+ ✓ should handle options and execute  493ms
645
+ ✓  node  test/proxy.test.ts (4 tests) 21ms
646
+ ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 52ms
647
+ ✓  node  test/server/webops-api.test.ts (9 tests) 16ms
648
+ ✓  node  test/sdk/localization/store-url.test.ts (4 tests) 5ms
649
+ ✓  node  test/components/account/orders/StatusCard.test.ts (3 tests) 37ms
650
+ ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 45ms
651
+ ✓  node  test/sdk/deliveryPromise/queries.test.ts (4 tests) 14ms
652
+ ✓  node  test/sdk/cart/waitForSessionValidated.test.ts (5 tests) 12ms
653
+ ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 6ms
654
+ ✓  node  test/server/cms/global.test.ts (3 tests) 5ms
655
+ ✓  node  test/sdk/deliveryPromise/provider.test.ts (11 tests) 7ms
656
+ ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 31ms
657
+ ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 7ms
658
+ ✓  node  test/components/account/Layout.test.tsx (6 tests) 78ms
659
+ ✓  node  test/server/rootSpan.test.ts (4 tests) 38ms
660
+ ✓  node  test/pages/api/preview.test.ts (2 tests) 10ms
653
661
  ✓  node  test/components/search/SearchInput.test.ts (6 tests) 9ms
654
- ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 4ms
655
- ✓  node  test/sdk/recommendations/vrn.test.ts (5 tests) 3ms
656
- ✓  node  test/server/cms/index.test.ts (2 tests) 4ms
662
+ ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 6ms
663
+ ✓  node  test/sdk/recommendations/vrn.test.ts (5 tests) 4ms
664
+ ✓  node  test/server/cms/index.test.ts (2 tests) 6ms
665
+ ✓  node  test/components/region/useRegion.test.ts (1 test) 29ms
657
666
  ✓  node  test/utils/retry.test.ts (5 tests) 13ms
658
- ✓  node  test/components/region/useRegion.test.ts (1 test) 24ms
659
- ✓  node  test/sdk/account/getB2BSessionClaims.test.ts (4 tests) 4ms
660
- ✓  node  test/utils/userOrderStatus.test.ts (4 tests) 3ms
661
- ✓  node  test/sdk/recommendations/useRecommendationUserId.test.ts (4 tests) 182ms
662
- ✓  node  test/utils/unlockResponse.test.ts (11 tests) 4ms
663
- ✓  node  test/sdk/recommendations/useRecommendations.test.ts (2 tests) 4ms
664
- ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 32ms
665
- ✓  node  test/sdk/deliveryPromise/reducer.test.ts (2 tests) 3ms
666
- ✓  node  test/utils/isLocalHost.test.ts (7 tests) 4ms
667
- ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (4 tests) 26ms
667
+ ✓  node  test/sdk/account/getB2BSessionClaims.test.ts (4 tests) 5ms
668
+ ✓  node  test/utils/userOrderStatus.test.ts (4 tests) 6ms
669
+ ✓  node  test/sdk/recommendations/useRecommendationUserId.test.ts (4 tests) 193ms
670
+ ✓  node  test/utils/unlockResponse.test.ts (11 tests) 7ms
671
+ ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 47ms
672
+ ✓  node  test/sdk/recommendations/useRecommendations.test.ts (2 tests) 7ms
673
+ ✓  node  test/sdk/deliveryPromise/reducer.test.ts (2 tests) 4ms
674
+ ✓  node  test/utils/isLocalHost.test.ts (7 tests) 5ms
668
675
  ✓  node  test/sdk/session/toSessionInput.test.ts (3 tests) 4ms
669
- ✓  node  test/components/account/orders/getOrderTotalLabel.test.ts (6 tests) 3ms
670
- ✓  node  test/components/account/orders/listOrdersLabels.test.ts (2 tests) 2ms
671
- ✓  node  test/utils/getCookie.test.ts (4 tests) 4ms
672
- ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 30ms
673
- ✓  node  test/sdk/session/isSignInAreaResolved.test.ts (3 tests) 3ms
674
- ✓  node  test/utils/filterChannel.test.ts (2 tests) 3ms
675
- ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 2ms
676
- ✓  node  test/instrumentation.test.ts (3 tests) 4ms
677
- ✓  node  test/components/account/orders/orderDetailsLabels.test.ts (1 test) 3ms
678
- ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 6ms
676
+ ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (4 tests) 54ms
677
+ ✓  node  test/components/account/orders/getOrderTotalLabel.test.ts (6 tests) 4ms
678
+ ✓  node  test/components/account/orders/listOrdersLabels.test.ts (2 tests) 3ms
679
+ ✓  node  test/utils/getCookie.test.ts (4 tests) 5ms
680
+ ✓  node  test/sdk/session/isSignInAreaResolved.test.ts (3 tests) 4ms
681
+ ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 43ms
682
+ ✓  node  test/utils/filterChannel.test.ts (2 tests) 5ms
683
+ ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 5ms
684
+ ✓  node  test/instrumentation.test.ts (3 tests) 7ms
685
+ ✓  node  test/components/account/orders/orderDetailsLabels.test.ts (1 test) 4ms
686
+ ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 12ms
679
687
 
680
688
   Test Files  135 passed (135)
681
-  Tests  1066 passed (1066)
682
-  Start at  15:00:34
683
-  Duration  48.73s (transform 13.25s, setup 0ms, import 43.07s, tests 17.28s, environment 60.29s)
689
+  Tests  1076 passed (1076)
690
+  Start at  19:21:02
691
+  Duration  63.77s (transform 15.47s, setup 0ms, import 57.20s, tests 23.12s, environment 78.69s)
684
692
 
package/CHANGELOG.md CHANGED
@@ -3,9 +3,11 @@
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.0](https://github.com/vtex/faststore/compare/v4.10.0...v4.10.1-dev.0) (2026-10-15)
6
+ ## [4.10.1](https://github.com/vtex/faststore/compare/v4.10.0...v4.10.1) (2026-10-23)
7
7
 
8
- **Note:** Version bump only for package @faststore/core
8
+ ### Bug Fixes
9
+
10
+ - **core:** cache Content Platform template list across page generations ([#3542](https://github.com/vtex/faststore/issues/3542)) ([ab44199](https://github.com/vtex/faststore/commit/ab441997841b5f47b4dcc65d82fdfbb03fb579f2)), closes [#3384](https://github.com/vtex/faststore/issues/3384)
9
11
 
10
12
  # [4.10.0](https://github.com/vtex/faststore/compare/v4.10.0-dev.1...v4.10.0) (2026-10-15)
11
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "4.10.1-dev.0",
3
+ "version": "4.10.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.10.1-dev.0",
75
- "@faststore/diagnostics": "4.10.1-dev.0",
76
- "@faststore/lighthouse": "4.10.1-dev.0",
77
- "@faststore/sdk": "4.10.1-dev.0",
78
- "@faststore/ui": "4.10.1-dev.0"
74
+ "@faststore/api": "4.10.1",
75
+ "@faststore/lighthouse": "4.10.1",
76
+ "@faststore/ui": "4.10.1",
77
+ "@faststore/sdk": "4.10.1",
78
+ "@faststore/diagnostics": "4.10.1"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@cypress/code-coverage": "^3.12.1",
@@ -19,15 +19,27 @@ import { isBranchPreview, isContentPlatformSource } from './utils'
19
19
 
20
20
  type ContentResult = ContentData | (ContentEntry & PageContentType)
21
21
 
22
+ type ContentListCacheEntry = {
23
+ expiresAt: number
24
+ promise: Promise<{ data: ContentResult[] }>
25
+ }
26
+
22
27
  const OPTIONAL_CONTENT_TYPES = [
23
28
  'globalHeaderSections',
24
29
  'globalFooterSections',
25
30
  ] as const
31
+
32
+ // Bounds how stale the template list can get in long-lived processes (ISR);
33
+ // a CMS publish already triggers a new build.
34
+ const CONTENT_LIST_CACHE_TTL_MS = 5 * 60 * 1000
35
+
26
36
  export class ContentService {
27
37
  private readonly clientCP = new ClientCP({
28
38
  tenant: config.api.storeId,
29
39
  })
30
40
 
41
+ private readonly contentListCache = new Map<string, ContentListCacheEntry>()
42
+
31
43
  async getSingleContent<T extends ContentData>(
32
44
  params: ContentParams
33
45
  ): Promise<T> {
@@ -46,29 +58,12 @@ export class ContentService {
46
58
 
47
59
  if (isContentPlatformSource()) {
48
60
  const serviceParams = this.convertOptionsToParams(options)
49
- // TODO: This should be temporary until the cp data-plane have a search engine.
50
- const MAX_PAGES = 25
51
- const allEntries: ContentEntry[] = []
52
- let scroll: string | undefined
53
- let pages = 0
54
-
55
- do {
56
- const result = await this.clientCP.listEntries(serviceParams, scroll)
57
- allEntries.push(...result.entries)
58
- const next = result.scroll ?? undefined
59
- if (next && next === scroll) break
60
- scroll = next
61
- } while (scroll && ++pages < MAX_PAGES)
62
-
63
- if (pages >= MAX_PAGES) {
64
- console.warn('listEntries pagination hit MAX_PAGES cap', serviceParams)
61
+
62
+ if (options.isPreview) {
63
+ return this.listAndFillEntries(serviceParams, true)
65
64
  }
66
65
 
67
- return this.fillEntriesWithData(
68
- allEntries,
69
- serviceParams,
70
- options.isPreview
71
- )
66
+ return this.getCachedContentList(serviceParams)
72
67
  }
73
68
  return getCMSPage(options.cmsOptions)
74
69
  }
@@ -136,6 +131,60 @@ export class ContentService {
136
131
  }
137
132
  }
138
133
 
134
+ private getCachedContentList(
135
+ serviceParams: EntryPathParams
136
+ ): Promise<{ data: ContentResult[] }> {
137
+ // listEntries only depends on these fields; the page slug must stay out of the key.
138
+ const { accountName, storeId, contentType, locale } = serviceParams
139
+ const key = JSON.stringify([accountName, storeId, contentType, locale])
140
+
141
+ const cached = this.contentListCache.get(key)
142
+ if (cached && cached.expiresAt > Date.now()) {
143
+ return cached.promise
144
+ }
145
+
146
+ const promise = this.listAndFillEntries(serviceParams, false).catch(
147
+ (err: unknown) => {
148
+ if (this.contentListCache.get(key)?.promise === promise) {
149
+ this.contentListCache.delete(key)
150
+ }
151
+ throw err
152
+ }
153
+ )
154
+
155
+ this.contentListCache.set(key, {
156
+ expiresAt: Date.now() + CONTENT_LIST_CACHE_TTL_MS,
157
+ promise,
158
+ })
159
+
160
+ return promise
161
+ }
162
+
163
+ private async listAndFillEntries(
164
+ serviceParams: EntryPathParams,
165
+ isPreview: boolean
166
+ ): Promise<{ data: (ContentEntry & PageContentType)[] }> {
167
+ // TODO: This should be temporary until the cp data-plane have a search engine.
168
+ const MAX_PAGES = 25
169
+ const allEntries: ContentEntry[] = []
170
+ let scroll: string | undefined
171
+ let pages = 0
172
+
173
+ do {
174
+ const result = await this.clientCP.listEntries(serviceParams, scroll)
175
+ allEntries.push(...result.entries)
176
+ const next = result.scroll ?? undefined
177
+ if (next && next === scroll) break
178
+ scroll = next
179
+ } while (scroll && ++pages < MAX_PAGES)
180
+
181
+ if (pages >= MAX_PAGES) {
182
+ console.warn('listEntries pagination hit MAX_PAGES cap', serviceParams)
183
+ }
184
+
185
+ return this.fillEntriesWithData(allEntries, serviceParams, isPreview)
186
+ }
187
+
139
188
  private async fillEntriesWithData(
140
189
  entries: ContentEntry[],
141
190
  serviceParams: EntryPathParams,
@@ -1,11 +1,21 @@
1
- import type { EntryPathParams } from '@vtex/client-cp'
2
- import { describe, expect, it } from 'vitest'
1
+ import type { ContentEntry, EntryPathParams } from '@vtex/client-cp'
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
3
3
 
4
4
  import { ContentService } from '../../../src/server/content/service'
5
5
  import type {
6
6
  ContentOptions,
7
7
  ContentParams,
8
8
  } from '../../../src/server/content/types'
9
+ import type * as ContentUtils from '../../../src/server/content/utils'
10
+ import { isContentPlatformSource } from '../../../src/server/content/utils'
11
+
12
+ vi.mock('../../../src/server/content/utils', async (importOriginal) => {
13
+ const actual = await importOriginal<typeof ContentUtils>()
14
+ return {
15
+ ...actual,
16
+ isContentPlatformSource: vi.fn(actual.isContentPlatformSource),
17
+ }
18
+ })
9
19
 
10
20
  type ContentServiceInternals = {
11
21
  createContentOptions: (params: ContentParams) => ContentOptions
@@ -15,6 +25,59 @@ type ContentServiceInternals = {
15
25
  const getServiceInternals = (service: ContentService) =>
16
26
  service as unknown as ContentServiceInternals
17
27
 
28
+ const NOT_FOUND = 'Request failed with status code 404'
29
+
30
+ const entryData: Record<string, Record<string, unknown>> = {
31
+ generic: { sections: [{ name: 'Generic' }] },
32
+ collection: {
33
+ settings: { template: { value: '/colecao' } },
34
+ sections: [{ name: 'Collection' }],
35
+ },
36
+ other: {
37
+ settings: { template: { value: '/outra' } },
38
+ sections: [{ name: 'Other' }],
39
+ },
40
+ }
41
+
42
+ const toEntry = (id: string) => ({ id, name: id }) as ContentEntry
43
+
44
+ function createFakeClientCP(pages: string[][]) {
45
+ return {
46
+ listEntries: vi.fn(async (_params: EntryPathParams, scroll?: string) => {
47
+ const index = scroll ? Number(scroll) : 0
48
+ return {
49
+ entries: pages[index].map(toEntry),
50
+ scroll: index + 1 < pages.length ? String(index + 1) : null,
51
+ }
52
+ }),
53
+ getEntry: vi.fn(async ({ entryId }: EntryPathParams) => ({
54
+ ...entryData[entryId as string],
55
+ })),
56
+ previewEntryById: vi.fn(async ({ entryId }: EntryPathParams) => ({
57
+ ...entryData[entryId as string],
58
+ })),
59
+ getEntryBySlug: vi.fn(
60
+ async (_params: EntryPathParams): Promise<Record<string, unknown>> => {
61
+ throw new Error(NOT_FOUND)
62
+ }
63
+ ),
64
+ previewEntryBySlug: vi.fn(
65
+ async (_params: EntryPathParams): Promise<Record<string, unknown>> => {
66
+ throw new Error(NOT_FOUND)
67
+ }
68
+ ),
69
+ }
70
+ }
71
+
72
+ function createServiceWithClient(
73
+ pages: string[][] = [['generic', 'collection', 'other']]
74
+ ) {
75
+ const service = new ContentService()
76
+ const clientCP = createFakeClientCP(pages)
77
+ Object.defineProperty(service, 'clientCP', { value: clientCP })
78
+ return { service, clientCP }
79
+ }
80
+
18
81
  describe('ContentService', () => {
19
82
  describe('convertOptionsToParams', () => {
20
83
  it('maps a flat SEO slug filter to the client-cp slug param', () => {
@@ -95,4 +158,180 @@ describe('ContentService', () => {
95
158
  expect('locale' in options.cmsOptions).toBe(false)
96
159
  })
97
160
  })
161
+
162
+ describe('Content Platform template list cache', () => {
163
+ const FIVE_MINUTES = 5 * 60 * 1000
164
+
165
+ beforeEach(() => {
166
+ vi.mocked(isContentPlatformSource).mockReturnValue(true)
167
+ vi.spyOn(console, 'warn').mockImplementation(() => {})
168
+ })
169
+
170
+ afterEach(() => {
171
+ vi.mocked(isContentPlatformSource).mockReset()
172
+ vi.restoreAllMocks()
173
+ vi.useRealTimers()
174
+ })
175
+
176
+ it('shares one list and entry fetch across concurrent PLPs with different slugs', async () => {
177
+ const { service, clientCP } = createServiceWithClient()
178
+
179
+ const [collectionPage, otherPage, unknownPage] = await Promise.all([
180
+ service.getPlpContent({ slug: 'colecao/camisetas' }, []),
181
+ service.getPlpContent({ slug: 'outra' }, []),
182
+ service.getPlpContent({ slug: 'nao-existe' }, []),
183
+ ])
184
+
185
+ expect(clientCP.listEntries).toHaveBeenCalledTimes(1)
186
+ expect(clientCP.getEntry).toHaveBeenCalledTimes(3)
187
+ expect(collectionPage.sections).toEqual([{ name: 'Collection' }])
188
+ expect(otherPage.sections).toEqual([{ name: 'Other' }])
189
+ expect(unknownPage.sections).toEqual([{ name: 'Generic' }])
190
+ })
191
+
192
+ it('reuses the cached list within the TTL', async () => {
193
+ const { service, clientCP } = createServiceWithClient()
194
+
195
+ await service.getPlpContent({ slug: 'colecao' }, [])
196
+ await service.getPlpContent({ slug: 'outra' }, [])
197
+
198
+ expect(clientCP.listEntries).toHaveBeenCalledTimes(1)
199
+ expect(clientCP.getEntry).toHaveBeenCalledTimes(3)
200
+ })
201
+
202
+ it('still prefers a direct slug match over the cached list', async () => {
203
+ const { service, clientCP } = createServiceWithClient()
204
+ await service.getPlpContent({ slug: 'colecao' }, [])
205
+
206
+ clientCP.getEntryBySlug.mockResolvedValueOnce({
207
+ sections: [{ name: 'Direct' }],
208
+ })
209
+ const page = await service.getPlpContent({ slug: 'colecao' }, [])
210
+
211
+ expect(page.sections).toEqual([{ name: 'Direct' }])
212
+ expect(clientCP.listEntries).toHaveBeenCalledTimes(1)
213
+ })
214
+
215
+ it('consumes every scroll page once', async () => {
216
+ const { service, clientCP } = createServiceWithClient([
217
+ ['generic'],
218
+ ['collection'],
219
+ ['other'],
220
+ ])
221
+
222
+ await Promise.all([
223
+ service.getMultipleContent({ contentType: 'plp' }),
224
+ service.getMultipleContent({ contentType: 'plp' }),
225
+ ])
226
+ const { data } = await service.getMultipleContent({ contentType: 'plp' })
227
+
228
+ expect(clientCP.listEntries).toHaveBeenCalledTimes(3)
229
+ expect(
230
+ clientCP.listEntries.mock.calls.map(([, scroll]) => scroll)
231
+ ).toEqual([undefined, '1', '2'])
232
+ expect(data.map((entry) => entry.id)).toEqual([
233
+ 'generic',
234
+ 'collection',
235
+ 'other',
236
+ ])
237
+ })
238
+
239
+ it('keeps separate entries per content type', async () => {
240
+ const { service, clientCP } = createServiceWithClient()
241
+
242
+ await service.getMultipleContent({ contentType: 'plp' })
243
+ await service.getMultipleContent({ contentType: 'pdp' })
244
+ await service.getMultipleContent({ contentType: 'pdp' })
245
+
246
+ expect(clientCP.listEntries).toHaveBeenCalledTimes(2)
247
+ expect(
248
+ clientCP.listEntries.mock.calls.map(([params]) => params.contentType)
249
+ ).toEqual(['plp', 'pdp'])
250
+ })
251
+
252
+ it('keeps separate entries per locale', async () => {
253
+ const { service, clientCP } = createServiceWithClient()
254
+
255
+ await service.getMultipleContent({ contentType: 'plp', locale: 'pt-BR' })
256
+ await service.getMultipleContent({ contentType: 'plp', locale: 'en-US' })
257
+ await service.getMultipleContent({ contentType: 'plp', locale: 'pt-BR' })
258
+
259
+ expect(clientCP.listEntries).toHaveBeenCalledTimes(2)
260
+ expect(
261
+ clientCP.listEntries.mock.calls.map(([params]) => params.locale)
262
+ ).toEqual(['pt-BR', 'en-US'])
263
+ })
264
+
265
+ it('bypasses the cache for previews', async () => {
266
+ const { service, clientCP } = createServiceWithClient()
267
+ const previewParams: ContentParams = {
268
+ contentType: 'plp',
269
+ previewData: {
270
+ contentType: 'plp',
271
+ documentId: 'collection',
272
+ versionId: 'version-1',
273
+ },
274
+ }
275
+
276
+ await service.getMultipleContent({ contentType: 'plp' })
277
+ await service.getMultipleContent(previewParams)
278
+ await service.getMultipleContent(previewParams)
279
+ await service.getMultipleContent({ contentType: 'plp' })
280
+
281
+ expect(clientCP.listEntries).toHaveBeenCalledTimes(3)
282
+ expect(clientCP.getEntry).toHaveBeenCalledTimes(3)
283
+ expect(clientCP.previewEntryById).toHaveBeenCalledTimes(6)
284
+ })
285
+
286
+ it('does not cache failures', async () => {
287
+ const { service, clientCP } = createServiceWithClient()
288
+ const error = new Error('Request failed with status code 500')
289
+ clientCP.listEntries.mockRejectedValueOnce(error)
290
+
291
+ await expect(
292
+ service.getMultipleContent({ contentType: 'plp' })
293
+ ).rejects.toBe(error)
294
+ const { data } = await service.getMultipleContent({ contentType: 'plp' })
295
+
296
+ expect(clientCP.listEntries).toHaveBeenCalledTimes(2)
297
+ expect(data).toHaveLength(3)
298
+ })
299
+
300
+ it('does not let a late failure evict a newer fetch', async () => {
301
+ vi.useFakeTimers()
302
+ const { service, clientCP } = createServiceWithClient()
303
+ let rejectFirst: (err: Error) => void = () => {}
304
+ clientCP.listEntries.mockImplementationOnce(
305
+ () =>
306
+ new Promise((_, reject) => {
307
+ rejectFirst = reject
308
+ })
309
+ )
310
+
311
+ const first = service.getMultipleContent({ contentType: 'plp' })
312
+ const firstResult = expect(first).rejects.toThrow('late failure')
313
+ vi.advanceTimersByTime(FIVE_MINUTES)
314
+ await service.getMultipleContent({ contentType: 'plp' })
315
+
316
+ rejectFirst(new Error('late failure'))
317
+ await firstResult
318
+ await service.getMultipleContent({ contentType: 'plp' })
319
+
320
+ expect(clientCP.listEntries).toHaveBeenCalledTimes(2)
321
+ })
322
+
323
+ it('fetches the list again after the TTL', async () => {
324
+ vi.useFakeTimers()
325
+ const { service, clientCP } = createServiceWithClient()
326
+
327
+ await service.getMultipleContent({ contentType: 'plp' })
328
+ vi.advanceTimersByTime(FIVE_MINUTES - 1)
329
+ await service.getMultipleContent({ contentType: 'plp' })
330
+ expect(clientCP.listEntries).toHaveBeenCalledTimes(1)
331
+
332
+ vi.advanceTimersByTime(1)
333
+ await service.getMultipleContent({ contentType: 'plp' })
334
+ expect(clientCP.listEntries).toHaveBeenCalledTimes(2)
335
+ })
336
+ })
98
337
  })