@faststore/core 4.10.1-dev.0 → 4.10.1-dev.2

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