@faststore/core 4.10.0 → 4.10.1-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
 
2
- > @faststore/core@4.10.0-dev.1 generate /home/runner/work/faststore/faststore/packages/core
2
+ > @faststore/core@4.10.1-dev.0 generate /home/runner/work/faststore/faststore/packages/core
3
3
  > pnpm run gen-types && pnpm run cache-graphql
4
4
 
5
5
 
6
- > @faststore/core@4.10.0-dev.1 gen-types /home/runner/work/faststore/faststore/packages/core
6
+ > @faststore/core@4.10.1-dev.0 gen-types /home/runner/work/faststore/faststore/packages/core
7
7
  > node ../cli/bin/run generate-types .
8
8
 
9
9
  [STARTED] Parse Configuration
@@ -19,7 +19,7 @@
19
19
  [COMPLETED] Generate to @generated/
20
20
  [COMPLETED] Generate outputs
21
21
 
22
- > @faststore/core@4.10.0-dev.1 cache-graphql /home/runner/work/faststore/faststore/packages/core
22
+ > @faststore/core@4.10.1-dev.0 cache-graphql /home/runner/work/faststore/faststore/packages/core
23
23
  > node ../cli/bin/run cache-graphql --config=./discovery.config.default.js --queries=./@generated/persisted-documents.json
24
24
 
25
25
  [Info] - Config file location: /home/runner/work/faststore/faststore/packages/core/discovery.config.default.js
@@ -1,5 +1,5 @@
1
1
 
2
- > @faststore/core@4.10.0-dev.1 test /home/runner/work/faststore/faststore/packages/core
2
+ > @faststore/core@4.10.1-dev.0 test /home/runner/work/faststore/faststore/packages/core
3
3
  > vitest run
4
4
 
5
5
 
@@ -7,30 +7,33 @@
7
7
 
8
8
  Not implemented: navigation to another Document
9
9
  Not implemented: navigation to another Document
10
- ✓  browser  test/sdk/ui/useScrollRestoration.browser.test.ts (43 tests) 1139ms
10
+ ✓  browser  test/sdk/ui/useScrollRestoration.browser.test.ts (43 tests) 1284ms
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) 3084ms
15
- ✓ shows the tab bar and renders Personal cards by default  1102ms
16
- ✓ paginates when there are more than 25 cards  561ms
17
- ✓  browser  test/components/account/ContractSwitcher.browser.test.tsx (22 tests) 3654ms
18
- ✓ shows an error state when the list fails to load  561ms
19
- ✓ requires selecting a contract before Confirm is enabled, then switches (REQ-03)  353ms
20
- ✓ keeps Confirm disabled until a different contract is selected  345ms
21
- ✓ keeps Confirm disabled when switching is unavailable  359ms
22
- ✓ keeps Confirm disabled when the selected contract is filtered out  384ms
23
- ✓ honors CMS-provided labels for the header, current session, search, footer, and available count  339ms
24
- ✓  browser  test/components/account/orders/BuyingPolicyAlert.browser.test.tsx (13 tests) 3027ms
25
- ✓ opens the reject confirmation modal with the default English copy  1153ms
26
- ✓ opens the reject confirmation modal with CMS-provided labels  524ms
27
- ✓  browser  test/components/ui/RichText/RichText.browser.test.tsx (8 tests) 282ms
28
- ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (10 tests) 1296ms
29
- ✓ renders the menu view with contract details and manage footer  699ms
30
- ✓  browser  test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx (13 tests) 154ms
31
- ✓  browser  test/sdk/localization/useBindingSelector.browser.test.tsx (9 tests) 113ms
32
- ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (6 tests) 22ms
33
- ✓  browser  test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx (8 tests) 150ms
14
+ ✓  browser  test/components/account/cards/MyAccountListCards.browser.test.tsx (16 tests) 2990ms
15
+ ✓ shows the tab bar and renders Personal cards by default  1029ms
16
+ ✓ paginates when there are more than 25 cards  575ms
17
+ ✓  browser  test/components/account/ContractSwitcher.browser.test.tsx (22 tests) 4422ms
18
+ ✓ shows an error state when the list fails to load  728ms
19
+ ✓ requires selecting a contract before Confirm is enabled, then switches (REQ-03)  541ms
20
+ ✓ keeps Confirm disabled until a different contract is selected  393ms
21
+ ✓ keeps Confirm disabled when switching is unavailable  372ms
22
+ ✓ keeps Confirm disabled when the selected contract is filtered out  441ms
23
+ ✓ honors CMS-provided labels for the header, current session, search, footer, and available count  431ms
24
+ ✓  browser  test/components/account/orders/BuyingPolicyAlert.browser.test.tsx (13 tests) 3066ms
25
+ ✓ opens the reject confirmation modal with the default English copy  1448ms
26
+ ✓ opens the reject confirmation modal with CMS-provided labels  435ms
27
+ ✓  browser  test/components/ui/RichText/RichText.browser.test.tsx (8 tests) 397ms
28
+ ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (10 tests) 2039ms
29
+ ✓ renders the menu view with contract details and manage footer  1045ms
30
+ ✓ opens the contract switcher and returns to the menu on back  410ms
31
+ ✓ resets to the menu when the drawer closes  323ms
32
+ ✓  browser  test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx (13 tests) 240ms
33
+ ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (6 tests) 11ms
34
+ ✓  browser  test/sdk/localization/useBindingSelector.browser.test.tsx (9 tests) 67ms
35
+ ✓  browser  test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx (8 tests) 99ms
36
+ ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 140ms
34
37
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
35
38
  Error in optimistic validation: TypeError: Failed to parse URL from /api/graphql?operationName=ValidateCartMutation&operationHash=3c71b0d3fc2bade14df68abfa271858beb361b12
36
39
   at node:internal/deps/undici/undici:16416:13
@@ -60,12 +63,10 @@ Browserslist: browsers data (caniuse-lite) is 8 months old. Please run:
60
63
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx > RecommendationShelf > renders nothing when there is an error
61
64
  Error fetching recommendations undefined boom for campaign vrn:recommendations:acc:rec-top-items-v2:campaign-1
62
65
 
63
- ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 116ms
64
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 129ms
65
- ✓  browser  test/components/CartSidebar/CartSidebar.browser.test.tsx (5 tests) 157ms
66
- ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (7 tests) 42ms
67
- ✓  browser  test/components/sections/Account/AccountOrderDetails.browser.test.tsx (4 tests) 62ms
68
- ✓  browser  test/components/navigation/Navbar.browser.test.tsx (3 tests) 40ms
66
+ ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 191ms
67
+ ✓  browser  test/components/CartSidebar/CartSidebar.browser.test.tsx (5 tests) 75ms
68
+ ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (7 tests) 13ms
69
+ ✓  browser  test/components/sections/Account/AccountOrderDetails.browser.test.tsx (4 tests) 77ms
69
70
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > posts the password and redirects to the requested return path
70
71
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
71
72
 
@@ -151,7 +152,7 @@ act(() => {
151
152
  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
152
153
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
153
154
 
154
- ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (8 tests) 360ms
155
+ ✓  browser  test/components/navigation/Navbar.browser.test.tsx (3 tests) 93ms
155
156
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > uses the fallback invalid-password message when API omits an error
156
157
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
157
158
 
@@ -296,8 +297,9 @@ act(() => {
296
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
297
298
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
298
299
 
299
- ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 586ms
300
- ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (5 tests) 40ms
300
+ ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 978ms
301
+ ✓ renders the password protection form  528ms
302
+ ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (8 tests) 560ms
301
303
  stderr | test/components/cms/RenderSections.browser.test.tsx
302
304
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
303
305
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -306,8 +308,9 @@ This ensures that you're testing the behavior the user would see in the browser.
306
308
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
307
309
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
308
310
 
309
- ✓  browser  test/components/navigation/NavbarSlider.browser.test.tsx (2 tests) 43ms
310
- ✓  browser  test/components/cms/RenderSections.browser.test.tsx (5 tests) 91ms
311
+ ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (5 tests) 71ms
312
+ ✓  browser  test/components/navigation/NavbarSlider.browser.test.tsx (2 tests) 48ms
313
+ ✓  browser  test/components/cms/RenderSections.browser.test.tsx (5 tests) 99ms
311
314
  stderr | test/sdk/localization/store-url.browser.test.ts
312
315
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
313
316
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -317,14 +320,14 @@ This ensures that you're testing the behavior the user would see in the browser.
317
320
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
318
321
 
319
322
  ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 6ms
320
- ✓  browser  test/components/account/Menu.browser.test.tsx (6 tests) 345ms
321
- ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 40ms
322
- ✓  browser  test/components/templates/SearchPage/SearchWrapper.browser.test.tsx (3 tests) 48ms
323
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx (2 tests) 134ms
324
- ✓  browser  test/components/sections/Account/AccountListQuotes.browser.test.tsx (4 tests) 135ms
325
- ✓  browser  test/components/navigation/NavbarAccountArea.browser.test.tsx (4 tests) 46ms
326
- ✓  browser  test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx (3 tests) 67ms
327
- ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 18ms
323
+ ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 32ms
324
+ ✓  browser  test/components/account/Menu.browser.test.tsx (6 tests) 385ms
325
+ ✓  browser  test/components/templates/SearchPage/SearchWrapper.browser.test.tsx (3 tests) 29ms
326
+ ✓  browser  test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx (2 tests) 90ms
327
+ ✓  browser  test/components/sections/Account/AccountListQuotes.browser.test.tsx (4 tests) 149ms
328
+ ✓  browser  test/components/navigation/NavbarAccountArea.browser.test.tsx (4 tests) 67ms
329
+ ✓  browser  test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx (3 tests) 66ms
330
+ ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 22ms
328
331
  stderr | test/components/account/doLogout.browser.test.ts
329
332
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
330
333
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -333,9 +336,9 @@ This ensures that you're testing the behavior the user would see in the browser.
333
336
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
334
337
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
335
338
 
336
- ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 25ms
337
- ✓  browser  test/components/ui/LocalizationButton.browser.test.tsx (3 tests) 50ms
338
- ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (5 tests) 367ms
339
+ ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 16ms
340
+ ✓  browser  test/components/ui/LocalizationButton.browser.test.tsx (3 tests) 49ms
341
+ ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (5 tests) 376ms
339
342
  stderr | test/sdk/account/clearPersistedSessionState.browser.test.ts
340
343
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
341
344
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -344,15 +347,14 @@ This ensures that you're testing the behavior the user would see in the browser.
344
347
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
345
348
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
346
349
 
347
- ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (4 tests) 24ms
348
- ✓  browser  test/components/sections/Account/AccountOrderDelivery.browser.test.tsx (2 tests) 109ms
349
- ✓  browser  test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx (2 tests) 88ms
350
- ✓  browser  test/components/sections/Account/AccountOrderStatus.browser.test.tsx (3 tests) 146ms
350
+ ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (4 tests) 21ms
351
+ ✓  browser  test/components/sections/Account/AccountOrderDelivery.browser.test.tsx (2 tests) 100ms
352
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx (2 tests) 102ms
353
+ ✓  browser  test/components/sections/Account/AccountOrderStatus.browser.test.tsx (3 tests) 112ms
351
354
  ✓  browser  test/components/region/useRegion.browser.test.ts (1 test) 26ms
352
- ✓  browser  test/components/sections/Account/AccountListCards.browser.test.tsx (3 tests) 95ms
353
- ✓  browser  test/components/sections/Account/AccountOrdersList.browser.test.tsx (2 tests) 29ms
354
- ✓  browser  test/components/account/orders/SummaryCard.browser.test.tsx (2 tests) 88ms
355
- ✓  browser  test/components/account/OrganizationSignInButton.browser.test.tsx (2 tests) 262ms
355
+ ✓  browser  test/components/sections/Account/AccountListCards.browser.test.tsx (3 tests) 90ms
356
+ ✓  browser  test/components/sections/Account/AccountOrdersList.browser.test.tsx (2 tests) 22ms
357
+ ✓  browser  test/components/account/orders/SummaryCard.browser.test.tsx (2 tests) 101ms
356
358
  stderr | test/sdk/session/sessionStore.browser.test.ts
357
359
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
358
360
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -361,8 +363,9 @@ This ensures that you're testing the behavior the user would see in the browser.
361
363
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
362
364
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
363
365
 
364
- ✓  browser  test/sdk/session/sessionStore.browser.test.ts (2 tests) 8ms
365
- ✓  browser  test/components/account/orders/DeliveryOptionAccordionDeliveryInfo.browser.test.tsx (1 test) 39ms
366
+ ✓  browser  test/sdk/session/sessionStore.browser.test.ts (2 tests) 10ms
367
+ ✓  browser  test/components/account/OrganizationSignInButton.browser.test.tsx (2 tests) 252ms
368
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordionDeliveryInfo.browser.test.tsx (1 test) 48ms
366
369
  stderr | test/sdk/session/validateSession.browser.test.ts
367
370
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
368
371
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -371,15 +374,13 @@ This ensures that you're testing the behavior the user would see in the browser.
371
374
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
372
375
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
373
376
 
374
- ✓  browser  test/sdk/session/validateSession.browser.test.ts (1 test) 6ms
375
- ✓  browser  test/components/account/orders/StatusCard.browser.test.tsx (2 tests) 132ms
377
+ ✓  browser  test/sdk/session/validateSession.browser.test.ts (1 test) 7ms
378
+ ✓  browser  test/components/account/orders/StatusCard.browser.test.tsx (2 tests) 115ms
376
379
  ✓  browser  test/utils/isLocalHost.browser.test.ts (3 tests) 6ms
377
- ✓  browser  test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts (1 test) 3ms
378
- ✓  browser  test/components/account/orders/DeliveryCard.browser.test.tsx (1 test) 50ms
379
- ✓  browser  test/components/skeletons/ProductGridSkeleton.browser.test.tsx (2 tests) 45ms
380
- ✓  browser  test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx (1 test) 35ms
381
- ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 31ms
382
- ✓  node  test/server/password-protection-service.test.ts (22 tests) 43ms
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) 64ms
382
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx (1 test) 71ms
383
+ ✓  browser  test/components/skeletons/ProductGridSkeleton.browser.test.tsx (2 tests) 30ms
383
384
  stderr | test/sdk/account/refreshToken.browser.test.ts
384
385
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
385
386
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -389,10 +390,10 @@ This ensures that you're testing the behavior the user would see in the browser.
389
390
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
390
391
 
391
392
  ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 6ms
392
- ✓  node  test/pages/api/switch-contract.test.ts (25 tests) 78ms
393
- ✓  browser  test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx (1 test) 238ms
394
- ✓  node  test/utils/match-url.test.ts (10 tests) 9ms
395
- ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 12ms
393
+ ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 32ms
394
+ ✓  node  test/server/password-protection-service.test.ts (22 tests) 39ms
395
+ ✓  browser  test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx (1 test) 229ms
396
+ ✓  node  test/pages/api/switch-contract.test.ts (25 tests) 93ms
396
397
  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
397
398
  Failed to fetch pickup points: Error: network
398
399
  at /home/runner/work/faststore/faststore/packages/core/test/sdk/deliveryPromise/provider.effect.test.tsx:250:30
@@ -419,12 +420,16 @@ This ensures that you're testing the behavior the user would see in the browser.
419
420
  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)
420
421
  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)
421
422
 
422
- ✓  node  test/sdk/deliveryPromise/provider.effect.test.tsx (9 tests) 94ms
423
- ✓  node  test/pages/api/unlock.test.ts (15 tests) 172ms
424
- ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (14 tests) 15ms
423
+ ✓  node  test/sdk/deliveryPromise/provider.effect.test.tsx (9 tests) 84ms
424
+ ✓  node  test/server/content/service.test.ts (15 tests) 27ms
425
+ ✓  node  test/utils/match-url.test.ts (10 tests) 11ms
426
+ ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 11ms
427
+ ✓  node  test/pages/api/unlock.test.ts (15 tests) 175ms
428
+ ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (14 tests) 14ms
425
429
  stderr | test/components/search/SearchInputComponent.test.tsx
426
430
  Warning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?
427
431
 
432
+ ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 9ms
428
433
  stderr | test/components/search/SearchInputComponent.test.tsx > SearchInput (OES integration) > renders without crashing
429
434
  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.
430
435
  at div
@@ -485,13 +490,12 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
485
490
  at Suspense
486
491
  at Wrapper (/home/runner/work/faststore/faststore/packages/core/test/components/search/SearchInputComponent.test.tsx:106:20)
487
492
 
488
- ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 9ms
489
- ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 75ms
490
- ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (15 tests) 454ms
491
- ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (11 tests) 830ms
493
+ ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 93ms
494
+ ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (15 tests) 472ms
495
+ ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 50ms
496
+ ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (11 tests) 840ms
492
497
  ✓ navigates with the search term on blur  361ms
493
- ✓ navigates without a label param when the search is cleared  358ms
494
- ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 67ms
498
+ ✓ navigates without a label param when the search is cleared  360ms
495
499
  stderr | test/pages/pvt/account/cards/index.test.ts > Cards page getServerSideProps > redirects to /pvt/account/403 when the query fails with 401/403
496
500
  { extensions: { status: 401 } }
497
501
 
@@ -499,10 +503,10 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
499
503
  { extensions: { status: 502 } }
500
504
 
501
505
  ✓  node  test/pages/pvt/account/cards/index.test.ts (8 tests) 12ms
502
- ✓  node  test/components/quotes/quoteFilters.test.ts (26 tests) 43ms
503
- ✓  node  test/utils/productJsonLd.test.ts (36 tests) 16ms
504
- ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 18ms
505
- ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 21ms
506
+ ✓  node  test/components/quotes/quoteFilters.test.ts (26 tests) 53ms
507
+ ✓  node  test/utils/productJsonLd.test.ts (36 tests) 12ms
508
+ ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 15ms
509
+ ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 10ms
506
510
  stderr | test/pages/api/graphql.test.ts > /api/graphql error status propagation > propagates the upstream 400 from a wrapped BadRequestError instead of 500
507
511
  Graphql execution returned with error: [
508
512
  {
@@ -612,78 +616,78 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
612
616
  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)
613
617
  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)
614
618
 
615
- ✓  node  test/pages/api/graphql.test.ts (19 tests) 34ms
616
- ✓  node  test/utils/clearCookies.test.ts (20 tests) 20ms
617
- ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 7ms
619
+ ✓  node  test/pages/api/graphql.test.ts (19 tests) 44ms
620
+ ✓  node  test/utils/clearCookies.test.ts (20 tests) 29ms
621
+ ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 6ms
618
622
  stderr | test/sdk/account/useSetPassword.test.ts > useSetPassword > maps wrongcredentials error responses to the existing message
619
623
  Set password request failed: Bad Request
620
624
 
621
- ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 49ms
625
+ ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 50ms
622
626
  ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 49ms
623
- ✓  node  test/utils/localization/localizeRedirectDestination.test.ts (11 tests) 6ms
624
- ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (8 tests) 193ms
625
- ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (14 tests) 181ms
626
- ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (13 tests) 10ms
627
- ✓  node  test/utils/localization/hreflang.test.ts (8 tests) 7ms
628
- ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (10 tests) 95ms
629
- ✓  node  test/sdk/redirects/index.test.ts (7 tests) 49ms
630
- ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 55ms
631
- ✓  node  test/proxy.localization.test.ts (5 tests) 39ms
632
- ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 53ms
633
- ✓  node  test/components/account/orders/getDeliveryOptionLabel.test.ts (21 tests) 10ms
634
- ✓  node  test/sdk/graphql/retryPolicy.test.ts (18 tests) 25ms
635
- ✓  node  test/server/cms/myAccountDefaultSections.test.ts (9 tests) 7ms
636
- ✓  node  test/utils/productJsonLdRendering.test.tsx (5 tests) 21ms
637
- ✓  node  test/utils/trustedForwardedHost.test.ts (27 tests) 9ms
638
- ✓  node  test/server/index.test.ts (7 tests) 1123ms
639
- ✓ should return a valid merged GraphQL schema  302ms
640
- ✓ should handle options and execute  548ms
641
- ✓  node  test/proxy.test.ts (4 tests) 26ms
642
- ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 52ms
643
- ✓  node  test/server/webops-api.test.ts (9 tests) 17ms
644
- ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 46ms
645
- ✓  node  test/sdk/localization/store-url.test.ts (4 tests) 5ms
627
+ ✓  node  test/utils/localization/localizeRedirectDestination.test.ts (11 tests) 9ms
628
+ ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (8 tests) 209ms
629
+ ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (14 tests) 198ms
630
+ ✓  node  test/utils/localization/hreflang.test.ts (8 tests) 8ms
631
+ ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (13 tests) 7ms
632
+ ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (10 tests) 127ms
633
+ ✓  node  test/sdk/redirects/index.test.ts (7 tests) 50ms
634
+ ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 51ms
635
+ ✓  node  test/proxy.localization.test.ts (5 tests) 60ms
636
+ ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 52ms
637
+ ✓  node  test/components/account/orders/getDeliveryOptionLabel.test.ts (21 tests) 8ms
638
+ ✓  node  test/sdk/graphql/retryPolicy.test.ts (18 tests) 18ms
639
+ ✓  node  test/server/cms/myAccountDefaultSections.test.ts (9 tests) 8ms
640
+ ✓  node  test/utils/productJsonLdRendering.test.tsx (5 tests) 16ms
641
+ ✓  node  test/utils/trustedForwardedHost.test.ts (27 tests) 11ms
642
+ ✓  node  test/server/index.test.ts (7 tests) 1039ms
643
+ ✓ should return a valid merged GraphQL schema  314ms
644
+ ✓ should exist with its plugins  317ms
645
+ ✓ should handle options and execute  393ms
646
+ ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 56ms
647
+ ✓  node  test/proxy.test.ts (4 tests) 22ms
648
+ ✓  node  test/server/webops-api.test.ts (9 tests) 18ms
649
+ ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 88ms
646
650
  ✓  node  test/components/account/orders/StatusCard.test.ts (3 tests) 32ms
647
- ✓  node  test/sdk/deliveryPromise/queries.test.ts (4 tests) 11ms
648
- ✓  node  test/sdk/cart/waitForSessionValidated.test.ts (5 tests) 24ms
649
- ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 8ms
650
- ✓  node  test/server/content/service.test.ts (5 tests) 6ms
651
- ✓  node  test/server/cms/global.test.ts (3 tests) 5ms
652
- ✓  node  test/sdk/deliveryPromise/provider.test.ts (11 tests) 6ms
653
- ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 32ms
654
- ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 10ms
655
- ✓  node  test/components/account/Layout.test.tsx (6 tests) 74ms
656
- ✓  node  test/pages/api/preview.test.ts (2 tests) 10ms
657
- ✓  node  test/server/rootSpan.test.ts (4 tests) 55ms
658
- ✓  node  test/components/search/SearchInput.test.ts (6 tests) 7ms
659
- ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 4ms
660
- ✓  node  test/sdk/recommendations/vrn.test.ts (5 tests) 6ms
661
- ✓  node  test/server/cms/index.test.ts (2 tests) 4ms
662
- ✓  node  test/utils/retry.test.ts (5 tests) 20ms
663
- ✓  node  test/components/region/useRegion.test.ts (1 test) 25ms
664
- ✓  node  test/sdk/recommendations/useRecommendationUserId.test.ts (4 tests) 196ms
665
- ✓  node  test/sdk/account/getB2BSessionClaims.test.ts (4 tests) 12ms
666
- ✓  node  test/utils/userOrderStatus.test.ts (4 tests) 5ms
667
- ✓  node  test/utils/unlockResponse.test.ts (11 tests) 6ms
651
+ ✓  node  test/sdk/localization/store-url.test.ts (4 tests) 6ms
652
+ ✓  node  test/sdk/deliveryPromise/queries.test.ts (4 tests) 8ms
653
+ ✓  node  test/sdk/cart/waitForSessionValidated.test.ts (5 tests) 13ms
654
+ ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 10ms
655
+ ✓  node  test/server/cms/global.test.ts (3 tests) 6ms
656
+ ✓  node  test/sdk/deliveryPromise/provider.test.ts (11 tests) 7ms
657
+ ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 37ms
658
+ ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 5ms
659
+ ✓  node  test/components/account/Layout.test.tsx (6 tests) 85ms
660
+ ✓  node  test/server/rootSpan.test.ts (4 tests) 51ms
661
+ ✓  node  test/pages/api/preview.test.ts (2 tests) 11ms
662
+ ✓  node  test/components/search/SearchInput.test.ts (6 tests) 8ms
663
+ ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 6ms
664
+ ✓  node  test/sdk/recommendations/vrn.test.ts (5 tests) 5ms
665
+ ✓  node  test/server/cms/index.test.ts (2 tests) 6ms
666
+ ✓  node  test/components/region/useRegion.test.ts (1 test) 27ms
667
+ ✓  node  test/utils/retry.test.ts (5 tests) 16ms
668
+ ✓  node  test/sdk/account/getB2BSessionClaims.test.ts (4 tests) 5ms
669
+ ✓  node  test/utils/userOrderStatus.test.ts (4 tests) 4ms
670
+ ✓  node  test/sdk/recommendations/useRecommendationUserId.test.ts (4 tests) 195ms
671
+ ✓  node  test/utils/unlockResponse.test.ts (11 tests) 5ms
668
672
  ✓  node  test/sdk/recommendations/useRecommendations.test.ts (2 tests) 6ms
669
- ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 56ms
670
- ✓  node  test/sdk/deliveryPromise/reducer.test.ts (2 tests) 8ms
671
- ✓  node  test/utils/isLocalHost.test.ts (7 tests) 6ms
672
- ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (4 tests) 47ms
673
- ✓  node  test/components/account/orders/getOrderTotalLabel.test.ts (6 tests) 5ms
673
+ ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 57ms
674
+ ✓  node  test/sdk/deliveryPromise/reducer.test.ts (2 tests) 6ms
675
+ ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (4 tests) 35ms
676
+ ✓  node  test/utils/isLocalHost.test.ts (7 tests) 5ms
674
677
  ✓  node  test/sdk/session/toSessionInput.test.ts (3 tests) 4ms
678
+ ✓  node  test/components/account/orders/getOrderTotalLabel.test.ts (6 tests) 6ms
679
+ ✓  node  test/components/account/orders/listOrdersLabels.test.ts (2 tests) 5ms
675
680
  ✓  node  test/utils/getCookie.test.ts (4 tests) 5ms
676
- ✓  node  test/components/account/orders/listOrdersLabels.test.ts (2 tests) 4ms
677
- ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 44ms
678
- ✓  node  test/utils/filterChannel.test.ts (2 tests) 4ms
679
- ✓  node  test/sdk/session/isSignInAreaResolved.test.ts (3 tests) 4ms
681
+ ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 39ms
682
+ ✓  node  test/sdk/session/isSignInAreaResolved.test.ts (3 tests) 3ms
683
+ ✓  node  test/utils/filterChannel.test.ts (2 tests) 6ms
680
684
  ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 5ms
681
- ✓  node  test/instrumentation.test.ts (3 tests) 7ms
682
- ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 14ms
683
- ✓  node  test/components/account/orders/orderDetailsLabels.test.ts (1 test) 3ms
685
+ ✓  node  test/instrumentation.test.ts (3 tests) 5ms
686
+ ✓  node  test/components/account/orders/orderDetailsLabels.test.ts (1 test) 4ms
687
+ ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 12ms
684
688
 
685
689
   Test Files  135 passed (135)
686
-  Tests  1066 passed (1066)
687
-  Start at  14:49:23
688
-  Duration  63.45s (transform 15.36s, setup 0ms, import 55.98s, tests 22.27s, environment 77.96s)
690
+  Tests  1076 passed (1076)
691
+  Start at  16:44:28
692
+  Duration  67.24s (transform 18.40s, setup 0ms, import 60.45s, tests 24.70s, environment 81.30s)
689
693
 
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.1](https://github.com/vtex/faststore/compare/v4.10.1-dev.0...v4.10.1-dev.1) (2026-10-21)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **core:** cache Content Platform template list across page generations ([#3526](https://github.com/vtex/faststore/issues/3526)) ([b03ed33](https://github.com/vtex/faststore/commit/b03ed332bd8c4ea5ef4bb834b8590728946852e5)), closes [#3384](https://github.com/vtex/faststore/issues/3384) [#3384](https://github.com/vtex/faststore/issues/3384)
11
+
12
+ ## [4.10.1-dev.0](https://github.com/vtex/faststore/compare/v4.10.0...v4.10.1-dev.0) (2026-10-15)
13
+
14
+ **Note:** Version bump only for package @faststore/core
15
+
6
16
  # [4.10.0](https://github.com/vtex/faststore/compare/v4.10.0-dev.1...v4.10.0) (2026-10-15)
7
17
 
8
18
  **Note:** Version bump only for package @faststore/core
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "4.10.0",
3
+ "version": "4.10.1-dev.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -71,11 +71,11 @@
71
71
  "style-loader": "^3.3.1",
72
72
  "swr": "^2.2.5",
73
73
  "use-sync-external-store": "^1.6.0",
74
- "@faststore/api": "4.10.0",
75
- "@faststore/diagnostics": "4.10.0",
76
- "@faststore/lighthouse": "4.10.0",
77
- "@faststore/sdk": "4.10.0",
78
- "@faststore/ui": "4.10.0"
74
+ "@faststore/api": "4.10.1-dev.1",
75
+ "@faststore/lighthouse": "4.10.1-dev.1",
76
+ "@faststore/diagnostics": "4.10.1-dev.1",
77
+ "@faststore/sdk": "4.10.1-dev.1",
78
+ "@faststore/ui": "4.10.1-dev.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
  })