@faststore/core 4.7.0-dev.4 → 4.7.0-dev.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
 
2
- > @faststore/core@4.7.0-dev.3 generate /home/runner/work/faststore/faststore/packages/core
2
+ > @faststore/core@4.7.0-dev.5 generate /home/runner/work/faststore/faststore/packages/core
3
3
  > pnpm run gen-types && pnpm run cache-graphql
4
4
 
5
5
 
6
- > @faststore/core@4.7.0-dev.3 gen-types /home/runner/work/faststore/faststore/packages/core
6
+ > @faststore/core@4.7.0-dev.5 gen-types /home/runner/work/faststore/faststore/packages/core
7
7
  > node ../cli/bin/run generate-types .
8
8
 
9
9
  [STARTED] Parse Configuration
@@ -19,7 +19,7 @@
19
19
  [COMPLETED] Generate to /home/runner/work/faststore/faststore/packages/core/@generated/
20
20
  [COMPLETED] Generate outputs
21
21
 
22
- > @faststore/core@4.7.0-dev.3 cache-graphql /home/runner/work/faststore/faststore/packages/core
22
+ > @faststore/core@4.7.0-dev.5 cache-graphql /home/runner/work/faststore/faststore/packages/core
23
23
  > node ../cli/bin/run cache-graphql --config=./discovery.config.default.js --queries=./@generated/persisted-documents.json
24
24
 
25
25
  [Info] - Config file location: /home/runner/work/faststore/faststore/packages/core/discovery.config.default.js
@@ -1,25 +1,25 @@
1
1
 
2
- > @faststore/core@4.7.0-dev.3 test /home/runner/work/faststore/faststore/packages/core
2
+ > @faststore/core@4.7.0-dev.5 test /home/runner/work/faststore/faststore/packages/core
3
3
  > vitest run
4
4
 
5
5
 
6
6
   RUN  v4.0.18 /home/runner/work/faststore/faststore/packages/core
7
7
 
8
+ Not implemented: navigation to another Document
9
+ Not implemented: navigation to another Document
10
+ ✓  browser  test/sdk/ui/useScrollRestoration.browser.test.ts (43 tests) 831ms
8
11
  Browserslist: browsers data (caniuse-lite) is 6 months old. Please run:
9
12
  npx update-browserslist-db@latest
10
13
  Why you should do it regularly: https://github.com/browserslist/update-db#readme
11
- Not implemented: navigation to another Document
12
- Not implemented: navigation to another Document
13
- ✓  browser  test/components/account/ContractSwitcher.browser.test.tsx (13 tests) 3129ms
14
- ✓ shows an error state when the list fails to load  876ms
15
- ✓ requires selecting a contract before Confirm is enabled, then switches (REQ-03)  533ms
16
- ✓  browser  test/sdk/ui/useScrollRestoration.browser.test.ts (43 tests) 1002ms
17
- ✓  browser  test/components/account/cards/MyAccountListCards.browser.test.tsx (14 tests) 2391ms
18
- ✓ shows the tab bar and renders Personal cards by default  1007ms
19
- ✓ paginates when there are more than 25 cards  358ms
20
- ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (5 tests) 30ms
21
- ✓  browser  test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx (8 tests) 162ms
22
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 128ms
14
+ ✓  browser  test/components/account/ContractSwitcher.browser.test.tsx (13 tests) 3134ms
15
+ ✓ shows an error state when the list fails to load  839ms
16
+ ✓ requires selecting a contract before Confirm is enabled, then switches (REQ-03)  541ms
17
+ ✓  browser  test/components/account/cards/MyAccountListCards.browser.test.tsx (14 tests) 2789ms
18
+ ✓ shows the tab bar and renders Personal cards by default  1093ms
19
+ ✓ paginates when there are more than 25 cards  498ms
20
+ ✓  browser  test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx (8 tests) 200ms
21
+ ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (5 tests) 41ms
22
+ ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 166ms
23
23
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
24
24
  Error in optimistic validation: TypeError: Failed to parse URL from /api/graphql?operationName=ValidateCartMutation&operationHash=3c71b0d3fc2bade14df68abfa271858beb361b12
25
25
   at node:internal/deps/undici/undici:16416:13
@@ -49,7 +49,8 @@ Not implemented: navigation to another Document
49
49
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx > RecommendationShelf > renders nothing when there is an error
50
50
  Error fetching recommendations undefined boom for campaign vrn:recommendations:acc:rec-top-items-v2:campaign-1
51
51
 
52
- ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (7 tests) 255ms
52
+ ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (7 tests) 258ms
53
+ ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (6 tests) 40ms
53
54
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > posts the password and redirects to the requested return path
54
55
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
55
56
 
@@ -231,6 +232,9 @@ act(() => {
231
232
  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
232
233
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
233
234
 
235
+ ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (5 tests) 1967ms
236
+ ✓ renders the menu view with contract details and manage footer  987ms
237
+ ✓ opens the contract switcher and returns to the menu on back  418ms
234
238
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > shows service unavailable when the login request fails
235
239
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
236
240
 
@@ -279,13 +283,9 @@ act(() => {
279
283
  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
280
284
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
281
285
 
282
- ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 1530ms
283
- ✓ renders the password protection form  544ms
284
- ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (5 tests) 1630ms
285
- ✓ renders the menu view with contract details and manage footer  917ms
286
- ✓ opens the contract switcher and returns to the menu on back  372ms
287
- ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (6 tests) 38ms
288
- ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 56ms
286
+ ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 1685ms
287
+ ✓ renders the password protection form  940ms
288
+ ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 24ms
289
289
  stderr | test/sdk/localization/store-url.browser.test.ts
290
290
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
291
291
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -294,11 +294,20 @@ This ensures that you're testing the behavior the user would see in the browser.
294
294
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:354:23)
295
295
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
296
296
 
297
- ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 5ms
298
- ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 41ms
299
- ✓  browser  test/components/templates/SearchPage/SearchWrapper.browser.test.tsx (3 tests) 30ms
300
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx (2 tests) 132ms
301
- ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (4 tests) 39ms
297
+ ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 6ms
298
+ stderr | test/components/cms/RenderSections.browser.test.tsx
299
+ Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
300
+ at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
301
+ at validateCart (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/index.ts:120:9)
302
+ at onValidate (/home/runner/work/faststore/faststore/packages/core/src/sdk/useStore.ts:18:20)
303
+ at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:354:23)
304
+  at processTicksAndRejections (node:internal/process/task_queues:103:5)
305
+
306
+ ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 40ms
307
+ ✓  browser  test/components/cms/RenderSections.browser.test.tsx (5 tests) 97ms
308
+ ✓  browser  test/components/templates/SearchPage/SearchWrapper.browser.test.tsx (3 tests) 44ms
309
+ ✓  browser  test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx (2 tests) 134ms
310
+ ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (4 tests) 47ms
302
311
  stderr | test/components/account/doLogout.browser.test.ts
303
312
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
304
313
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -307,10 +316,10 @@ This ensures that you're testing the behavior the user would see in the browser.
307
316
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:354:23)
308
317
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
309
318
 
310
- ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 22ms
311
- ✓  browser  test/components/sections/Account/AccountListCards.browser.test.tsx (3 tests) 98ms
312
- ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (3 tests) 301ms
313
- ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (3 tests) 275ms
319
+ ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 24ms
320
+ ✓  browser  test/components/sections/Account/AccountListCards.browser.test.tsx (3 tests) 91ms
321
+ ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (3 tests) 286ms
322
+ ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (3 tests) 338ms
314
323
  stderr | test/sdk/account/clearPersistedSessionState.browser.test.ts
315
324
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
316
325
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -319,11 +328,11 @@ This ensures that you're testing the behavior the user would see in the browser.
319
328
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:354:23)
320
329
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
321
330
 
322
- ✓  browser  test/utils/isLocalHost.browser.test.ts (3 tests) 5ms
323
331
  ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (1 test) 8ms
324
- ✓  browser  test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts (1 test) 3ms
325
- ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 19ms
326
- ✓  node  test/server/password-protection-service.test.ts (22 tests) 34ms
332
+ ✓  browser  test/utils/isLocalHost.browser.test.ts (3 tests) 4ms
333
+ ✓  browser  test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts (1 test) 4ms
334
+ ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 20ms
335
+ ✓  node  test/server/password-protection-service.test.ts (22 tests) 42ms
327
336
  stderr | test/sdk/account/refreshToken.browser.test.ts
328
337
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
329
338
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -332,12 +341,12 @@ This ensures that you're testing the behavior the user would see in the browser.
332
341
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:354:23)
333
342
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
334
343
 
335
- ✓  browser  test/components/skeletons/ProductGridSkeleton.browser.test.tsx (2 tests) 38ms
336
- ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 3ms
337
- ✓  node  test/utils/match-url.test.ts (10 tests) 9ms
338
- ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 14ms
339
- ✓  node  test/pages/api/unlock.test.ts (15 tests) 151ms
340
- ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 7ms
344
+ ✓  browser  test/components/skeletons/ProductGridSkeleton.browser.test.tsx (2 tests) 39ms
345
+ ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 8ms
346
+ ✓  node  test/utils/match-url.test.ts (10 tests) 24ms
347
+ ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 17ms
348
+ ✓  node  test/pages/api/unlock.test.ts (15 tests) 169ms
349
+ ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 10ms
341
350
  stderr | test/components/search/SearchInputComponent.test.tsx
342
351
  Warning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?
343
352
 
@@ -401,19 +410,19 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
401
410
  at Suspense
402
411
  at Wrapper (/home/runner/work/faststore/faststore/packages/core/test/components/search/SearchInputComponent.test.tsx:106:20)
403
412
 
404
- ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 74ms
405
- ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 59ms
406
- ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 12ms
413
+ ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 83ms
414
+ ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 52ms
415
+ ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 19ms
407
416
  stderr | test/pages/pvt/account/cards/index.test.ts > Cards page getServerSideProps > redirects to /pvt/account/403 when the query fails with 401/403
408
417
  { extensions: { status: 401 } }
409
418
 
410
419
  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
411
420
  { extensions: { status: 502 } }
412
421
 
413
- ✓  node  test/pages/pvt/account/cards/index.test.ts (8 tests) 10ms
414
- ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 7ms
415
- ✓  node  test/utils/clearCookies.test.ts (20 tests) 30ms
416
- ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (12 tests) 380ms
422
+ ✓  node  test/pages/pvt/account/cards/index.test.ts (8 tests) 12ms
423
+ ✓  node  test/utils/clearCookies.test.ts (20 tests) 28ms
424
+ ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 11ms
425
+ ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (12 tests) 445ms
417
426
  stderr | test/pages/api/graphql.test.ts > /api/graphql error status propagation > propagates the upstream 400 from a wrapped BadRequestError instead of 500
418
427
  Graphql execution returned with error: [
419
428
  {
@@ -507,62 +516,66 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
507
516
  at Traces.$ (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/traces.CCmnQaNT.js:142:27)
508
517
 
509
518
  ✓  node  test/pages/api/graphql.test.ts (17 tests) 29ms
519
+ ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (9 tests) 848ms
520
+ ✓ navigates with the search term on blur  365ms
521
+ ✓ navigates without a label param when the search is cleared  362ms
510
522
  stderr | test/sdk/account/useSetPassword.test.ts > useSetPassword > maps wrongcredentials error responses to the existing message
511
523
  Set password request failed: Bad Request
512
524
 
513
- ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 28ms
514
- ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (9 tests) 806ms
515
- ✓ navigates with the search term on blur  360ms
516
- ✓ navigates without a label param when the search is cleared  359ms
517
- ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 30ms
518
- ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 5ms
519
- ✓  node  test/components/quotes/quoteFilters.test.ts (23 tests) 25ms
520
- ✓  node  test/utils/localization/localizeRedirectDestination.test.ts (11 tests) 5ms
521
- ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (12 tests) 9ms
522
- ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 56ms
523
- ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (5 tests) 136ms
524
- ✓  node  test/proxy.localization.test.ts (5 tests) 37ms
525
- ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 30ms
526
- ✓  node  test/server/index.test.ts (7 tests) 820ms
527
- ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (6 tests) 9ms
528
- ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (8 tests) 117ms
529
- ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 57ms
530
- ✓  node  test/proxy.test.ts (4 tests) 19ms
531
- ✓  node  test/server/webops-api.test.ts (9 tests) 13ms
532
- ✓  node  test/sdk/cart/waitForSessionValidated.test.ts (5 tests) 11ms
533
- ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 35ms
534
- ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 7ms
535
- ✓  node  test/server/content/service.test.ts (5 tests) 5ms
525
+ ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 59ms
526
+ ✓  node  test/components/quotes/quoteFilters.test.ts (23 tests) 40ms
527
+ ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 44ms
528
+ ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 7ms
529
+ ✓  node  test/utils/localization/localizeRedirectDestination.test.ts (11 tests) 8ms
530
+ ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (12 tests) 10ms
531
+ ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 51ms
532
+ ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (5 tests) 166ms
533
+ ✓  node  test/utils/productJsonLd.test.ts (17 tests) 10ms
534
+ ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 47ms
535
+ ✓  node  test/proxy.localization.test.ts (5 tests) 56ms
536
+ ✓  node  test/server/index.test.ts (7 tests) 964ms
537
+ ✓ should return a valid merged GraphQL schema  355ms
538
+ ✓ should handle options and execute  303ms
539
+ ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (6 tests) 8ms
540
+ ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 54ms
541
+ ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (8 tests) 122ms
542
+ ✓  node  test/proxy.test.ts (4 tests) 22ms
543
+ ✓  node  test/server/webops-api.test.ts (9 tests) 14ms
544
+ ✓  node  test/sdk/cart/waitForSessionValidated.test.ts (5 tests) 17ms
545
+ ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 39ms
546
+ ✓  node  test/server/content/service.test.ts (5 tests) 6ms
547
+ ✓  node  test/server/cms/myAccountDefaultSections.test.ts (7 tests) 6ms
548
+ ✓  node  test/utils/productJsonLdRendering.test.tsx (4 tests) 15ms
549
+ ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 9ms
536
550
  ✓  node  test/server/cms/global.test.ts (3 tests) 6ms
537
551
  ✓  node  test/sdk/localization/store-url.test.ts (1 test) 3ms
538
- ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 22ms
539
- ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (6 tests) 126ms
540
- ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 7ms
541
- ✓  node  test/server/rootSpan.test.ts (4 tests) 39ms
542
- ✓  node  test/pages/api/preview.test.ts (2 tests) 8ms
543
- ✓  node  test/components/search/SearchInput.test.ts (6 tests) 9ms
544
- ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 10ms
545
- ✓  node  test/components/account/Layout.test.tsx (6 tests) 63ms
546
- ✓  node  test/sdk/recommendations/vrn.test.ts (5 tests) 4ms
547
- ✓  node  test/utils/retry.test.ts (5 tests) 14ms
552
+ ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 34ms
553
+ ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (6 tests) 144ms
554
+ ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 8ms
555
+ ✓  node  test/server/rootSpan.test.ts (4 tests) 72ms
556
+ ✓  node  test/pages/api/preview.test.ts (2 tests) 12ms
557
+ ✓  node  test/components/account/Layout.test.tsx (6 tests) 80ms
558
+ ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 5ms
559
+ ✓  node  test/components/search/SearchInput.test.ts (6 tests) 17ms
560
+ ✓  node  test/sdk/recommendations/vrn.test.ts (5 tests) 6ms
561
+ ✓  node  test/utils/retry.test.ts (5 tests) 15ms
548
562
  ✓  node  test/server/cms/index.test.ts (2 tests) 6ms
549
- ✓  node  test/sdk/account/getB2BSessionClaims.test.ts (4 tests) 4ms
550
- ✓  node  test/utils/unlockResponse.test.ts (11 tests) 4ms
551
- ✓  node  test/server/cms/myAccountDefaultSections.test.ts (5 tests) 4ms
552
- ✓  node  test/sdk/recommendations/useRecommendationUserId.test.ts (4 tests) 191ms
553
- ✓  node  test/sdk/recommendations/useRecommendations.test.ts (2 tests) 5ms
554
- ✓  node  test/utils/isLocalHost.test.ts (7 tests) 4ms
555
- ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 54ms
556
- ✓  node  test/utils/userOrderStatus.test.ts (3 tests) 3ms
557
- ✓  node  test/utils/getCookie.test.ts (4 tests) 5ms
558
- ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 4ms
559
- ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 38ms
560
- ✓  node  test/instrumentation.test.ts (3 tests) 7ms
561
- ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 15ms
562
- ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (2 tests) 24ms
563
-
564
-  Test Files  87 passed (87)
565
-  Tests  695 passed (695)
566
-  Start at  21:11:21
567
-  Duration  43.29s (transform 10.74s, setup 0ms, import 37.56s, tests 15.12s, environment 54.08s)
563
+ ✓  node  test/sdk/account/getB2BSessionClaims.test.ts (4 tests) 6ms
564
+ ✓  node  test/utils/unlockResponse.test.ts (11 tests) 5ms
565
+ ✓  node  test/sdk/recommendations/useRecommendations.test.ts (2 tests) 4ms
566
+ ✓  node  test/sdk/recommendations/useRecommendationUserId.test.ts (4 tests) 190ms
567
+ ✓  node  test/utils/isLocalHost.test.ts (7 tests) 6ms
568
+ ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 57ms
569
+ ✓  node  test/utils/userOrderStatus.test.ts (3 tests) 5ms
570
+ ✓  node  test/utils/getCookie.test.ts (4 tests) 6ms
571
+ ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 5ms
572
+ ✓  node  test/instrumentation.test.ts (3 tests) 5ms
573
+ ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 42ms
574
+ ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 13ms
575
+ ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (2 tests) 32ms
576
+
577
+  Test Files  90 passed (90)
578
+  Tests  723 passed (723)
579
+  Start at  14:52:14
580
+  Duration  46.99s (transform 11.84s, setup 0ms, import 41.77s, tests 16.67s, environment 57.05s)
568
581
 
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.7.0-dev.6](https://github.com/vtex/faststore/compare/v4.7.0-dev.5...v4.7.0-dev.6) (2026-08-26)
7
+
8
+ ### Bug Fixes
9
+
10
+ - make PDP product JSON-LD Schema.org compliant ([#3465](https://github.com/vtex/faststore/issues/3465)) ([0c09f15](https://github.com/vtex/faststore/commit/0c09f15456d3490602970ce0b4dff6b3c76a4f9c)), closes [#1449246](https://github.com/vtex/faststore/issues/1449246)
11
+
12
+ # [4.7.0-dev.5](https://github.com/vtex/faststore/compare/v4.7.0-dev.4...v4.7.0-dev.5) (2026-08-25)
13
+
14
+ ### Bug Fixes
15
+
16
+ - **core:** restore Order Details layout and first-paint rendering in My Account ([#3464](https://github.com/vtex/faststore/issues/3464)) ([3464523](https://github.com/vtex/faststore/commit/346452304b265d439184bca3f56c2ae8a9341cce)), closes [#3354](https://github.com/vtex/faststore/issues/3354) [#3354](https://github.com/vtex/faststore/issues/3354) [#3417](https://github.com/vtex/faststore/issues/3417) [/github.com/vtex/faststore/pull/3464#discussion_r3848325785](https://github.com//github.com/vtex/faststore/pull/3464/issues/discussion_r3848325785) [#3411](https://github.com/vtex/faststore/issues/3411) [#3354](https://github.com/vtex/faststore/issues/3354)
17
+
6
18
  # [4.7.0-dev.4](https://github.com/vtex/faststore/compare/v4.7.0-dev.3...v4.7.0-dev.4) (2026-08-25)
7
19
 
8
20
  ### Bug Fixes
@@ -32,5 +32,5 @@ breaking change to the rendered storefront.
32
32
 
33
33
  ## Contents
34
34
 
35
- - `components/` — 14 `cms_component__account*.jsonc` section schemas.
36
- - `pages/` — 6 `cms_content_type__myaccount*.jsonc` content-types.
35
+ - `components/` — 15 `cms_component__account*.jsonc` section schemas.
36
+ - `pages/` — 7 `cms_content_type__myaccount*.jsonc` content-types.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "4.7.0-dev.4",
3
+ "version": "4.7.0-dev.6",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -71,11 +71,11 @@
71
71
  "style-loader": "^3.3.1",
72
72
  "swr": "^2.2.5",
73
73
  "use-sync-external-store": "^1.6.0",
74
- "@faststore/diagnostics": "4.7.0-dev.4",
75
- "@faststore/lighthouse": "4.7.0-dev.4",
76
- "@faststore/sdk": "4.7.0-dev.4",
77
- "@faststore/api": "4.7.0-dev.4",
78
- "@faststore/ui": "4.7.0-dev.4"
74
+ "@faststore/api": "4.7.0-dev.6",
75
+ "@faststore/lighthouse": "4.7.0-dev.6",
76
+ "@faststore/sdk": "4.7.0-dev.6",
77
+ "@faststore/ui": "4.7.0-dev.6",
78
+ "@faststore/diagnostics": "4.7.0-dev.6"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@cypress/code-coverage": "^3.12.1",
@@ -118,6 +118,19 @@
118
118
  }
119
119
  }
120
120
 
121
+ // Grid placement targets the CMS *section wrapper*, not the card.
122
+ // The CMS render wraps every section in `<section class="section section-account-order-*">`,
123
+ // which is the real grid item; `grid-column` on a card nested inside it is inert.
124
+ // Attribute selectors are required here because this is a CSS module: a plain
125
+ // class selector would be hashed and never match the global class emitted by
126
+ // `Section.tsx` (and stylelint does not support `:global()`).
127
+ // The rules below have three different fates — do not treat them as one group:
128
+ // 1. `[class~="section-account-order-*"]` — the live contract for the CMS render.
129
+ // 2. `[data-fs-delivery-option-accordion]` — ALSO live: the delivery wrapper is
130
+ // `display: contents`, so the accordion is a direct grid item again.
131
+ // 3. `[data-fs-order-status-card]`, `[data-fs-order-budgets-card]` — dead for the
132
+ // CMS render; they only serve `@deprecated OrderDetails.tsx`, so they can go
133
+ // when that component does.
121
134
  [data-fs-order-details-content] {
122
135
  display: flex;
123
136
  flex-direction: column;
@@ -128,22 +141,50 @@
128
141
  grid-template-columns: repeat(2, 1fr);
129
142
  }
130
143
 
131
- [data-fs-order-status-card] {
144
+ // Cards must fill their section wrapper. The wrapper is the grid item and
145
+ // stretches to the row height, but a block child keeps its natural height,
146
+ // so a short card next to a tall one stopped short and left a gap below it.
147
+ // Before the CMS render the card itself was the grid item and stretched, so
148
+ // cards sharing a row matched heights — this restores that.
149
+ [class~="section-account-order-ordered-by"],
150
+ [class~="section-account-order-payment"],
151
+ [class~="section-account-order-summary"],
152
+ [class~="section-account-order-more-info"],
153
+ [class~="section-account-order-status"],
154
+ [class~="section-account-order-budgets"] {
155
+ display: grid;
156
+ }
157
+
158
+ // (1) Live — full-width section wrappers.
159
+ [class~="section-account-order-details"] {
132
160
  grid-column: span 2;
133
161
  }
134
162
 
135
- [data-fs-buying-policy-alert] {
163
+ [class~="section-account-order-status"] {
136
164
  grid-column: span 2;
137
165
  }
138
166
 
139
- [data-fs-pending-policies-alert] {
167
+ [class~="section-account-order-budgets"] {
140
168
  grid-column: span 2;
141
169
  }
142
170
 
171
+ // (1) Live — the delivery section holds two blocks the design treats as
172
+ // independent (compact recipient card, full-width option list). Dropping the
173
+ // wrapper box makes both direct grid items, so the rules below still apply.
174
+ [class~="section-account-order-delivery"] {
175
+ display: contents;
176
+ }
177
+
178
+ // (2) Live — direct grid item via the `display: contents` above.
143
179
  [data-fs-delivery-option-accordion] {
144
180
  grid-column: span 2;
145
181
  }
146
182
 
183
+ // (3) Dead for the CMS render — kept for `@deprecated OrderDetails.tsx` only.
184
+ [data-fs-order-status-card] {
185
+ grid-column: span 2;
186
+ }
187
+
147
188
  [data-fs-order-budgets-card] {
148
189
  grid-column: span 2;
149
190
  }
@@ -23,6 +23,23 @@ interface Props {
23
23
  isInteractive?: boolean
24
24
  }
25
25
 
26
+ type RenderSectionsBaseProps = Props & {
27
+ /**
28
+ * Opts the whole render pass out of viewport lazy loading, so every section
29
+ * renders on first paint (SSR HTML included) instead of behind a placeholder.
30
+ *
31
+ * Meant for authenticated, above-the-fold pages such as My Account, where
32
+ * lazy loading only delays content. The public storefront must not set it.
33
+ *
34
+ * Wider counterpart of the per-section `data.skipLazyLoadingSection` flag.
35
+ *
36
+ * Only `RenderSectionsBase` honours it: the default `RenderSections` export
37
+ * also renders global sections (CartSidebar, RegionModal, RegionSlider),
38
+ * whose lazy loading is load-bearing, so it must not be opted out wholesale.
39
+ */
40
+ skipLazyLoading?: boolean
41
+ }
42
+
26
43
  const REGION_SLIDER_SECTION_NAME = 'RegionSlider'
27
44
 
28
45
  const SECTIONS_OUT_OF_VIEWPORT = [
@@ -139,7 +156,8 @@ export const RenderSectionsBase = ({
139
156
  sections = [],
140
157
  components,
141
158
  isInteractive,
142
- }: Props) => {
159
+ skipLazyLoading,
160
+ }: RenderSectionsBaseProps) => {
143
161
  return (
144
162
  <>
145
163
  {sections.map(({ name, data = {}, $componentKey }, index) => {
@@ -159,7 +177,7 @@ export const RenderSectionsBase = ({
159
177
 
160
178
  return (
161
179
  <SectionBoundary key={`cms-section-${name}-${index}`} name={name}>
162
- {data.skipLazyLoadingSection ? (
180
+ {skipLazyLoading || data.skipLazyLoadingSection ? (
163
181
  <Component {...sectionProps} />
164
182
  ) : (
165
183
  <LazyLoadingSection
@@ -28,6 +28,7 @@ import { useSession } from 'src/sdk/session'
28
28
  import { execute } from 'src/server'
29
29
  import { getComponentKey } from 'src/utils/cms'
30
30
  import { getChannelForLocale } from 'src/utils/localization/bindingPaths'
31
+ import { toProductJsonLdOffer } from 'src/utils/productJsonLd'
31
32
 
32
33
  import storeConfig from 'discovery.config'
33
34
  import {
@@ -328,11 +329,13 @@ function Page({
328
329
  description={description}
329
330
  brand={product.brand.name}
330
331
  sku={product.sku}
331
- gtin={product.gtin}
332
- mpn={product.mpn}
333
- releaseDate={product.releaseDate}
332
+ // Spread conditionally so an unregistered identifier is omitted rather
333
+ // than published as "": next-seo passes these through untouched.
334
+ {...(product.gtin && { gtin: product.gtin })}
335
+ {...(product.mpn && { mpn: product.mpn })}
336
+ {...(product.releaseDate && { releaseDate: product.releaseDate })}
334
337
  images={product.image.map((img) => img.url)} // Somehow, Google does not understand this valid Schema.org schema, so we need to do conversions
335
- offers={offers}
338
+ {...(offers && { offers })}
336
339
  {...(itemListElements.length !== 0 && {
337
340
  category: itemListElements[0].name,
338
341
  })}
@@ -508,19 +511,10 @@ export const getStaticProps: GetStaticProps<
508
511
 
509
512
  const meta = { title, description, canonical }
510
513
 
511
- let offer = {}
512
-
513
- if (data.product.offers.offers.length > 0) {
514
- const { listPrice, ...offerData } = data.product.offers.offers[0]
515
-
516
- offer = offerData
517
- }
518
-
519
- const offers = {
520
- ...offer,
514
+ const offers = toProductJsonLdOffer(data.product.offers.offers[0], {
521
515
  priceCurrency: data.product.offers.priceCurrency,
522
516
  url: canonical,
523
- }
517
+ })
524
518
 
525
519
  const globalSectionsResult = injectGlobalSections({
526
520
  globalSections,
@@ -66,6 +66,7 @@ function Page({
66
66
  <NextSeo noindex nofollow />
67
67
 
68
68
  <RenderSectionsBase
69
+ skipLazyLoading
69
70
  sections={pageSections}
70
71
  components={ACCOUNT_COMPONENTS}
71
72
  />
@@ -57,7 +57,11 @@ function Page({
57
57
 
58
58
  <Layout accountName={accountName}>
59
59
  {sections && sections.length > 0 && (
60
- <RenderSectionsBase sections={sections} components={COMPONENTS} />
60
+ <RenderSectionsBase
61
+ skipLazyLoading
62
+ sections={sections}
63
+ components={COMPONENTS}
64
+ />
61
65
  )}
62
66
  </Layout>
63
67
  </RenderSections>
@@ -94,6 +94,7 @@ export default function ListCardsPage({
94
94
  navigationLabels={navigationLabels}
95
95
  >
96
96
  <RenderSectionsBase
97
+ skipLazyLoading
97
98
  sections={pageSections}
98
99
  components={ACCOUNT_COMPONENTS}
99
100
  />
@@ -76,6 +76,7 @@ export default function OrderDetailsPage({
76
76
  <OrderDetailsPageShell>
77
77
  <main data-fs-order-details-content>
78
78
  <RenderSectionsBase
79
+ skipLazyLoading
79
80
  sections={pageSections}
80
81
  components={ACCOUNT_COMPONENTS}
81
82
  />
@@ -408,7 +409,10 @@ const getServerSidePropsBase: GetServerSideProps<
408
409
  navigationLabels: navigationData as AccountNavigationLabels,
409
410
  accountPageData: {
410
411
  order: orderDetails.data.userOrder,
411
- orderStatusLabels,
412
+ // Yields undefined when no section carries order-status labels — the
413
+ // case for a store with no CMS content, since the default sections are
414
+ // created with empty data. getServerSideProps cannot serialize it.
415
+ ...(orderStatusLabels ? { orderStatusLabels } : {}),
412
416
  },
413
417
  pageSections,
414
418
  isRepresentative,
@@ -75,6 +75,7 @@ export default function ListOrdersPage({
75
75
  >
76
76
  <BeforeSection />
77
77
  <RenderSectionsBase
78
+ skipLazyLoading
78
79
  sections={pageSections}
79
80
  components={ACCOUNT_COMPONENTS}
80
81
  />
@@ -74,6 +74,7 @@ export default function Profile({
74
74
  >
75
75
  <BeforeSection />
76
76
  <RenderSectionsBase
77
+ skipLazyLoading
77
78
  sections={pageSections}
78
79
  components={ACCOUNT_COMPONENTS}
79
80
  />
@@ -74,6 +74,7 @@ export default function Page({
74
74
  >
75
75
  <BeforeSection />
76
76
  <RenderSectionsBase
77
+ skipLazyLoading
77
78
  sections={pageSections}
78
79
  components={ACCOUNT_COMPONENTS}
79
80
  />
@@ -73,6 +73,7 @@ export default function Page({
73
73
  >
74
74
  <BeforeSection />
75
75
  <RenderSectionsBase
76
+ skipLazyLoading
76
77
  sections={pageSections}
77
78
  components={ACCOUNT_COMPONENTS}
78
79
  />
@@ -20,16 +20,21 @@ const DEFAULT_SECTION_KEYS: Record<NativeMyAccountContentType, string[]> = {
20
20
  myAccountProfile: ['AccountNavigation', 'AccountProfile'],
21
21
  myAccountOrders: ['AccountNavigation', 'AccountOrdersList'],
22
22
  myAccountCards: ['AccountNavigation', 'AccountListCards'],
23
+ // Mirrors the render order of the `@deprecated OrderDetails.tsx` component,
24
+ // which is the design reference available in the repo. The delivery option
25
+ // accordions render inside AccountOrderDelivery, so they sit right after the
26
+ // compact delivery card instead of after the summary. Known deviation: fixing
27
+ // it would mean splitting AccountOrderDelivery into two CMS sections.
23
28
  myAccountOrderDetails: [
24
29
  'AccountNavigation',
25
30
  'AccountOrderDetails',
31
+ 'AccountOrderOrderedBy',
32
+ 'AccountOrderDelivery',
26
33
  'AccountOrderStatus',
27
34
  'AccountOrderPayment',
28
- 'AccountOrderDelivery',
29
35
  'AccountOrderSummary',
30
- 'AccountOrderOrderedBy',
31
- 'AccountOrderBudgets',
32
36
  'AccountOrderMoreInfo',
37
+ 'AccountOrderBudgets',
33
38
  ],
34
39
  myAccountUserDetails: ['AccountNavigation', 'AccountUserDetails'],
35
40
  myAccountSecurity: ['AccountNavigation', 'AccountSecurity'],
@@ -0,0 +1,71 @@
1
+ import type { ServerProductQueryQuery } from '@generated/graphql'
2
+
3
+ type ServerOffers = ServerProductQueryQuery['product']['offers']
4
+ type ServerOffer = ServerOffers['offers'][number]
5
+
6
+ /**
7
+ * Schema.org properties the PDP JSON-LD is allowed to publish on an Offer.
8
+ *
9
+ * This is an allowlist on purpose. The PDP's server query and
10
+ * `ProductDetailsFragment_product` both select `offers.offers`, and GraphQL
11
+ * merges the two selection sets — so the offer object reaching this module
12
+ * carries UI-only fields (`priceWithTaxes`, `listPrice`, `listPriceWithTaxes`,
13
+ * `quantity`, `priceToken`) that have no meaning to Schema.org consumers.
14
+ * Excluding them by name would mean tracking a fragment this module does not
15
+ * own, which is exactly how they leaked into public markup in the first place.
16
+ */
17
+ const SCHEMA_ORG_OFFER_FIELDS = [
18
+ 'availability',
19
+ 'itemCondition',
20
+ 'price',
21
+ 'priceValidUntil',
22
+ ] as const satisfies ReadonlyArray<keyof ServerOffer>
23
+
24
+ // Emptiness, not falsiness: `price` is numeric and `0` is a real price, so a
25
+ // falsy check would strip it and leave an Offer with no price at all.
26
+ const isEmpty = (value: unknown) =>
27
+ value === null || value === undefined || value === ''
28
+
29
+ type ObjectLevelFields = {
30
+ priceCurrency: ServerOffers['priceCurrency']
31
+ url: string
32
+ }
33
+
34
+ /**
35
+ * Builds the `offers` object for the PDP's `ProductJsonLd`, or `undefined` when
36
+ * the product has no offer to describe.
37
+ *
38
+ * `undefined` matters: an `Offer` carrying only a currency and a URL is invalid
39
+ * Schema.org, so omitting the property entirely beats emitting a priceless one.
40
+ * Every key is dropped when its value is empty — `priceValidUntil` and
41
+ * `availability` are both nullable upstream.
42
+ */
43
+ export const toProductJsonLdOffer = (
44
+ serverOffer: ServerOffer | undefined,
45
+ { priceCurrency, url }: ObjectLevelFields
46
+ ) => {
47
+ if (!serverOffer) {
48
+ return undefined
49
+ }
50
+
51
+ const offer: Record<string, unknown> = { priceCurrency, url }
52
+
53
+ for (const field of SCHEMA_ORG_OFFER_FIELDS) {
54
+ offer[field] = serverOffer[field]
55
+ }
56
+
57
+ for (const [key, value] of Object.entries(offer)) {
58
+ if (isEmpty(value)) {
59
+ delete offer[key]
60
+ }
61
+ }
62
+
63
+ // `price` resolves to null outside the search and order-form roots. Dropping
64
+ // the key alone would leave a priceless Offer — the same invalid markup the
65
+ // no-offer case above avoids, so it gets the same treatment.
66
+ if (isEmpty(offer.price)) {
67
+ return undefined
68
+ }
69
+
70
+ return offer
71
+ }
@@ -0,0 +1,135 @@
1
+ import { UIProvider } from '@faststore/ui'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { renderToString } from 'react-dom/server'
4
+ import { beforeAll, describe, expect, it } from 'vitest'
5
+
6
+ import { RenderSectionsBase } from 'src/components/cms/RenderSections'
7
+ import { getDefaultMyAccountSections } from 'src/server/cms/myAccountDefaultSections'
8
+
9
+ /**
10
+ * First-paint behavior of the My Account pages.
11
+ *
12
+ * Sections rendered through `RenderSectionsBase` go through `ViewportObserver`,
13
+ * which emits an 823px-tall placeholder instead of the section content until the
14
+ * element intersects the viewport. That is correct for the public storefront and
15
+ * wrong for My Account, which is authenticated, SSR and above the fold.
16
+ *
17
+ * `skipLazyLoading` opts a whole render pass out of it.
18
+ */
19
+
20
+ /** Never-firing observer — reproduces the pre-intersection (first paint) state. */
21
+ class NeverIntersectingObserver implements IntersectionObserver {
22
+ readonly root = null
23
+ readonly rootMargin = ''
24
+ readonly thresholds = [] as unknown as ReadonlyArray<number>
25
+ observe() {}
26
+ unobserve() {}
27
+ disconnect() {}
28
+ takeRecords() {
29
+ return []
30
+ }
31
+ }
32
+
33
+ beforeAll(() => {
34
+ window.IntersectionObserver =
35
+ NeverIntersectingObserver as unknown as typeof IntersectionObserver
36
+ })
37
+
38
+ const orderDetailsSections = getDefaultMyAccountSections(
39
+ 'myAccountOrderDetails'
40
+ ).filter((section) => section.$componentKey !== 'AccountNavigation')
41
+
42
+ const markerComponents = Object.fromEntries(
43
+ orderDetailsSections.map((section) => [
44
+ section.$componentKey,
45
+ () => <div>{section.$componentKey}</div>,
46
+ ])
47
+ )
48
+
49
+ describe('skipLazyLoading', () => {
50
+ it('renders section content immediately instead of a placeholder', () => {
51
+ const { container } = render(
52
+ <UIProvider>
53
+ <RenderSectionsBase
54
+ skipLazyLoading
55
+ sections={[{ name: 'AccountOrderStatus', data: {} }]}
56
+ components={{ AccountOrderStatus: () => <div>status</div> }}
57
+ />
58
+ </UIProvider>
59
+ )
60
+
61
+ expect(screen.getByText('status')).toBeTruthy()
62
+ expect(container.querySelector('[data-store-section-name]')).toBeNull()
63
+ })
64
+
65
+ it('renders every default order-details section on first paint', () => {
66
+ const { container } = render(
67
+ <UIProvider>
68
+ <RenderSectionsBase
69
+ skipLazyLoading
70
+ sections={orderDetailsSections}
71
+ components={markerComponents}
72
+ />
73
+ </UIProvider>
74
+ )
75
+
76
+ for (const section of orderDetailsSections) {
77
+ expect(screen.getByText(section.$componentKey)).toBeTruthy()
78
+ }
79
+
80
+ expect(
81
+ container.querySelectorAll('[data-store-section-name]')
82
+ ).toHaveLength(0)
83
+ })
84
+
85
+ it('covers store-authored sections, not only the framework defaults', () => {
86
+ render(
87
+ <UIProvider>
88
+ <RenderSectionsBase
89
+ skipLazyLoading
90
+ sections={[{ name: 'StoreCustomAccountSection', data: {} }]}
91
+ components={{
92
+ StoreCustomAccountSection: () => <div>custom</div>,
93
+ }}
94
+ />
95
+ </UIProvider>
96
+ )
97
+
98
+ expect(screen.getByText('custom')).toBeTruthy()
99
+ })
100
+
101
+ it('puts the section content in the SSR HTML, not the placeholder', () => {
102
+ const html = renderToString(
103
+ <UIProvider>
104
+ <RenderSectionsBase
105
+ skipLazyLoading
106
+ sections={orderDetailsSections}
107
+ components={markerComponents}
108
+ />
109
+ </UIProvider>
110
+ )
111
+
112
+ expect(html).toContain('AccountOrderStatus')
113
+ expect(html).not.toContain('data-store-section-name')
114
+ expect(html).not.toContain('823')
115
+ })
116
+
117
+ it('preserves lazy loading when the flag is absent', () => {
118
+ const { container } = render(
119
+ <UIProvider>
120
+ <RenderSectionsBase
121
+ sections={[{ name: 'AccountOrderStatus', data: {} }]}
122
+ components={{ AccountOrderStatus: () => <div>status</div> }}
123
+ />
124
+ </UIProvider>
125
+ )
126
+
127
+ expect(screen.queryByText('status')).toBeNull()
128
+
129
+ const placeholder = container.querySelector(
130
+ '[data-store-section-name="AccountOrderStatus"]'
131
+ ) as HTMLElement | null
132
+
133
+ expect(placeholder?.style.height).toBe('823px')
134
+ })
135
+ })
@@ -20,6 +20,50 @@ describe('getDefaultMyAccountSections', () => {
20
20
  it('returns an empty list for store (unknown) content-types', () => {
21
21
  expect(getDefaultMyAccountSections('myAccountWishlist')).toEqual([])
22
22
  })
23
+
24
+ /**
25
+ * The order-details defaults must mirror the render order of the
26
+ * `@deprecated OrderDetails.tsx` component, which is the design reference
27
+ * available in the repo. The delivery option accordions travel with
28
+ * AccountOrderDelivery, which is the single known deviation.
29
+ */
30
+ it('orders the myAccountOrderDetails defaults like the deprecated component', () => {
31
+ expect(
32
+ getDefaultMyAccountSections('myAccountOrderDetails').map(
33
+ (section) => section.$componentKey
34
+ )
35
+ ).toEqual([
36
+ 'AccountNavigation',
37
+ 'AccountOrderDetails',
38
+ 'AccountOrderOrderedBy',
39
+ 'AccountOrderDelivery',
40
+ 'AccountOrderStatus',
41
+ 'AccountOrderPayment',
42
+ 'AccountOrderSummary',
43
+ 'AccountOrderMoreInfo',
44
+ 'AccountOrderBudgets',
45
+ ])
46
+ })
47
+
48
+ it('keeps the myAccountOrderDetails default section set unchanged', () => {
49
+ const keys = getDefaultMyAccountSections('myAccountOrderDetails').map(
50
+ (section) => section.$componentKey
51
+ )
52
+
53
+ expect(new Set(keys)).toEqual(
54
+ new Set([
55
+ 'AccountNavigation',
56
+ 'AccountOrderDetails',
57
+ 'AccountOrderStatus',
58
+ 'AccountOrderPayment',
59
+ 'AccountOrderDelivery',
60
+ 'AccountOrderSummary',
61
+ 'AccountOrderOrderedBy',
62
+ 'AccountOrderBudgets',
63
+ 'AccountOrderMoreInfo',
64
+ ])
65
+ )
66
+ })
23
67
  })
24
68
 
25
69
  describe('withDefaultMyAccountSections', () => {
@@ -0,0 +1,126 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { toProductJsonLdOffer } from '../../src/utils/productJsonLd'
3
+
4
+ const OBJECT_LEVEL = { priceCurrency: 'USD', url: 'https://store.example/p' }
5
+
6
+ // Mirrors what actually reaches getStaticProps: the PDP query's Schema.org
7
+ // fields merged with the UI-only fields ProductDetailsFragment_product selects
8
+ // on the same `offers.offers` field.
9
+ const makeServerOffer = (overrides: Record<string, unknown> = {}) =>
10
+ ({
11
+ availability: 'https://schema.org/InStock',
12
+ itemCondition: 'https://schema.org/NewCondition',
13
+ price: 12.5,
14
+ priceValidUntil: '2027-08-24T19:48:25Z',
15
+ // UI-only fields that must never reach the markup
16
+ listPrice: 20,
17
+ listPriceWithTaxes: 22,
18
+ priceWithTaxes: 13.75,
19
+ quantity: 10000,
20
+ priceToken: 'a-short-lived-pricing-token',
21
+ seller: { identifier: '1' },
22
+ ...overrides,
23
+ }) as any
24
+
25
+ describe('toProductJsonLdOffer', () => {
26
+ it('emits exactly the allowed Schema.org keys', () => {
27
+ const offer = toProductJsonLdOffer(makeServerOffer(), OBJECT_LEVEL)
28
+
29
+ expect(Object.keys(offer!).sort()).toEqual([
30
+ 'availability',
31
+ 'itemCondition',
32
+ 'price',
33
+ 'priceCurrency',
34
+ 'priceValidUntil',
35
+ 'url',
36
+ ])
37
+ })
38
+
39
+ it.each([
40
+ 'listPrice',
41
+ 'listPriceWithTaxes',
42
+ 'priceWithTaxes',
43
+ 'quantity',
44
+ 'priceToken',
45
+ 'seller',
46
+ ])('does not leak %s into the markup', (field) => {
47
+ const offer = toProductJsonLdOffer(makeServerOffer(), OBJECT_LEVEL)
48
+
49
+ expect(offer).not.toHaveProperty(field)
50
+ })
51
+
52
+ // The regression guarantee: the mapper picks by allowlist, so a field added
53
+ // to any fragment selecting offers.offers cannot reach public markup.
54
+ it('ignores a field a future fragment might add', () => {
55
+ const offer = toProductJsonLdOffer(
56
+ makeServerOffer({ someFutureInternalField: 'leaked' }),
57
+ OBJECT_LEVEL
58
+ )
59
+
60
+ expect(offer).not.toHaveProperty('someFutureInternalField')
61
+ })
62
+
63
+ it('carries the allowed values through unchanged', () => {
64
+ const offer = toProductJsonLdOffer(makeServerOffer(), OBJECT_LEVEL)
65
+
66
+ expect(offer).toEqual({
67
+ availability: 'https://schema.org/InStock',
68
+ itemCondition: 'https://schema.org/NewCondition',
69
+ price: 12.5,
70
+ priceValidUntil: '2027-08-24T19:48:25Z',
71
+ priceCurrency: 'USD',
72
+ url: 'https://store.example/p',
73
+ })
74
+ })
75
+
76
+ it.each([
77
+ ['priceValidUntil', ''],
78
+ ['priceValidUntil', null],
79
+ ['availability', null],
80
+ ])('drops %s when it resolves to %p', (field, value) => {
81
+ const offer = toProductJsonLdOffer(
82
+ makeServerOffer({ [field]: value }),
83
+ OBJECT_LEVEL
84
+ )
85
+
86
+ expect(offer).not.toHaveProperty(field)
87
+ expect(offer).toHaveProperty('price')
88
+ })
89
+
90
+ // Emptiness, not falsiness — a free item still has a price.
91
+ it('keeps a price of 0', () => {
92
+ const offer = toProductJsonLdOffer(
93
+ makeServerOffer({ price: 0 }),
94
+ OBJECT_LEVEL
95
+ )
96
+
97
+ expect(offer).toHaveProperty('price', 0)
98
+ })
99
+
100
+ it('drops an empty priceCurrency', () => {
101
+ const offer = toProductJsonLdOffer(makeServerOffer(), {
102
+ ...OBJECT_LEVEL,
103
+ priceCurrency: '',
104
+ })
105
+
106
+ expect(offer).not.toHaveProperty('priceCurrency')
107
+ })
108
+
109
+ // An Offer with only a currency and a URL is invalid Schema.org, so the
110
+ // property is omitted entirely rather than emitted priceless.
111
+ it('returns undefined when the product has no offer', () => {
112
+ expect(toProductJsonLdOffer(undefined, OBJECT_LEVEL)).toBeUndefined()
113
+ })
114
+
115
+ // StoreOffer.price resolves to null outside the search and order-form roots.
116
+ // Dropping the key alone would leave a priceless Offer, which is exactly the
117
+ // invalid markup the no-offer case avoids.
118
+ it.each([
119
+ ['null', null],
120
+ ['an empty string', ''],
121
+ ])('returns undefined when the price is %s', (_label, price) => {
122
+ expect(
123
+ toProductJsonLdOffer(makeServerOffer({ price }), OBJECT_LEVEL)
124
+ ).toBeUndefined()
125
+ })
126
+ })
@@ -0,0 +1,74 @@
1
+ import { ProductJsonLd } from 'next-seo'
2
+ import { renderToStaticMarkup } from 'react-dom/server'
3
+ import { describe, expect, it } from 'vitest'
4
+ import { toProductJsonLdOffer } from '../../src/utils/productJsonLd'
5
+
6
+ /**
7
+ * Pins the next-seo behaviour the PDP's JSON-LD depends on.
8
+ *
9
+ * `p.tsx` omits `mpn`, `gtin` and `releaseDate` by conditional spread rather
10
+ * than passing empty strings, and drops `seller` from the offer allowlist. Both
11
+ * choices only work because of how next-seo builds the script — it forwards
12
+ * unknown props untouched (so `""` would be published) and turns any truthy
13
+ * `seller` into an `Organization`, even when it has no name. A next-seo upgrade
14
+ * that changed either would silently reintroduce the defects from ticket
15
+ * #1449246, so the behaviour is asserted here instead of assumed.
16
+ */
17
+
18
+ // `useAppDir` renders the script directly; without it next-seo emits through
19
+ // next/head, which produces no markup outside a Next document.
20
+ const renderJsonLd = (props: Record<string, unknown>) => {
21
+ const html = renderToStaticMarkup(
22
+ <ProductJsonLd useAppDir productName="Product" images={[]} {...props} />
23
+ )
24
+
25
+ return JSON.parse(html.replace(/^[\s\S]*?>([\s\S]*)<\/script>[\s\S]*$/, '$1'))
26
+ }
27
+
28
+ describe('next-seo ProductJsonLd contract', () => {
29
+ it('publishes an empty string when a prop is passed empty', () => {
30
+ expect(renderJsonLd({ mpn: '' })).toHaveProperty('mpn', '')
31
+ })
32
+
33
+ it('omits the key when the prop is absent — what the conditional spread relies on', () => {
34
+ expect(renderJsonLd({})).not.toHaveProperty('mpn')
35
+ })
36
+
37
+ it('emits a nameless Organization for a seller that has no name', () => {
38
+ const jsonLd = renderJsonLd({
39
+ offers: { price: 1, seller: { identifier: '1' } },
40
+ })
41
+
42
+ expect(jsonLd.offers.seller).toEqual({ '@type': 'Organization' })
43
+ })
44
+
45
+ it('leaves the mapper output intact, adding only @type', () => {
46
+ const offers = toProductJsonLdOffer(
47
+ {
48
+ availability: 'https://schema.org/InStock',
49
+ itemCondition: 'https://schema.org/NewCondition',
50
+ price: 0.2,
51
+ priceValidUntil: '2027-08-24T19:48:25Z',
52
+ priceWithTaxes: 0.2,
53
+ listPrice: 0.2,
54
+ listPriceWithTaxes: 0.2,
55
+ quantity: 10000,
56
+ priceToken: 'a-short-lived-pricing-token',
57
+ seller: { identifier: '1' },
58
+ } as any,
59
+ { priceCurrency: 'USD', url: 'https://store.example/p' }
60
+ )
61
+
62
+ const jsonLd = renderJsonLd({ offers })
63
+
64
+ expect(jsonLd.offers).toEqual({
65
+ '@type': 'Offer',
66
+ availability: 'https://schema.org/InStock',
67
+ itemCondition: 'https://schema.org/NewCondition',
68
+ price: 0.2,
69
+ priceCurrency: 'USD',
70
+ priceValidUntil: '2027-08-24T19:48:25Z',
71
+ url: 'https://store.example/p',
72
+ })
73
+ })
74
+ })