@faststore/core 4.9.0 → 4.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
 
2
- > @faststore/core@4.9.0-dev.8 generate /home/runner/work/faststore/faststore/packages/core
2
+ > @faststore/core@4.9.0 generate /home/runner/work/faststore/faststore/packages/core
3
3
  > pnpm run gen-types && pnpm run cache-graphql
4
4
 
5
5
 
6
- > @faststore/core@4.9.0-dev.8 gen-types /home/runner/work/faststore/faststore/packages/core
6
+ > @faststore/core@4.9.0 gen-types /home/runner/work/faststore/faststore/packages/core
7
7
  > node ../cli/bin/run generate-types .
8
8
 
9
9
  [STARTED] Parse Configuration
@@ -19,7 +19,7 @@
19
19
  [COMPLETED] Generate to @generated/
20
20
  [COMPLETED] Generate outputs
21
21
 
22
- > @faststore/core@4.9.0-dev.8 cache-graphql /home/runner/work/faststore/faststore/packages/core
22
+ > @faststore/core@4.9.0 cache-graphql /home/runner/work/faststore/faststore/packages/core
23
23
  > node ../cli/bin/run cache-graphql --config=./discovery.config.default.js --queries=./@generated/persisted-documents.json
24
24
 
25
25
  [Info] - Config file location: /home/runner/work/faststore/faststore/packages/core/discovery.config.default.js
@@ -1,5 +1,5 @@
1
1
 
2
- > @faststore/core@4.9.0-dev.8 test /home/runner/work/faststore/faststore/packages/core
2
+ > @faststore/core@4.9.0 test /home/runner/work/faststore/faststore/packages/core
3
3
  > vitest run
4
4
 
5
5
 
@@ -7,27 +7,25 @@
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) 807ms
11
- Browserslist: browsers data (caniuse-lite) is 7 months old. Please run:
10
+ ✓  browser  test/sdk/ui/useScrollRestoration.browser.test.ts (43 tests) 991ms
11
+ Browserslist: browsers data (caniuse-lite) is 8 months old. Please run:
12
12
  npx update-browserslist-db@latest
13
13
  Why you should do it regularly: https://github.com/browserslist/update-db#readme
14
- ✓  browser  test/components/account/cards/MyAccountListCards.browser.test.tsx (16 tests) 2413ms
15
- ✓ shows the tab bar and renders Personal cards by default  745ms
16
- ✓ switches to the Shared tab by clicking it, and shows the tooltip info icon  304ms
17
- ✓ paginates when there are more than 25 cards  405ms
18
- ✓  browser  test/components/account/ContractSwitcher.browser.test.tsx (22 tests) 4082ms
19
- ✓ shows an error state when the list fails to load  1068ms
20
- ✓ requires selecting a contract before Confirm is enabled, then switches (REQ-03)  456ms
21
- ✓ keeps Confirm disabled when switching is unavailable  383ms
22
- ✓ keeps Confirm disabled when the selected contract is filtered out  325ms
23
- ✓  browser  test/components/account/orders/BuyingPolicyAlert.browser.test.tsx (13 tests) 2845ms
24
- ✓ opens the reject confirmation modal with the default English copy  1379ms
25
- ✓ opens the reject confirmation modal with CMS-provided labels  428ms
26
- ✓  browser  test/sdk/localization/useBindingSelector.browser.test.tsx (9 tests) 78ms
27
- ✓  browser  test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx (13 tests) 246ms
28
- ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (10 tests) 1356ms
29
- ✓ renders the menu view with contract details and manage footer  715ms
30
- ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (5 tests) 11ms
14
+ ✓  browser  test/components/account/ContractSwitcher.browser.test.tsx (22 tests) 2974ms
15
+ ✓ shows an error state when the list fails to load  681ms
16
+ ✓ requires selecting a contract before Confirm is enabled, then switches (REQ-03)  370ms
17
+ ✓ keeps Confirm disabled until a different contract is selected  346ms
18
+ ✓  browser  test/components/account/cards/MyAccountListCards.browser.test.tsx (16 tests) 2099ms
19
+ ✓ shows the tab bar and renders Personal cards by default  966ms
20
+ ✓  browser  test/components/account/orders/BuyingPolicyAlert.browser.test.tsx (13 tests) 1905ms
21
+ ✓ opens the reject confirmation modal with the default English copy  705ms
22
+ ✓ opens the reject confirmation modal with CMS-provided labels  341ms
23
+ ✓  browser  test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx (13 tests) 221ms
24
+ ✓  browser  test/sdk/localization/useBindingSelector.browser.test.tsx (9 tests) 139ms
25
+ ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (10 tests) 1489ms
26
+ ✓ renders the menu view with contract details and manage footer  815ms
27
+ ✓  browser  test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx (8 tests) 90ms
28
+ ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (5 tests) 13ms
31
29
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
32
30
  Error in optimistic validation: TypeError: Failed to parse URL from /api/graphql?operationName=ValidateCartMutation&operationHash=3c71b0d3fc2bade14df68abfa271858beb361b12
33
31
   at node:internal/deps/undici/undici:16416:13
@@ -54,16 +52,13 @@ Browserslist: browsers data (caniuse-lite) is 7 months old. Please run:
54
52
  }
55
53
  }
56
54
 
57
- ✓  browser  test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx (8 tests) 132ms
55
+ ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 50ms
58
56
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx > RecommendationShelf > renders nothing when there is an error
59
57
  Error fetching recommendations undefined boom for campaign vrn:recommendations:acc:rec-top-items-v2:campaign-1
60
58
 
61
- ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 144ms
62
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 99ms
63
- ✓  browser  test/components/CartSidebar/CartSidebar.browser.test.tsx (5 tests) 64ms
64
- ✓  browser  test/components/sections/Account/AccountOrderDetails.browser.test.tsx (4 tests) 129ms
65
- ✓  browser  test/components/navigation/Navbar.browser.test.tsx (3 tests) 77ms
66
- ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (6 tests) 8ms
59
+ ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 139ms
60
+ ✓  browser  test/components/CartSidebar/CartSidebar.browser.test.tsx (5 tests) 51ms
61
+ ✓  browser  test/components/sections/Account/AccountOrderDetails.browser.test.tsx (4 tests) 92ms
67
62
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > posts the password and redirects to the requested return path
68
63
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
69
64
 
@@ -101,6 +96,7 @@ act(() => {
101
96
  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
102
97
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
103
98
 
99
+ ✓  browser  test/components/navigation/Navbar.browser.test.tsx (3 tests) 44ms
104
100
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > defaults returnTo to root and shows API validation errors
105
101
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
106
102
 
@@ -293,8 +289,11 @@ act(() => {
293
289
  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
294
290
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
295
291
 
296
- ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 799ms
297
- ✓ renders the password protection form  358ms
292
+ ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 740ms
293
+ ✓ renders the password protection form  431ms
294
+ ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (6 tests) 7ms
295
+ ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (5 tests) 37ms
296
+ ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (8 tests) 319ms
298
297
  stderr | test/components/cms/RenderSections.browser.test.tsx
299
298
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
300
299
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -303,10 +302,9 @@ This ensures that you're testing the behavior the user would see in the browser.
303
302
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
304
303
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
305
304
 
306
- ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (8 tests) 354ms
307
- ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (5 tests) 59ms
308
- ✓  browser  test/components/cms/RenderSections.browser.test.tsx (5 tests) 62ms
309
- ✓  browser  test/components/navigation/NavbarSlider.browser.test.tsx (2 tests) 42ms
305
+ ✓  browser  test/components/cms/RenderSections.browser.test.tsx (5 tests) 63ms
306
+ ✓  browser  test/components/navigation/NavbarSlider.browser.test.tsx (2 tests) 31ms
307
+ ✓  browser  test/components/account/Menu.browser.test.tsx (6 tests) 234ms
310
308
  stderr | test/sdk/localization/store-url.browser.test.ts
311
309
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
312
310
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -315,15 +313,14 @@ This ensures that you're testing the behavior the user would see in the browser.
315
313
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
316
314
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
317
315
 
318
- ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 5ms
319
- ✓  browser  test/components/account/Menu.browser.test.tsx (6 tests) 359ms
320
- ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 37ms
321
- ✓  browser  test/components/templates/SearchPage/SearchWrapper.browser.test.tsx (3 tests) 36ms
322
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx (2 tests) 129ms
323
- ✓  browser  test/components/navigation/NavbarAccountArea.browser.test.tsx (4 tests) 55ms
324
- ✓  browser  test/components/sections/Account/AccountListQuotes.browser.test.tsx (4 tests) 120ms
325
- ✓  browser  test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx (3 tests) 69ms
326
- ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 22ms
316
+ ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 4ms
317
+ ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 30ms
318
+ ✓  browser  test/components/templates/SearchPage/SearchWrapper.browser.test.tsx (3 tests) 34ms
319
+ ✓  browser  test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx (2 tests) 125ms
320
+ ✓  browser  test/components/sections/Account/AccountListQuotes.browser.test.tsx (4 tests) 101ms
321
+ ✓  browser  test/components/navigation/NavbarAccountArea.browser.test.tsx (4 tests) 57ms
322
+ ✓  browser  test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx (3 tests) 35ms
323
+ ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 15ms
327
324
  stderr | test/components/account/doLogout.browser.test.ts
328
325
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
329
326
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -332,8 +329,9 @@ This ensures that you're testing the behavior the user would see in the browser.
332
329
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
333
330
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
334
331
 
335
- ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 35ms
336
- ✓  browser  test/components/ui/LocalizationButton.browser.test.tsx (3 tests) 65ms
332
+ ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 20ms
333
+ ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (5 tests) 272ms
334
+ ✓  browser  test/components/ui/LocalizationButton.browser.test.tsx (3 tests) 37ms
337
335
  stderr | test/sdk/account/clearPersistedSessionState.browser.test.ts
338
336
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
339
337
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -342,22 +340,21 @@ This ensures that you're testing the behavior the user would see in the browser.
342
340
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
343
341
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
344
342
 
345
- ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (5 tests) 358ms
346
- ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (4 tests) 20ms
347
- ✓  browser  test/components/sections/Account/AccountOrderDelivery.browser.test.tsx (2 tests) 93ms
348
- ✓  browser  test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx (2 tests) 96ms
349
- ✓  browser  test/components/sections/Account/AccountOrderStatus.browser.test.tsx (3 tests) 103ms
350
- ✓  browser  test/components/sections/Account/AccountOrdersList.browser.test.tsx (2 tests) 26ms
351
- ✓  browser  test/components/sections/Account/AccountListCards.browser.test.tsx (3 tests) 111ms
352
- ✓  browser  test/components/account/orders/SummaryCard.browser.test.tsx (2 tests) 94ms
353
- ✓  browser  test/components/account/orders/DeliveryOptionAccordionDeliveryInfo.browser.test.tsx (1 test) 40ms
354
- ✓  browser  test/components/account/orders/StatusCard.browser.test.tsx (2 tests) 112ms
355
- ✓  browser  test/components/account/OrganizationSignInButton.browser.test.tsx (2 tests) 237ms
356
- ✓  browser  test/utils/isLocalHost.browser.test.ts (3 tests) 3ms
343
+ ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (4 tests) 14ms
344
+ ✓  browser  test/components/sections/Account/AccountOrderDelivery.browser.test.tsx (2 tests) 61ms
345
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx (2 tests) 74ms
346
+ ✓  browser  test/components/sections/Account/AccountOrderStatus.browser.test.tsx (3 tests) 90ms
347
+ ✓  browser  test/components/sections/Account/AccountOrdersList.browser.test.tsx (2 tests) 37ms
348
+ ✓  browser  test/components/sections/Account/AccountListCards.browser.test.tsx (3 tests) 71ms
349
+ ✓  browser  test/components/account/orders/SummaryCard.browser.test.tsx (2 tests) 65ms
350
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordionDeliveryInfo.browser.test.tsx (1 test) 26ms
351
+ ✓  browser  test/components/account/OrganizationSignInButton.browser.test.tsx (2 tests) 182ms
352
+ ✓  browser  test/components/account/orders/StatusCard.browser.test.tsx (2 tests) 86ms
353
+ ✓  browser  test/utils/isLocalHost.browser.test.ts (3 tests) 4ms
357
354
  ✓  browser  test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts (1 test) 3ms
358
- ✓  browser  test/components/account/orders/DeliveryCard.browser.test.tsx (1 test) 49ms
359
- ✓  browser  test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx (1 test) 30ms
360
- ✓  browser  test/components/skeletons/ProductGridSkeleton.browser.test.tsx (2 tests) 30ms
355
+ ✓  browser  test/components/account/orders/DeliveryCard.browser.test.tsx (1 test) 39ms
356
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx (1 test) 37ms
357
+ ✓  browser  test/components/skeletons/ProductGridSkeleton.browser.test.tsx (2 tests) 22ms
361
358
  stderr | test/sdk/account/refreshToken.browser.test.ts
362
359
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
363
360
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -366,18 +363,18 @@ This ensures that you're testing the behavior the user would see in the browser.
366
363
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
367
364
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
368
365
 
369
- ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 5ms
366
+ ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 4ms
370
367
  ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 19ms
371
- ✓  node  test/pages/api/switch-contract.test.ts (25 tests) 68ms
372
- ✓  node  test/server/password-protection-service.test.ts (22 tests) 33ms
373
- ✓  node  test/utils/match-url.test.ts (10 tests) 7ms
374
- ✓  browser  test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx (1 test) 236ms
375
- ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 14ms
376
- ✓  node  test/pages/api/unlock.test.ts (15 tests) 160ms
377
- ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 10ms
368
+ ✓  node  test/pages/api/switch-contract.test.ts (25 tests) 67ms
369
+ ✓  node  test/server/password-protection-service.test.ts (22 tests) 26ms
370
+ ✓  node  test/utils/match-url.test.ts (10 tests) 8ms
371
+ ✓  browser  test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx (1 test) 167ms
372
+ ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 11ms
373
+ ✓  node  test/pages/api/unlock.test.ts (15 tests) 136ms
378
374
  stderr | test/components/search/SearchInputComponent.test.tsx
379
375
  Warning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?
380
376
 
377
+ ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 8ms
381
378
  stderr | test/components/search/SearchInputComponent.test.tsx > SearchInput (OES integration) > renders without crashing
382
379
  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.
383
380
  at div
@@ -438,22 +435,22 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
438
435
  at Suspense
439
436
  at Wrapper (/home/runner/work/faststore/faststore/packages/core/test/components/search/SearchInputComponent.test.tsx:106:20)
440
437
 
441
- ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 89ms
442
- ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (15 tests) 403ms
438
+ ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 63ms
439
+ ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (15 tests) 350ms
443
440
  stderr | test/pages/pvt/account/cards/index.test.ts > Cards page getServerSideProps > redirects to /pvt/account/403 when the query fails with 401/403
444
441
  { extensions: { status: 401 } }
445
442
 
446
443
  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
447
444
  { extensions: { status: 502 } }
448
445
 
449
- ✓  node  test/pages/pvt/account/cards/index.test.ts (8 tests) 11ms
450
- ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 43ms
451
- ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (11 tests) 822ms
452
- ✓ navigates with the search term on blur  359ms
453
- ✓ navigates without a label param when the search is cleared  357ms
454
- ✓  node  test/components/quotes/quoteFilters.test.ts (26 tests) 35ms
455
- ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 14ms
456
- ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 9ms
446
+ ✓  node  test/pages/pvt/account/cards/index.test.ts (8 tests) 6ms
447
+ ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 28ms
448
+ ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (11 tests) 798ms
449
+ ✓ navigates with the search term on blur  360ms
450
+ ✓ navigates without a label param when the search is cleared  356ms
451
+ ✓  node  test/components/quotes/quoteFilters.test.ts (26 tests) 31ms
452
+ ✓  node  test/utils/productJsonLd.test.ts (36 tests) 11ms
453
+ ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 12ms
457
454
  stderr | test/pages/api/graphql.test.ts > /api/graphql error status propagation > propagates the upstream 400 from a wrapped BadRequestError instead of 500
458
455
  Graphql execution returned with error: [
459
456
  {
@@ -505,7 +502,7 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
505
502
 
506
503
  stderr | test/pages/api/graphql.test.ts > /api/graphql request handling > maps BadRequestError thrown by execute to its status
507
504
  Something unexpected occurred querying Graphql endpoint:
508
- _ [FastStoreError]: malformed
505
+ k [FastStoreError]: malformed
509
506
  at /home/runner/work/faststore/faststore/packages/core/test/pages/api/graphql.test.ts:201:31
510
507
  at /home/runner/work/faststore/faststore/packages/core/test/pages/api/graphql.test.ts:207:39
511
508
  at file:///home/runner/work/faststore/faststore/node_modules/.pnpm/@vitest+runner@4.0.18/node_modules/@vitest/runner/dist/index.js:1024:60
@@ -563,73 +560,72 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
563
560
  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)
564
561
  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)
565
562
 
566
- ✓  node  test/pages/api/graphql.test.ts (19 tests) 26ms
567
- ✓  node  test/utils/clearCookies.test.ts (20 tests) 23ms
563
+ ✓  node  test/pages/api/graphql.test.ts (19 tests) 28ms
564
+ ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 8ms
565
+ ✓  node  test/utils/clearCookies.test.ts (20 tests) 20ms
568
566
  ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 5ms
569
567
  stderr | test/sdk/account/useSetPassword.test.ts > useSetPassword > maps wrongcredentials error responses to the existing message
570
568
  Set password request failed: Bad Request
571
569
 
572
- ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 42ms
573
- ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 42ms
574
- ✓  node  test/utils/localization/localizeRedirectDestination.test.ts (11 tests) 7ms
575
- ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (8 tests) 182ms
576
- ✓  node  test/utils/localization/hreflang.test.ts (8 tests) 7ms
577
- ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (14 tests) 132ms
578
- ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (13 tests) 9ms
579
- ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (10 tests) 140ms
580
- ✓  node  test/sdk/redirects/index.test.ts (7 tests) 44ms
581
- ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 52ms
582
- ✓  node  test/utils/productJsonLd.test.ts (17 tests) 7ms
583
- ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 47ms
584
- ✓  node  test/proxy.localization.test.ts (5 tests) 54ms
585
- ✓  node  test/components/account/orders/getDeliveryOptionLabel.test.ts (21 tests) 8ms
586
- ✓  node  test/sdk/graphql/retryPolicy.test.ts (18 tests) 20ms
587
- ✓  node  test/server/cms/myAccountDefaultSections.test.ts (9 tests) 5ms
570
+ ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 33ms
571
+ ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 26ms
572
+ ✓  node  test/utils/localization/localizeRedirectDestination.test.ts (11 tests) 5ms
573
+ ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (8 tests) 137ms
574
+ ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (14 tests) 130ms
575
+ ✓  node  test/utils/localization/hreflang.test.ts (8 tests) 5ms
576
+ ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (13 tests) 8ms
577
+ ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (10 tests) 94ms
578
+ ✓  node  test/sdk/redirects/index.test.ts (7 tests) 50ms
579
+ ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 39ms
580
+ ✓  node  test/proxy.localization.test.ts (5 tests) 34ms
581
+ ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 34ms
582
+ ✓  node  test/components/account/orders/getDeliveryOptionLabel.test.ts (21 tests) 6ms
583
+ ✓  node  test/sdk/graphql/retryPolicy.test.ts (18 tests) 15ms
584
+ ✓  node  test/server/cms/myAccountDefaultSections.test.ts (9 tests) 8ms
585
+ ✓  node  test/utils/productJsonLdRendering.test.tsx (5 tests) 11ms
588
586
  ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (6 tests) 6ms
589
- ✓  node  test/server/index.test.ts (7 tests) 1395ms
590
- ✓ should return a valid merged GraphQL schema  332ms
591
- ✓ should handle options and execute  784ms
592
- ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 43ms
593
- ✓  node  test/proxy.test.ts (4 tests) 25ms
594
- ✓  node  test/server/webops-api.test.ts (9 tests) 14ms
595
- ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 33ms
596
- ✓  node  test/components/account/orders/StatusCard.test.ts (3 tests) 27ms
587
+ ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 31ms
588
+ ✓  node  test/server/index.test.ts (7 tests) 1039ms
589
+ ✓ should handle options and execute  591ms
590
+ ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 22ms
591
+ ✓  node  test/server/webops-api.test.ts (9 tests) 11ms
592
+ ✓  node  test/proxy.test.ts (4 tests) 15ms
593
+ ✓  node  test/components/account/orders/StatusCard.test.ts (3 tests) 24ms
597
594
  ✓  node  test/sdk/localization/store-url.test.ts (4 tests) 3ms
598
- ✓  node  test/sdk/cart/waitForSessionValidated.test.ts (5 tests) 12ms
599
- ✓  node  test/utils/productJsonLdRendering.test.tsx (4 tests) 14ms
600
- ✓  node  test/server/content/service.test.ts (5 tests) 5ms
601
- ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 8ms
602
- ✓  node  test/server/cms/global.test.ts (3 tests) 5ms
603
- ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 33ms
604
- ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 7ms
605
- ✓  node  test/components/account/Layout.test.tsx (6 tests) 66ms
606
- ✓  node  test/server/rootSpan.test.ts (4 tests) 27ms
607
- ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 5ms
608
- ✓  node  test/pages/api/preview.test.ts (2 tests) 10ms
609
- ✓  node  test/components/search/SearchInput.test.ts (6 tests) 8ms
610
- ✓  node  test/sdk/recommendations/vrn.test.ts (5 tests) 5ms
611
- ✓  node  test/server/cms/index.test.ts (2 tests) 5ms
612
- ✓  node  test/utils/retry.test.ts (5 tests) 18ms
613
- ✓  node  test/sdk/account/getB2BSessionClaims.test.ts (4 tests) 5ms
614
- ✓  node  test/utils/userOrderStatus.test.ts (4 tests) 5ms
615
- ✓  node  test/sdk/recommendations/useRecommendationUserId.test.ts (4 tests) 191ms
616
- ✓  node  test/utils/unlockResponse.test.ts (11 tests) 4ms
617
- ✓  node  test/sdk/recommendations/useRecommendations.test.ts (2 tests) 5ms
618
- ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 39ms
619
- ✓  node  test/utils/isLocalHost.test.ts (7 tests) 5ms
620
- ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (4 tests) 43ms
621
- ✓  node  test/components/account/orders/getOrderTotalLabel.test.ts (6 tests) 4ms
595
+ ✓  node  test/sdk/cart/waitForSessionValidated.test.ts (5 tests) 9ms
596
+ ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 5ms
597
+ ✓  node  test/server/content/service.test.ts (5 tests) 4ms
598
+ ✓  node  test/server/cms/global.test.ts (3 tests) 4ms
599
+ ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 24ms
600
+ ✓  node  test/components/account/Layout.test.tsx (6 tests) 55ms
601
+ ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 6ms
602
+ ✓  node  test/server/rootSpan.test.ts (4 tests) 25ms
603
+ ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 4ms
604
+ ✓  node  test/pages/api/preview.test.ts (2 tests) 8ms
605
+ ✓  node  test/components/search/SearchInput.test.ts (6 tests) 6ms
606
+ ✓  node  test/sdk/recommendations/vrn.test.ts (5 tests) 3ms
607
+ ✓  node  test/server/cms/index.test.ts (2 tests) 4ms
608
+ ✓  node  test/utils/retry.test.ts (5 tests) 12ms
609
+ ✓  node  test/sdk/account/getB2BSessionClaims.test.ts (4 tests) 3ms
610
+ ✓  node  test/utils/userOrderStatus.test.ts (4 tests) 4ms
611
+ ✓  node  test/sdk/recommendations/useRecommendationUserId.test.ts (4 tests) 182ms
612
+ ✓  node  test/utils/unlockResponse.test.ts (11 tests) 5ms
613
+ ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 29ms
614
+ ✓  node  test/sdk/recommendations/useRecommendations.test.ts (2 tests) 4ms
615
+ ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (4 tests) 36ms
616
+ ✓  node  test/utils/isLocalHost.test.ts (7 tests) 4ms
617
+ ✓  node  test/components/account/orders/listOrdersLabels.test.ts (2 tests) 2ms
618
+ ✓  node  test/components/account/orders/getOrderTotalLabel.test.ts (6 tests) 3ms
622
619
  ✓  node  test/utils/getCookie.test.ts (4 tests) 5ms
623
- ✓  node  test/components/account/orders/listOrdersLabels.test.ts (2 tests) 4ms
624
620
  ✓  node  test/sdk/session/isSignInAreaResolved.test.ts (3 tests) 3ms
625
- ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 4ms
626
621
  ✓  node  test/instrumentation.test.ts (3 tests) 4ms
627
- ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 32ms
628
- ✓  node  test/components/account/orders/orderDetailsLabels.test.ts (1 test) 3ms
629
- ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 10ms
622
+ ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 3ms
623
+ ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 31ms
624
+ ✓  node  test/components/account/orders/orderDetailsLabels.test.ts (1 test) 4ms
625
+ ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 9ms
630
626
 
631
627
   Test Files  123 passed (123)
632
-  Tests  965 passed (965)
633
-  Start at  14:22:58
634
-  Duration  58.15s (transform 14.76s, setup 0ms, import 52.62s, tests 21.12s, environment 70.79s)
628
+  Tests  985 passed (985)
629
+  Start at  14:08:29
630
+  Duration  46.77s (transform 11.26s, setup 0ms, import 41.54s, tests 17.36s, environment 57.52s)
635
631
 
@@ -1727,7 +1727,7 @@ export type StoreProduct = {
1727
1727
  otherLocales: Maybe<Array<StoreProductLocale>>;
1728
1728
  /** Product ID, such as [ISBN](https://www.isbn-international.org/content/what-isbn) or similar global IDs. */
1729
1729
  productID: Scalars['String']['output'];
1730
- /** The product's release date. Formatted using https://en.wikipedia.org/wiki/ISO_8601 */
1730
+ /** The product's release date, passed through exactly as the catalog returns it, without validation or normalization. Usually epoch milliseconds (e.g. "1774224000000"), but some accounts store an ISO 8601 string, and the value may be empty. Parse and validate it before use. */
1731
1731
  releaseDate: Scalars['String']['output'];
1732
1732
  /** Array with review information. */
1733
1733
  review: Array<StoreReview>;
@@ -1730,7 +1730,7 @@ type StoreProduct {
1730
1730
  """Array of additional properties."""
1731
1731
  additionalProperty: [StorePropertyValue!]!
1732
1732
  """
1733
- The product's release date. Formatted using https://en.wikipedia.org/wiki/ISO_8601
1733
+ The product's release date, passed through exactly as the catalog returns it, without validation or normalization. Usually epoch milliseconds (e.g. "1774224000000"), but some accounts store an ISO 8601 string, and the value may be empty. Parse and validate it before use.
1734
1734
  """
1735
1735
  releaseDate: String!
1736
1736
  """Sku Unit Multiplier"""
package/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [4.9.1](https://github.com/vtex/faststore/compare/v4.9.0...v4.9.1) (2026-10-02)
7
+
8
+ ### Bug Fixes
9
+
10
+ - restore raw StoreProduct.releaseDate, normalize only in PDP JSON-LD ([#3508](https://github.com/vtex/faststore/issues/3508)) ([fb0112d](https://github.com/vtex/faststore/commit/fb0112df966dc59b8971c335bcb5fd405b715763)), closes [#3465](https://github.com/vtex/faststore/issues/3465) [#3465](https://github.com/vtex/faststore/issues/3465) [#1438](https://github.com/vtex/faststore/issues/1438) [#3437](https://github.com/vtex/faststore/issues/3437)
11
+
6
12
  # [4.9.0](https://github.com/vtex/faststore/compare/v4.9.0-dev.8...v4.9.0) (2026-09-30)
7
13
 
8
14
  **Note:** Version bump only for package @faststore/core
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "4.9.0",
3
+ "version": "4.9.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -71,11 +71,11 @@
71
71
  "style-loader": "^3.3.1",
72
72
  "swr": "^2.2.5",
73
73
  "use-sync-external-store": "^1.6.0",
74
- "@faststore/api": "4.9.0",
75
- "@faststore/sdk": "4.9.0",
76
- "@faststore/lighthouse": "4.9.0",
77
- "@faststore/ui": "4.9.0",
78
- "@faststore/diagnostics": "4.9.0"
74
+ "@faststore/api": "4.9.1",
75
+ "@faststore/diagnostics": "4.9.1",
76
+ "@faststore/sdk": "4.9.1",
77
+ "@faststore/lighthouse": "4.9.1",
78
+ "@faststore/ui": "4.9.1"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@cypress/code-coverage": "^3.12.1",
@@ -30,7 +30,10 @@ import { execute } from 'src/server'
30
30
  import { getComponentKey } from 'src/utils/cms'
31
31
  import { getChannelForLocale } from 'src/utils/localization/bindingPaths'
32
32
  import { buildHreflangLinks } from 'src/utils/localization/hreflang'
33
- import { toProductJsonLdOffer } from 'src/utils/productJsonLd'
33
+ import {
34
+ toProductJsonLdIdentifiers,
35
+ toProductJsonLdOffer,
36
+ } from 'src/utils/productJsonLd'
34
37
 
35
38
  import storeConfig from 'discovery.config'
36
39
  import {
@@ -295,11 +298,9 @@ function Page({
295
298
  description={description}
296
299
  brand={product.brand.name}
297
300
  sku={product.sku}
298
- // Spread conditionally so an unregistered identifier is omitted rather
299
- // than published as "": next-seo passes these through untouched.
300
- {...(product.gtin && { gtin: product.gtin })}
301
- {...(product.mpn && { mpn: product.mpn })}
302
- {...(product.releaseDate && { releaseDate: product.releaseDate })}
301
+ // Omits empty identifiers and normalizes releaseDate to ISO 8601; see
302
+ // toProductJsonLdIdentifiers.
303
+ {...toProductJsonLdIdentifiers(product)}
303
304
  images={product.image.map((img) => img.url)} // Somehow, Google does not understand this valid Schema.org schema, so we need to do conversions
304
305
  {...(offers && { offers })}
305
306
  {...(itemListElements.length !== 0 && {
@@ -1,6 +1,7 @@
1
1
  import type { ServerProductQueryQuery } from '@generated/graphql'
2
2
 
3
- type ServerOffers = ServerProductQueryQuery['product']['offers']
3
+ type ServerProduct = ServerProductQueryQuery['product']
4
+ type ServerOffers = ServerProduct['offers']
4
5
  type ServerOffer = ServerOffers['offers'][number]
5
6
 
6
7
  /**
@@ -69,3 +70,101 @@ export const toProductJsonLdOffer = (
69
70
 
70
71
  return offer
71
72
  }
73
+
74
+ // Epoch values are disambiguated by magnitude, not digit count: anything at or
75
+ // above this is milliseconds (>= 1973-03-03), anything below is seconds. A
76
+ // millisecond value under the threshold is read as seconds — accepted, since no
77
+ // real catalog carries a release date that early, and a digit-count heuristic
78
+ // leaves a strictly larger hole (9-digit seconds and 12-digit milliseconds both
79
+ // fall through to the string parser, which reads them as a year).
80
+ const MILLISECONDS_THRESHOLD = 1e11
81
+
82
+ const isAllDigits = (value: string) => /^\d+$/.test(value)
83
+
84
+ // An ISO date-time with no timezone designator. `new Date()` reads these as
85
+ // host-local time (date-only forms are already read as UTC), which would make
86
+ // the emitted calendar day depend on the build machine's zone.
87
+ const ISO_DATE_TIME_WITHOUT_ZONE =
88
+ /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/
89
+
90
+ const toDate = (value: string): Date => {
91
+ if (!isAllDigits(value)) {
92
+ return new Date(
93
+ ISO_DATE_TIME_WITHOUT_ZONE.test(value) ? `${value}Z` : value
94
+ )
95
+ }
96
+
97
+ const epoch = Number(value)
98
+
99
+ return new Date(epoch >= MILLISECONDS_THRESHOLD ? epoch : epoch * 1000)
100
+ }
101
+
102
+ /**
103
+ * Normalizes `StoreProduct.releaseDate` to the ISO 8601 calendar date
104
+ * (`YYYY-MM-DD`) Schema.org expects for the PDP's `ProductJsonLd`.
105
+ *
106
+ * The API passes the field through as Intelligent Search delivers it — epoch
107
+ * milliseconds for most accounts, an ISO string for others — because stores
108
+ * read that raw value in their own customizations. Normalizing here, and only
109
+ * here, keeps the structured data valid without changing that contract.
110
+ *
111
+ * Accepts epoch milliseconds, epoch seconds, and date strings. Returns an empty
112
+ * string when the input is absent or unparseable — never throws, and never
113
+ * returns `"Invalid Date"`.
114
+ *
115
+ * The calendar day is always taken in UTC, so a build produces the same markup
116
+ * on any machine. An input carrying a timezone offset is converted first, which
117
+ * means `2026-03-23T21:00:00-05:00` normalizes to `2026-03-24`. That is
118
+ * deliberate: preserving the source calendar day would make the output depend on
119
+ * the offset embedded in each record. An input with no timezone designator is
120
+ * read as UTC rather than as host-local time, for the same reason.
121
+ */
122
+ export const toProductJsonLdReleaseDate = (
123
+ value: string | number | null | undefined
124
+ ): string => {
125
+ if (value === null || value === undefined) {
126
+ return ''
127
+ }
128
+
129
+ const raw = String(value).trim()
130
+
131
+ if (raw === '') {
132
+ return ''
133
+ }
134
+
135
+ const date = toDate(raw)
136
+
137
+ if (Number.isNaN(date.getTime())) {
138
+ return ''
139
+ }
140
+
141
+ return date.toISOString().slice(0, 10)
142
+ }
143
+
144
+ /**
145
+ * Builds the identifier props (`gtin`, `mpn`, `releaseDate`) for the PDP's
146
+ * `ProductJsonLd`, ready to spread.
147
+ *
148
+ * Every key is omitted when its value is empty, because next-seo publishes
149
+ * props untouched and an empty string is invalid Schema.org. `releaseDate` is
150
+ * normalized here because `StoreProduct.releaseDate` is the raw upstream value
151
+ * (usually epoch milliseconds), which Schema.org consumers reject.
152
+ *
153
+ * This lives here rather than inline in `p.tsx` so the rendering tests exercise
154
+ * the same code the page runs: passing `product.releaseDate` straight through
155
+ * would publish `"1774224000000"` and still leave a unit test of the
156
+ * normalizer green.
157
+ */
158
+ export const toProductJsonLdIdentifiers = ({
159
+ gtin,
160
+ mpn,
161
+ releaseDate,
162
+ }: Pick<ServerProduct, 'gtin' | 'mpn' | 'releaseDate'>) => {
163
+ const normalizedReleaseDate = toProductJsonLdReleaseDate(releaseDate)
164
+
165
+ return {
166
+ ...(gtin && { gtin }),
167
+ ...(mpn && { mpn }),
168
+ ...(normalizedReleaseDate && { releaseDate: normalizedReleaseDate }),
169
+ }
170
+ }
@@ -1,5 +1,9 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { toProductJsonLdOffer } from '../../src/utils/productJsonLd'
2
+ import {
3
+ toProductJsonLdIdentifiers,
4
+ toProductJsonLdOffer,
5
+ toProductJsonLdReleaseDate,
6
+ } from '../../src/utils/productJsonLd'
3
7
 
4
8
  const OBJECT_LEVEL = { priceCurrency: 'USD', url: 'https://store.example/p' }
5
9
 
@@ -124,3 +128,99 @@ describe('toProductJsonLdOffer', () => {
124
128
  ).toBeUndefined()
125
129
  })
126
130
  })
131
+
132
+ describe('toProductJsonLdReleaseDate', () => {
133
+ const resolve = toProductJsonLdReleaseDate
134
+
135
+ it('converts epoch milliseconds to an ISO calendar date', () => {
136
+ expect(resolve('1774224000000')).toBe('2026-03-23')
137
+ })
138
+
139
+ it('converts epoch seconds to the same date as its millisecond form', () => {
140
+ expect(resolve('1774224000')).toBe(resolve('1774224000000'))
141
+ })
142
+
143
+ it('reads a value at the millisecond threshold as milliseconds', () => {
144
+ expect(resolve('100000000000')).toBe('1973-03-03')
145
+ })
146
+
147
+ it('reads a value just below the threshold as seconds', () => {
148
+ expect(resolve('99999999999')).toBe('5138-11-16')
149
+ })
150
+
151
+ it('accepts a numeric epoch, not only its string form', () => {
152
+ expect(resolve(1774224000000)).toBe('2026-03-23')
153
+ })
154
+
155
+ it('passes an ISO date through unchanged', () => {
156
+ expect(resolve('2026-03-23')).toBe('2026-03-23')
157
+ })
158
+
159
+ it('truncates an ISO datetime to its calendar date', () => {
160
+ expect(resolve('2026-03-23T14:30:00Z')).toBe('2026-03-23')
161
+ })
162
+
163
+ // UTC is deliberate: preserving the source calendar day would make the
164
+ // output depend on the offset embedded in each record.
165
+ it('resolves an offset that crosses midnight to the UTC day', () => {
166
+ expect(resolve('2026-03-23T21:00:00-05:00')).toBe('2026-03-24')
167
+ })
168
+
169
+ // `new Date(...)` reads an ISO date-time with no timezone designator as
170
+ // host-local, so without explicit handling this value resolves to the day
171
+ // before in any zone ahead of UTC. The build machine must not change the
172
+ // markup.
173
+ it('reads an ISO date-time with no timezone as UTC, not host-local', () => {
174
+ expect(resolve('2026-03-23T00:30:00')).toBe('2026-03-23')
175
+ })
176
+
177
+ it('reads an end-of-day ISO date-time with no timezone as UTC', () => {
178
+ expect(resolve('2026-03-23T23:30:00')).toBe('2026-03-23')
179
+ })
180
+
181
+ it.each([
182
+ ['an empty string', ''],
183
+ ['whitespace', ' '],
184
+ ['an unparseable value', 'not-a-date'],
185
+ ['null', null],
186
+ ['undefined', undefined],
187
+ ])('returns an empty string for %s', (_label, input) => {
188
+ expect(resolve(input)).toBe('')
189
+ })
190
+
191
+ it('never returns "Invalid Date"', () => {
192
+ expect(resolve('garbage')).not.toContain('Invalid')
193
+ })
194
+ })
195
+
196
+ describe('toProductJsonLdIdentifiers', () => {
197
+ it('normalizes an epoch releaseDate to an ISO calendar date', () => {
198
+ expect(
199
+ toProductJsonLdIdentifiers({
200
+ gtin: '0012345678905',
201
+ mpn: 'MPN-1',
202
+ releaseDate: '1774224000000',
203
+ })
204
+ ).toEqual({
205
+ gtin: '0012345678905',
206
+ mpn: 'MPN-1',
207
+ releaseDate: '2026-03-23',
208
+ })
209
+ })
210
+
211
+ it('omits every identifier that is empty', () => {
212
+ expect(
213
+ toProductJsonLdIdentifiers({ gtin: '', mpn: '', releaseDate: '' })
214
+ ).toEqual({})
215
+ })
216
+
217
+ it('omits a releaseDate that cannot be parsed', () => {
218
+ expect(
219
+ toProductJsonLdIdentifiers({
220
+ gtin: '',
221
+ mpn: '',
222
+ releaseDate: 'not-a-date',
223
+ })
224
+ ).toEqual({})
225
+ })
226
+ })
@@ -1,14 +1,17 @@
1
1
  import { ProductJsonLd } from 'next-seo'
2
2
  import { renderToStaticMarkup } from 'react-dom/server'
3
3
  import { describe, expect, it } from 'vitest'
4
- import { toProductJsonLdOffer } from '../../src/utils/productJsonLd'
4
+ import {
5
+ toProductJsonLdIdentifiers,
6
+ toProductJsonLdOffer,
7
+ } from '../../src/utils/productJsonLd'
5
8
 
6
9
  /**
7
10
  * Pins the next-seo behaviour the PDP's JSON-LD depends on.
8
11
  *
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
+ * `p.tsx` omits empty `mpn`, `gtin` and `releaseDate` (via
13
+ * `toProductJsonLdIdentifiers`) rather than passing empty strings, and drops
14
+ * `seller` from the offer allowlist. Both choices only work because of how next-seo builds the script — it forwards
12
15
  * unknown props untouched (so `""` would be published) and turns any truthy
13
16
  * `seller` into an `Organization`, even when it has no name. A next-seo upgrade
14
17
  * that changed either would silently reintroduce the defects from ticket
@@ -71,4 +74,21 @@ describe('next-seo ProductJsonLd contract', () => {
71
74
  url: 'https://store.example/p',
72
75
  })
73
76
  })
77
+
78
+ // `p.tsx` spreads `toProductJsonLdIdentifiers(product)` into ProductJsonLd,
79
+ // and `StoreProduct.releaseDate` is the raw upstream epoch. This pins what
80
+ // actually lands in the published script for that input.
81
+ it('publishes an epoch releaseDate as an ISO calendar date', () => {
82
+ const jsonLd = renderJsonLd(
83
+ toProductJsonLdIdentifiers({
84
+ gtin: '',
85
+ mpn: '',
86
+ releaseDate: '1774224000000',
87
+ })
88
+ )
89
+
90
+ expect(jsonLd.releaseDate).toBe('2026-03-23')
91
+ expect(jsonLd).not.toHaveProperty('gtin')
92
+ expect(jsonLd).not.toHaveProperty('mpn')
93
+ })
74
94
  })