@faststore/core 4.9.1-dev.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.
@@ -7,29 +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) 1266ms
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) 3075ms
15
- ✓ shows the tab bar and renders Personal cards by default  1368ms
16
- ✓ paginates when there are more than 25 cards  454ms
17
- ✓  browser  test/components/account/ContractSwitcher.browser.test.tsx (22 tests) 3836ms
18
- ✓ shows a loading state while contracts load  336ms
19
- ✓ shows an error state when the list fails to load  608ms
20
- ✓ requires selecting a contract before Confirm is enabled, then switches (REQ-03)  605ms
21
- ✓ keeps Confirm disabled until a different contract is selected  305ms
22
- ✓ keeps Confirm disabled when switching is unavailable  301ms
23
- ✓ honors CMS-provided labels for the header, current session, search, footer, and available count  372ms
24
- ✓  browser  test/components/account/orders/BuyingPolicyAlert.browser.test.tsx (13 tests) 2985ms
25
- ✓ opens the reject confirmation modal with the default English copy  1310ms
26
- ✓ opens the reject confirmation modal with CMS-provided labels  484ms
27
- ✓  browser  test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx (13 tests) 237ms
28
- ✓  browser  test/sdk/localization/useBindingSelector.browser.test.tsx (9 tests) 118ms
29
- ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (10 tests) 1963ms
30
- ✓ renders the menu view with contract details and manage footer  1061ms
31
- ✓ opens the contract switcher and returns to the menu on back  388ms
32
- ✓  browser  test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx (8 tests) 98ms
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
33
29
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
34
30
  Error in optimistic validation: TypeError: Failed to parse URL from /api/graphql?operationName=ValidateCartMutation&operationHash=3c71b0d3fc2bade14df68abfa271858beb361b12
35
31
   at node:internal/deps/undici/undici:16416:13
@@ -56,14 +52,13 @@ Browserslist: browsers data (caniuse-lite) is 7 months old. Please run:
56
52
  }
57
53
  }
58
54
 
59
- ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (5 tests) 21ms
55
+ ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 50ms
60
56
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx > RecommendationShelf > renders nothing when there is an error
61
57
  Error fetching recommendations undefined boom for campaign vrn:recommendations:acc:rec-top-items-v2:campaign-1
62
58
 
63
- ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 134ms
64
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 82ms
65
- ✓  browser  test/components/CartSidebar/CartSidebar.browser.test.tsx (5 tests) 121ms
66
- ✓  browser  test/components/sections/Account/AccountOrderDetails.browser.test.tsx (4 tests) 78ms
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,11 +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) 856ms
297
- ✓ renders the password protection form  441ms
298
- ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (6 tests) 14ms
299
- ✓  browser  test/components/navigation/Navbar.browser.test.tsx (3 tests) 61ms
300
- ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (8 tests) 410ms
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
301
297
  stderr | test/components/cms/RenderSections.browser.test.tsx
302
298
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
303
299
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -306,9 +302,9 @@ This ensures that you're testing the behavior the user would see in the browser.
306
302
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
307
303
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
308
304
 
309
- ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (5 tests) 47ms
310
- ✓  browser  test/components/cms/RenderSections.browser.test.tsx (5 tests) 89ms
311
- ✓  browser  test/components/navigation/NavbarSlider.browser.test.tsx (2 tests) 49ms
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
312
308
  stderr | test/sdk/localization/store-url.browser.test.ts
313
309
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
314
310
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -318,14 +314,13 @@ This ensures that you're testing the behavior the user would see in the browser.
318
314
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
319
315
 
320
316
  ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 4ms
321
- ✓  browser  test/components/account/Menu.browser.test.tsx (6 tests) 394ms
322
- ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 37ms
323
- ✓  browser  test/components/templates/SearchPage/SearchWrapper.browser.test.tsx (3 tests) 36ms
324
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx (2 tests) 130ms
325
- ✓  browser  test/components/sections/Account/AccountListQuotes.browser.test.tsx (4 tests) 121ms
326
- ✓  browser  test/components/navigation/NavbarAccountArea.browser.test.tsx (4 tests) 65ms
327
- ✓  browser  test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx (3 tests) 65ms
328
- ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 19ms
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
329
324
  stderr | test/components/account/doLogout.browser.test.ts
330
325
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
331
326
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -334,9 +329,9 @@ This ensures that you're testing the behavior the user would see in the browser.
334
329
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
335
330
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
336
331
 
337
- ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 24ms
338
- ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (5 tests) 363ms
339
- ✓  browser  test/components/ui/LocalizationButton.browser.test.tsx (3 tests) 40ms
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
340
335
  stderr | test/sdk/account/clearPersistedSessionState.browser.test.ts
341
336
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
342
337
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -345,22 +340,21 @@ This ensures that you're testing the behavior the user would see in the browser.
345
340
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
346
341
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
347
342
 
348
- ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (4 tests) 21ms
349
- ✓  browser  test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx (2 tests) 102ms
350
- ✓  browser  test/components/sections/Account/AccountOrderDelivery.browser.test.tsx (2 tests) 111ms
351
- ✓  browser  test/components/sections/Account/AccountOrderStatus.browser.test.tsx (3 tests) 98ms
352
- ✓  browser  test/components/sections/Account/AccountOrdersList.browser.test.tsx (2 tests) 31ms
353
- ✓  browser  test/components/sections/Account/AccountListCards.browser.test.tsx (3 tests) 85ms
354
- ✓  browser  test/components/account/orders/SummaryCard.browser.test.tsx (2 tests) 93ms
355
- ✓  browser  test/components/account/orders/DeliveryOptionAccordionDeliveryInfo.browser.test.tsx (1 test) 31ms
356
- ✓  browser  test/components/account/OrganizationSignInButton.browser.test.tsx (2 tests) 273ms
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
357
353
  ✓  browser  test/utils/isLocalHost.browser.test.ts (3 tests) 4ms
358
- ✓  browser  test/components/account/orders/StatusCard.browser.test.tsx (2 tests) 116ms
359
354
  ✓  browser  test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts (1 test) 3ms
360
- ✓  browser  test/components/account/orders/DeliveryCard.browser.test.tsx (1 test) 55ms
361
- ✓  browser  test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx (1 test) 49ms
362
- ✓  browser  test/components/skeletons/ProductGridSkeleton.browser.test.tsx (2 tests) 45ms
363
- ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 38ms
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
364
358
  stderr | test/sdk/account/refreshToken.browser.test.ts
365
359
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
366
360
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -369,17 +363,18 @@ This ensures that you're testing the behavior the user would see in the browser.
369
363
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
370
364
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
371
365
 
372
- ✓  node  test/server/password-protection-service.test.ts (22 tests) 28ms
373
- ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 6ms
374
- ✓  node  test/pages/api/switch-contract.test.ts (25 tests) 90ms
375
- ✓  node  test/utils/match-url.test.ts (10 tests) 10ms
376
- ✓  browser  test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx (1 test) 213ms
377
- ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 24ms
378
- ✓  node  test/pages/api/unlock.test.ts (15 tests) 162ms
379
- ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 10ms
366
+ ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 4ms
367
+ ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 19ms
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
380
374
  stderr | test/components/search/SearchInputComponent.test.tsx
381
375
  Warning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?
382
376
 
377
+ ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 8ms
383
378
  stderr | test/components/search/SearchInputComponent.test.tsx > SearchInput (OES integration) > renders without crashing
384
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.
385
380
  at div
@@ -440,21 +435,22 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
440
435
  at Suspense
441
436
  at Wrapper (/home/runner/work/faststore/faststore/packages/core/test/components/search/SearchInputComponent.test.tsx:106:20)
442
437
 
443
- ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 87ms
444
- ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (15 tests) 421ms
438
+ ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 63ms
439
+ ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (15 tests) 350ms
445
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
446
441
  { extensions: { status: 401 } }
447
442
 
448
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
449
444
  { extensions: { status: 502 } }
450
445
 
451
- ✓  node  test/pages/pvt/account/cards/index.test.ts (8 tests) 9ms
452
- ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 40ms
453
- ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (11 tests) 858ms
454
- ✓ navigates with the search term on blur  364ms
455
- ✓ navigates without a label param when the search is cleared  358ms
456
- ✓  node  test/components/quotes/quoteFilters.test.ts (26 tests) 42ms
457
- ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 16ms
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
458
454
  stderr | test/pages/api/graphql.test.ts > /api/graphql error status propagation > propagates the upstream 400 from a wrapped BadRequestError instead of 500
459
455
  Graphql execution returned with error: [
460
456
  {
@@ -506,7 +502,7 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
506
502
 
507
503
  stderr | test/pages/api/graphql.test.ts > /api/graphql request handling > maps BadRequestError thrown by execute to its status
508
504
  Something unexpected occurred querying Graphql endpoint:
509
- _ [FastStoreError]: malformed
505
+ k [FastStoreError]: malformed
510
506
  at /home/runner/work/faststore/faststore/packages/core/test/pages/api/graphql.test.ts:201:31
511
507
  at /home/runner/work/faststore/faststore/packages/core/test/pages/api/graphql.test.ts:207:39
512
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
@@ -564,73 +560,72 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
564
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)
565
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)
566
562
 
567
- ✓  node  test/pages/api/graphql.test.ts (19 tests) 29ms
568
- ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 10ms
569
- ✓  node  test/utils/clearCookies.test.ts (20 tests) 29ms
570
- ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 7ms
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
566
+ ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 5ms
571
567
  stderr | test/sdk/account/useSetPassword.test.ts > useSetPassword > maps wrongcredentials error responses to the existing message
572
568
  Set password request failed: Bad Request
573
569
 
574
- ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 58ms
575
- ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 55ms
576
- ✓  node  test/utils/localization/localizeRedirectDestination.test.ts (11 tests) 8ms
577
- ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (8 tests) 206ms
578
- ✓  node  test/utils/localization/hreflang.test.ts (8 tests) 15ms
579
- ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (13 tests) 10ms
580
- ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (14 tests) 180ms
581
- ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (10 tests) 124ms
582
- ✓  node  test/sdk/redirects/index.test.ts (7 tests) 51ms
583
- ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 49ms
584
- ✓  node  test/utils/productJsonLd.test.ts (17 tests) 10ms
585
- ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 35ms
586
- ✓  node  test/proxy.localization.test.ts (5 tests) 38ms
587
- ✓  node  test/components/account/orders/getDeliveryOptionLabel.test.ts (21 tests) 10ms
588
- ✓  node  test/sdk/graphql/retryPolicy.test.ts (18 tests) 26ms
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
589
584
  ✓  node  test/server/cms/myAccountDefaultSections.test.ts (9 tests) 8ms
585
+ ✓  node  test/utils/productJsonLdRendering.test.tsx (5 tests) 11ms
590
586
  ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (6 tests) 6ms
591
- ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 42ms
592
- ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 28ms
593
- ✓  node  test/proxy.test.ts (4 tests) 15ms
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
594
591
  ✓  node  test/server/webops-api.test.ts (9 tests) 11ms
595
- ✓  node  test/server/index.test.ts (7 tests) 1779ms
596
- ✓ should handle options and execute  1260ms
597
- ✓  node  test/components/account/orders/StatusCard.test.ts (3 tests) 28ms
598
- ✓  node  test/sdk/localization/store-url.test.ts (4 tests) 5ms
599
- ✓  node  test/sdk/cart/waitForSessionValidated.test.ts (5 tests) 11ms
600
- ✓  node  test/utils/productJsonLdRendering.test.tsx (4 tests) 14ms
601
- ✓  node  test/server/content/service.test.ts (5 tests) 5ms
602
- ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 10ms
592
+ ✓  node  test/proxy.test.ts (4 tests) 15ms
593
+ ✓  node  test/components/account/orders/StatusCard.test.ts (3 tests) 24ms
594
+ ✓  node  test/sdk/localization/store-url.test.ts (4 tests) 3ms
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
603
598
  ✓  node  test/server/cms/global.test.ts (3 tests) 4ms
604
- ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 31ms
605
- ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 8ms
606
- ✓  node  test/components/account/Layout.test.tsx (6 tests) 81ms
607
- ✓  node  test/server/rootSpan.test.ts (4 tests) 46ms
608
- ✓  node  test/components/search/SearchInput.test.ts (6 tests) 9ms
609
- ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 6ms
610
- ✓  node  test/pages/api/preview.test.ts (2 tests) 9ms
611
- ✓  node  test/sdk/recommendations/vrn.test.ts (5 tests) 7ms
612
- ✓  node  test/server/cms/index.test.ts (2 tests) 7ms
613
- ✓  node  test/utils/retry.test.ts (5 tests) 14ms
614
- ✓  node  test/sdk/account/getB2BSessionClaims.test.ts (4 tests) 6ms
615
- ✓  node  test/utils/userOrderStatus.test.ts (4 tests) 5ms
616
- ✓  node  test/sdk/recommendations/useRecommendationUserId.test.ts (4 tests) 196ms
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
617
612
  ✓  node  test/utils/unlockResponse.test.ts (11 tests) 5ms
618
- ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 62ms
619
- ✓  node  test/sdk/recommendations/useRecommendations.test.ts (2 tests) 6ms
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
620
616
  ✓  node  test/utils/isLocalHost.test.ts (7 tests) 4ms
621
- ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (4 tests) 46ms
622
- ✓  node  test/components/account/orders/getOrderTotalLabel.test.ts (6 tests) 4ms
623
- ✓  node  test/components/account/orders/listOrdersLabels.test.ts (2 tests) 4ms
624
- ✓  node  test/utils/getCookie.test.ts (4 tests) 6ms
617
+ ✓  node  test/components/account/orders/listOrdersLabels.test.ts (2 tests) 2ms
618
+ ✓  node  test/components/account/orders/getOrderTotalLabel.test.ts (6 tests) 3ms
619
+ ✓  node  test/utils/getCookie.test.ts (4 tests) 5ms
625
620
  ✓  node  test/sdk/session/isSignInAreaResolved.test.ts (3 tests) 3ms
621
+ ✓  node  test/instrumentation.test.ts (3 tests) 4ms
626
622
  ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 3ms
627
- ✓  node  test/instrumentation.test.ts (3 tests) 6ms
628
- ✓  node  test/components/account/orders/orderDetailsLabels.test.ts (1 test) 5ms
629
- ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 19ms
630
- ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 26ms
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
631
626
 
632
627
   Test Files  123 passed (123)
633
-  Tests  965 passed (965)
634
-  Start at  14:35:24
635
-  Duration  60.95s (transform 13.85s, setup 0ms, import 52.85s, tests 23.55s, environment 75.21s)
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)
636
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,9 +3,11 @@
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-dev.0](https://github.com/vtex/faststore/compare/v4.9.0...v4.9.1-dev.0) (2026-09-30)
6
+ ## [4.9.1](https://github.com/vtex/faststore/compare/v4.9.0...v4.9.1) (2026-10-02)
7
7
 
8
- **Note:** Version bump only for package @faststore/core
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)
9
11
 
10
12
  # [4.9.0](https://github.com/vtex/faststore/compare/v4.9.0-dev.8...v4.9.0) (2026-09-30)
11
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "4.9.1-dev.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/diagnostics": "4.9.1-dev.0",
75
- "@faststore/lighthouse": "4.9.1-dev.0",
76
- "@faststore/ui": "4.9.1-dev.0",
77
- "@faststore/sdk": "4.9.1-dev.0",
78
- "@faststore/api": "4.9.1-dev.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
  })