@faststore/core 4.10.1-dev.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 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 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 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 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,26 +7,33 @@
7
7
 
8
8
  Not implemented: navigation to another Document
9
9
  Not implemented: navigation to another Document
10
- ✓  browser  test/sdk/ui/useScrollRestoration.browser.test.ts (43 tests) 1018ms
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) 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) 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
30
37
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
31
38
  Error in optimistic validation: TypeError: Failed to parse URL from /api/graphql?operationName=ValidateCartMutation&operationHash=3c71b0d3fc2bade14df68abfa271858beb361b12
32
39
   at node:internal/deps/undici/undici:16416:13
@@ -56,10 +63,10 @@ Browserslist: browsers data (caniuse-lite) is 8 months old. Please run:
56
63
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx > RecommendationShelf > renders nothing when there is an error
57
64
  Error fetching recommendations undefined boom for campaign vrn:recommendations:acc:rec-top-items-v2:campaign-1
58
65
 
59
- ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 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
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
63
70
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > posts the password and redirects to the requested return path
64
71
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
65
72
 
@@ -97,7 +104,6 @@ act(() => {
97
104
  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
105
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
99
106
 
100
- ✓  browser  test/components/navigation/Navbar.browser.test.tsx (3 tests) 44ms
101
107
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > defaults returnTo to root and shows API validation errors
102
108
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
103
109
 
@@ -146,6 +152,7 @@ act(() => {
146
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
147
153
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
148
154
 
155
+ ✓  browser  test/components/navigation/Navbar.browser.test.tsx (3 tests) 93ms
149
156
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > uses the fallback invalid-password message when API omits an error
150
157
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
151
158
 
@@ -290,10 +297,9 @@ 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) 978ms
301
+ ✓ renders the password protection form  528ms
302
+ ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (8 tests) 560ms
297
303
  stderr | test/components/cms/RenderSections.browser.test.tsx
298
304
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
299
305
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -302,8 +308,9 @@ This ensures that you're testing the behavior the user would see in the browser.
302
308
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
303
309
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
304
310
 
305
- ✓  browser  test/components/navigation/NavbarSlider.browser.test.tsx (2 tests) 34ms
306
- ✓  browser  test/components/cms/RenderSections.browser.test.tsx (5 tests) 65ms
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
307
314
  stderr | test/sdk/localization/store-url.browser.test.ts
308
315
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
309
316
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -312,15 +319,15 @@ This ensures that you're testing the behavior the user would see in the browser.
312
319
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
313
320
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
314
321
 
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
322
+ ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 6ms
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
324
331
  stderr | test/components/account/doLogout.browser.test.ts
325
332
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
326
333
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -329,9 +336,9 @@ This ensures that you're testing the behavior the user would see in the browser.
329
336
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
330
337
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
331
338
 
332
- ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 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
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
335
342
  stderr | test/sdk/account/clearPersistedSessionState.browser.test.ts
336
343
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
337
344
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -340,14 +347,14 @@ This ensures that you're testing the behavior the user would see in the browser.
340
347
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
341
348
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
342
349
 
343
- ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (4 tests) 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
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
354
+ ✓  browser  test/components/region/useRegion.browser.test.ts (1 test) 26ms
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
351
358
  stderr | test/sdk/session/sessionStore.browser.test.ts
352
359
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
353
360
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -356,9 +363,9 @@ This ensures that you're testing the behavior the user would see in the browser.
356
363
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
357
364
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
358
365
 
359
- ✓  browser  test/components/account/OrganizationSignInButton.browser.test.tsx (2 tests) 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
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
362
369
  stderr | test/sdk/session/validateSession.browser.test.ts
363
370
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
364
371
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -367,13 +374,13 @@ This ensures that you're testing the behavior the user would see in the browser.
367
374
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
368
375
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
369
376
 
370
- ✓  browser  test/sdk/session/validateSession.browser.test.ts (1 test) 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
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
379
+ ✓  browser  test/utils/isLocalHost.browser.test.ts (3 tests) 6ms
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
377
384
  stderr | test/sdk/account/refreshToken.browser.test.ts
378
385
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
379
386
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -382,12 +389,11 @@ This ensures that you're testing the behavior the user would see in the browser.
382
389
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
383
390
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
384
391
 
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
392
+ ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 6ms
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
391
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
392
398
  Failed to fetch pickup points: Error: network
393
399
  at /home/runner/work/faststore/faststore/packages/core/test/sdk/deliveryPromise/provider.effect.test.tsx:250:30
@@ -414,13 +420,16 @@ This ensures that you're testing the behavior the user would see in the browser.
414
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)
415
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)
416
422
 
417
- ✓  node  test/sdk/deliveryPromise/provider.effect.test.tsx (9 tests) 64ms
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
418
426
  ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 11ms
419
- ✓  node  test/pages/api/unlock.test.ts (15 tests) 126ms
427
+ ✓  node  test/pages/api/unlock.test.ts (15 tests) 175ms
428
+ ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (14 tests) 14ms
420
429
  stderr | test/components/search/SearchInputComponent.test.tsx
421
430
  Warning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?
422
431
 
423
- ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (14 tests) 10ms
432
+ ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 9ms
424
433
  stderr | test/components/search/SearchInputComponent.test.tsx > SearchInput (OES integration) > renders without crashing
425
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.
426
435
  at div
@@ -481,24 +490,23 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
481
490
  at Suspense
482
491
  at Wrapper (/home/runner/work/faststore/faststore/packages/core/test/components/search/SearchInputComponent.test.tsx:106:20)
483
492
 
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
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
497
+ ✓ navigates with the search term on blur  361ms
498
+ ✓ navigates without a label param when the search is cleared  360ms
488
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
489
500
  { extensions: { status: 401 } }
490
501
 
491
502
  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
503
  { extensions: { status: 502 } }
493
504
 
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
505
+ ✓  node  test/pages/pvt/account/cards/index.test.ts (8 tests) 12ms
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
502
510
  stderr | test/pages/api/graphql.test.ts > /api/graphql error status propagation > propagates the upstream 400 from a wrapped BadRequestError instead of 500
503
511
  Graphql execution returned with error: [
504
512
  {
@@ -608,77 +616,78 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
608
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)
609
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)
610
618
 
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
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
614
622
  stderr | test/sdk/account/useSetPassword.test.ts > useSetPassword > maps wrongcredentials error responses to the existing message
615
623
  Set password request failed: Bad Request
616
624
 
617
- ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 38ms
618
- ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 34ms
619
- ✓  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
653
- ✓  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
657
- ✓  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
625
+ ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 50ms
626
+ ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 49ms
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
650
+ ✓  node  test/components/account/orders/StatusCard.test.ts (3 tests) 32ms
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
672
+ ✓  node  test/sdk/recommendations/useRecommendations.test.ts (2 tests) 6ms
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
668
677
  ✓  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
678
+ ✓  node  test/components/account/orders/getOrderTotalLabel.test.ts (6 tests) 6ms
679
+ ✓  node  test/components/account/orders/listOrdersLabels.test.ts (2 tests) 5ms
680
+ ✓  node  test/utils/getCookie.test.ts (4 tests) 5ms
681
+ ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 39ms
673
682
  ✓  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
683
+ ✓  node  test/utils/filterChannel.test.ts (2 tests) 6ms
684
+ ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 5ms
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
679
688
 
680
689
   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)
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)
684
693
 
package/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [4.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
+
6
12
  ## [4.10.1-dev.0](https://github.com/vtex/faststore/compare/v4.10.0...v4.10.1-dev.0) (2026-10-15)
7
13
 
8
14
  **Note:** Version bump only for package @faststore/core
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "4.10.1-dev.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.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-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
  })