@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.
- package/.turbo/turbo-generate.log +3 -3
- package/.turbo/turbo-test.log +112 -99
- package/CHANGELOG.md +12 -0
- package/cms/faststore/my-account/README.md +2 -2
- package/package.json +6 -6
- package/src/components/account/orders/OrderDetails/section.module.scss +44 -3
- package/src/components/cms/RenderSections.tsx +20 -2
- package/src/pages/[slug]/p.tsx +9 -15
- package/src/pages/pvt/account/403.tsx +1 -0
- package/src/pages/pvt/account/404.tsx +5 -1
- package/src/pages/pvt/account/cards/index.tsx +1 -0
- package/src/pages/pvt/account/orders/[id].tsx +5 -1
- package/src/pages/pvt/account/orders/index.tsx +1 -0
- package/src/pages/pvt/account/profile.tsx +1 -0
- package/src/pages/pvt/account/security.tsx +1 -0
- package/src/pages/pvt/account/user-details.tsx +1 -0
- package/src/server/cms/myAccountDefaultSections.ts +8 -3
- package/src/utils/productJsonLd.ts +71 -0
- package/test/components/cms/RenderSections.browser.test.tsx +135 -0
- package/test/server/cms/myAccountDefaultSections.test.ts +44 -0
- package/test/utils/productJsonLd.test.ts +126 -0
- package/test/utils/productJsonLdRendering.test.tsx +74 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
> @faststore/core@4.7.0-dev.
|
|
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.
|
|
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
|
[33m[STARTED][39m Parse Configuration
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
[32m[COMPLETED][39m Generate to /home/runner/work/faststore/faststore/packages/core/@generated/
|
|
20
20
|
[32m[COMPLETED][39m Generate outputs
|
|
21
21
|
|
|
22
|
-
> @faststore/core@4.7.0-dev.
|
|
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
|
package/.turbo/turbo-test.log
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
|
|
2
|
-
> @faststore/core@4.7.0-dev.
|
|
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
|
[1m[46m RUN [49m[22m [36mv4.0.18 [39m[90m/home/runner/work/faststore/faststore/packages/core[39m
|
|
7
7
|
|
|
8
|
+
Not implemented: navigation to another Document
|
|
9
|
+
Not implemented: navigation to another Document
|
|
10
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/ui/useScrollRestoration.browser.test.ts [2m([22m[2m43 tests[22m[2m)[22m[33m 831[2mms[22m[39m
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
[33m[2m✓[22m[39m
|
|
16
|
-
|
|
17
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/reconcileSessionLocale.browser.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 30[2mms[22m[39m
|
|
21
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx [2m([22m[2m8 tests[22m[2m)[22m[32m 162[2mms[22m[39m
|
|
22
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/recommendations/useRecommendationShelf.browser.test.tsx [2m([22m[2m12 tests[22m[2m)[22m[32m 128[2mms[22m[39m
|
|
14
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/ContractSwitcher.browser.test.tsx [2m([22m[2m13 tests[22m[2m)[22m[33m 3134[2mms[22m[39m
|
|
15
|
+
[33m[2m✓[22m[39m shows an error state when the list fails to load [33m 839[2mms[22m[39m
|
|
16
|
+
[33m[2m✓[22m[39m requires selecting a contract before Confirm is enabled, then switches (REQ-03) [33m 541[2mms[22m[39m
|
|
17
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/cards/MyAccountListCards.browser.test.tsx [2m([22m[2m14 tests[22m[2m)[22m[33m 2789[2mms[22m[39m
|
|
18
|
+
[33m[2m✓[22m[39m shows the tab bar and renders Personal cards by default [33m 1093[2mms[22m[39m
|
|
19
|
+
[33m[2m✓[22m[39m paginates when there are more than 25 cards [33m 498[2mms[22m[39m
|
|
20
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx [2m([22m[2m8 tests[22m[2m)[22m[32m 200[2mms[22m[39m
|
|
21
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/reconcileSessionLocale.browser.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 41[2mms[22m[39m
|
|
22
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/recommendations/useRecommendationShelf.browser.test.tsx [2m([22m[2m12 tests[22m[2m)[22m[32m 166[2mms[22m[39m
|
|
23
23
|
[90mstderr[2m | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
|
|
24
24
|
[22m[39mError in optimistic validation: TypeError: Failed to parse URL from /api/graphql?operationName=ValidateCartMutation&operationHash=3c71b0d3fc2bade14df68abfa271858beb361b12
|
|
25
25
|
[90m at node:internal/deps/undici/undici:16416:13[39m
|
|
@@ -49,7 +49,8 @@ Not implemented: navigation to another Document
|
|
|
49
49
|
[90mstderr[2m | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx[2m > [22m[2mRecommendationShelf[2m > [22m[2mrenders nothing when there is an error
|
|
50
50
|
[22m[39mError fetching recommendations [90mundefined[39m boom for campaign vrn:recommendations:acc:rec-top-items-v2:campaign-1
|
|
51
51
|
|
|
52
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx [2m([22m[2m7 tests[22m[2m)[22m[32m
|
|
52
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx [2m([22m[2m7 tests[22m[2m)[22m[32m 258[2mms[22m[39m
|
|
53
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/getInitialSession.browser.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 40[2mms[22m[39m
|
|
53
54
|
[90mstderr[2m | test/pages/password-protection.browser.test.tsx[2m > [22m[2mPasswordProtectionLogin[2m > [22m[2mposts the password and redirects to the requested return path
|
|
54
55
|
[22m[39mWarning: 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
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/OrganizationDrawer.browser.test.tsx [2m([22m[2m5 tests[22m[2m)[22m[33m 1967[2mms[22m[39m
|
|
236
|
+
[33m[2m✓[22m[39m renders the menu view with contract details and manage footer [33m 987[2mms[22m[39m
|
|
237
|
+
[33m[2m✓[22m[39m opens the contract switcher and returns to the menu on back [33m 418[2mms[22m[39m
|
|
234
238
|
[90mstderr[2m | test/pages/password-protection.browser.test.tsx[2m > [22m[2mPasswordProtectionLogin[2m > [22m[2mshows service unavailable when the login request fails
|
|
235
239
|
[22m[39mWarning: 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
|
-
[32m✓[39m [30m[46m browser [49m[39m test/pages/password-protection.browser.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[33m
|
|
283
|
-
[33m[2m✓[22m[39m renders the password protection form [33m
|
|
284
|
-
[32m✓[39m [30m[46m browser [49m[39m test/
|
|
285
|
-
[33m[2m✓[22m[39m renders the menu view with contract details and manage footer [33m 917[2mms[22m[39m
|
|
286
|
-
[33m[2m✓[22m[39m opens the contract switcher and returns to the menu on back [33m 372[2mms[22m[39m
|
|
287
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/getInitialSession.browser.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 38[2mms[22m[39m
|
|
288
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/changeContractToken.browser.test.ts [2m([22m[2m9 tests[22m[2m)[22m[32m 56[2mms[22m[39m
|
|
286
|
+
[32m✓[39m [30m[46m browser [49m[39m test/pages/password-protection.browser.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[33m 1685[2mms[22m[39m
|
|
287
|
+
[33m[2m✓[22m[39m renders the password protection form [33m 940[2mms[22m[39m
|
|
288
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/changeContractToken.browser.test.ts [2m([22m[2m9 tests[22m[2m)[22m[32m 24[2mms[22m[39m
|
|
289
289
|
[90mstderr[2m | test/sdk/localization/store-url.browser.test.ts
|
|
290
290
|
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
291
291
|
at waitForSessionValidated [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/waitForSessionValidated.ts:16:32[90m)[39m
|
|
@@ -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
|
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
296
296
|
|
|
297
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/localization/store-url.browser.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
297
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/localization/store-url.browser.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
298
|
+
[90mstderr[2m | test/components/cms/RenderSections.browser.test.tsx
|
|
299
|
+
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
300
|
+
at waitForSessionValidated [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/waitForSessionValidated.ts:16:32[90m)[39m
|
|
301
|
+
at validateCart [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/index.ts:120:9[90m)[39m
|
|
302
|
+
at onValidate [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/useStore.ts:18:20[90m)[39m
|
|
303
|
+
at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:354:23)
|
|
304
|
+
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
305
|
+
|
|
306
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/useAvailableContracts.browser.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 40[2mms[22m[39m
|
|
307
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/cms/RenderSections.browser.test.tsx [2m([22m[2m5 tests[22m[2m)[22m[32m 97[2mms[22m[39m
|
|
308
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/templates/SearchPage/SearchWrapper.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 44[2mms[22m[39m
|
|
309
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m 134[2mms[22m[39m
|
|
310
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/useSwitchContract.browser.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 47[2mms[22m[39m
|
|
302
311
|
[90mstderr[2m | test/components/account/doLogout.browser.test.ts
|
|
303
312
|
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
304
313
|
at waitForSessionValidated [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/waitForSessionValidated.ts:16:32[90m)[39m
|
|
@@ -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
|
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
309
318
|
|
|
310
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/account/doLogout.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m
|
|
311
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/sections/Account/AccountListCards.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
312
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/account/OrganizationDrawerHeader.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[
|
|
313
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/account/OrganizationDrawerFooter.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[
|
|
319
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/doLogout.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 24[2mms[22m[39m
|
|
320
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/sections/Account/AccountListCards.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 91[2mms[22m[39m
|
|
321
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/OrganizationDrawerHeader.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 286[2mms[22m[39m
|
|
322
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/account/OrganizationDrawerFooter.browser.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[33m 338[2mms[22m[39m
|
|
314
323
|
[90mstderr[2m | test/sdk/account/clearPersistedSessionState.browser.test.ts
|
|
315
324
|
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
316
325
|
at waitForSessionValidated [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/waitForSessionValidated.ts:16:32[90m)[39m
|
|
@@ -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
|
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
321
330
|
|
|
322
|
-
[32m✓[39m [30m[46m browser [49m[39m test/utils/isLocalHost.browser.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
323
331
|
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/clearPersistedSessionState.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 8[2mms[22m[39m
|
|
324
|
-
[32m✓[39m [30m[46m browser [49m[39m test/
|
|
325
|
-
[32m✓[39m [30m[
|
|
326
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
332
|
+
[32m✓[39m [30m[46m browser [49m[39m test/utils/isLocalHost.browser.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
333
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
334
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/localization/bindingPaths.test.ts [2m([22m[2m71 tests[22m[2m)[22m[32m 20[2mms[22m[39m
|
|
335
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/password-protection-service.test.ts [2m([22m[2m22 tests[22m[2m)[22m[32m 42[2mms[22m[39m
|
|
327
336
|
[90mstderr[2m | test/sdk/account/refreshToken.browser.test.ts
|
|
328
337
|
[22m[39mError in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
|
|
329
338
|
at waitForSessionValidated [90m(/home/runner/work/faststore/faststore/packages/core/[39msrc/sdk/cart/waitForSessionValidated.ts:16:32[90m)[39m
|
|
@@ -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
|
[90m at processTicksAndRejections (node:internal/process/task_queues:103:5)[39m
|
|
334
343
|
|
|
335
|
-
[32m✓[39m [30m[46m browser [49m[39m test/components/skeletons/ProductGridSkeleton.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
336
|
-
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/refreshToken.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m
|
|
337
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/match-url.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m
|
|
338
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/useBindingSelector.test.tsx [2m([22m[2m22 tests[22m[2m)[22m[32m
|
|
339
|
-
[32m✓[39m [30m[43m node [49m[39m test/pages/api/unlock.test.ts [2m([22m[2m15 tests[22m[2m)[22m[32m
|
|
340
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/bindingSelector.test.ts [2m([22m[2m18 tests[22m[2m)[22m[32m
|
|
344
|
+
[32m✓[39m [30m[46m browser [49m[39m test/components/skeletons/ProductGridSkeleton.browser.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m 39[2mms[22m[39m
|
|
345
|
+
[32m✓[39m [30m[46m browser [49m[39m test/sdk/account/refreshToken.browser.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 8[2mms[22m[39m
|
|
346
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/match-url.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 24[2mms[22m[39m
|
|
347
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/useBindingSelector.test.tsx [2m([22m[2m22 tests[22m[2m)[22m[32m 17[2mms[22m[39m
|
|
348
|
+
[32m✓[39m [30m[43m node [49m[39m test/pages/api/unlock.test.ts [2m([22m[2m15 tests[22m[2m)[22m[32m 169[2mms[22m[39m
|
|
349
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/bindingSelector.test.ts [2m([22m[2m18 tests[22m[2m)[22m[32m 10[2mms[22m[39m
|
|
341
350
|
[90mstderr[2m | test/components/search/SearchInputComponent.test.tsx
|
|
342
351
|
[22m[39mWarning: 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
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/search/SearchInputComponent.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[32m
|
|
405
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/search/useSearchHistory.test.ts [2m([22m[2m13 tests[22m[2m)[22m[32m
|
|
406
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/cookieCacheBusting.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m
|
|
413
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/search/SearchInputComponent.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[32m 83[2mms[22m[39m
|
|
414
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/search/useSearchHistory.test.ts [2m([22m[2m13 tests[22m[2m)[22m[32m 52[2mms[22m[39m
|
|
415
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/cookieCacheBusting.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 19[2mms[22m[39m
|
|
407
416
|
[90mstderr[2m | test/pages/pvt/account/cards/index.test.ts[2m > [22m[2mCards page getServerSideProps[2m > [22m[2mredirects to /pvt/account/403 when the query fails with 401/403
|
|
408
417
|
[22m[39m{ extensions: { status: [33m401[39m } }
|
|
409
418
|
|
|
410
419
|
[90mstderr[2m | test/pages/pvt/account/cards/index.test.ts[2m > [22m[2mCards page getServerSideProps[2m > [22m[2mrenders the error state (no redirect) when the query fails with a non-auth status
|
|
411
420
|
[22m[39m{ extensions: { status: [33m502[39m } }
|
|
412
421
|
|
|
413
|
-
[32m✓[39m [30m[43m node [49m[39m test/pages/pvt/account/cards/index.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m
|
|
414
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
415
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
416
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountListQuotesTable.test.tsx [2m([22m[2m12 tests[22m[2m)[22m[33m
|
|
422
|
+
[32m✓[39m [30m[43m node [49m[39m test/pages/pvt/account/cards/index.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 12[2mms[22m[39m
|
|
423
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/clearCookies.test.ts [2m([22m[2m20 tests[22m[2m)[22m[32m 28[2mms[22m[39m
|
|
424
|
+
[32m✓[39m [30m[43m node [49m[39m test/experimental/myAccountCmsServerSideProps.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 11[2mms[22m[39m
|
|
425
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountListQuotesTable.test.tsx [2m([22m[2m12 tests[22m[2m)[22m[33m 445[2mms[22m[39m
|
|
417
426
|
[90mstderr[2m | test/pages/api/graphql.test.ts[2m > [22m[2m/api/graphql error status propagation[2m > [22m[2mpropagates the upstream 400 from a wrapped BadRequestError instead of 500
|
|
418
427
|
[22m[39mGraphql 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/[4m.pnpm[24m/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/[4mvitest[24m/dist/chunks/traces.CCmnQaNT.js:142:27)
|
|
508
517
|
|
|
509
518
|
[32m✓[39m [30m[43m node [49m[39m test/pages/api/graphql.test.ts [2m([22m[2m17 tests[22m[2m)[22m[32m 29[2mms[22m[39m
|
|
519
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountListQuotes.test.tsx [2m([22m[2m9 tests[22m[2m)[22m[33m 848[2mms[22m[39m
|
|
520
|
+
[33m[2m✓[22m[39m navigates with the search term on blur [33m 365[2mms[22m[39m
|
|
521
|
+
[33m[2m✓[22m[39m navigates without a label param when the search is cleared [33m 362[2mms[22m[39m
|
|
510
522
|
[90mstderr[2m | test/sdk/account/useSetPassword.test.ts[2m > [22m[2museSetPassword[2m > [22m[2mmaps wrongcredentials error responses to the existing message
|
|
511
523
|
[22m[39mSet password request failed: Bad Request
|
|
512
524
|
|
|
513
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/useSetPassword.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m
|
|
514
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
518
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
519
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
520
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
521
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
522
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/
|
|
523
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
524
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/session/syncSalesChannelFromOrderForm.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m
|
|
528
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
529
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
530
|
-
[32m✓[39m [30m[43m node [49m[39m test/proxy.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m
|
|
531
|
-
[32m✓[39m [30m[43m node [49m[39m test/server/webops-api.test.ts [2m([22m[2m9 tests[22m[2m)[22m[32m
|
|
532
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/cart/waitForSessionValidated.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
533
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderFormItems.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m
|
|
534
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
535
|
-
[32m✓[39m [30m[43m node [49m[39m test/server/
|
|
525
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/useSetPassword.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 59[2mms[22m[39m
|
|
526
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/quoteFilters.test.ts [2m([22m[2m23 tests[22m[2m)[22m[32m 40[2mms[22m[39m
|
|
527
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderEntryOperation.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 44[2mms[22m[39m
|
|
528
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/multipleTemplates.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 7[2mms[22m[39m
|
|
529
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/localization/localizeRedirectDestination.test.ts [2m([22m[2m11 tests[22m[2m)[22m[32m 8[2mms[22m[39m
|
|
530
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/getMyAccountRoutes.test.ts [2m([22m[2m12 tests[22m[2m)[22m[32m 10[2mms[22m[39m
|
|
531
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderEntryUpload.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 51[2mms[22m[39m
|
|
532
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuotesFilterSlider.test.tsx [2m([22m[2m5 tests[22m[2m)[22m[32m 166[2mms[22m[39m
|
|
533
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/productJsonLd.test.ts [2m([22m[2m17 tests[22m[2m)[22m[32m 10[2mms[22m[39m
|
|
534
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderEntry.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 47[2mms[22m[39m
|
|
535
|
+
[32m✓[39m [30m[43m node [49m[39m test/proxy.localization.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 56[2mms[22m[39m
|
|
536
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/index.test.ts [2m([22m[2m7 tests[22m[2m)[22m[33m 964[2mms[22m[39m
|
|
537
|
+
[33m[2m✓[22m[39m should return a valid merged GraphQL schema [33m 355[2mms[22m[39m
|
|
538
|
+
[33m[2m✓[22m[39m should handle options and execute [33m 303[2mms[22m[39m
|
|
539
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/session/syncSalesChannelFromOrderForm.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 8[2mms[22m[39m
|
|
540
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/analytics/useStartRecommendationSession.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 54[2mms[22m[39m
|
|
541
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuotesSelectedTags.test.tsx [2m([22m[2m8 tests[22m[2m)[22m[32m 122[2mms[22m[39m
|
|
542
|
+
[32m✓[39m [30m[43m node [49m[39m test/proxy.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 22[2mms[22m[39m
|
|
543
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/webops-api.test.ts [2m([22m[2m9 tests[22m[2m)[22m[32m 14[2mms[22m[39m
|
|
544
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/cart/waitForSessionValidated.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 17[2mms[22m[39m
|
|
545
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/orderEntry/useOrderFormItems.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m 39[2mms[22m[39m
|
|
546
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/content/service.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
547
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/cms/myAccountDefaultSections.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
548
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/productJsonLdRendering.test.tsx [2m([22m[2m4 tests[22m[2m)[22m[32m 15[2mms[22m[39m
|
|
549
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/refreshToken.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 9[2mms[22m[39m
|
|
536
550
|
[32m✓[39m [30m[43m node [49m[39m test/server/cms/global.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
537
551
|
[32m✓[39m [30m[43m node [49m[39m test/sdk/localization/store-url.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 3[2mms[22m[39m
|
|
538
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/auth/useAuth.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
539
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuotesStatusSelector.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[32m
|
|
540
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/getRequestHostname.test.ts [2m([22m[2m12 tests[22m[2m)[22m[32m
|
|
541
|
-
[32m✓[39m [30m[43m node [49m[39m test/server/rootSpan.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m
|
|
542
|
-
[32m✓[39m [30m[43m node [49m[39m test/pages/api/preview.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m
|
|
543
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/
|
|
544
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/validateSessionRefreshToken.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m
|
|
545
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/
|
|
546
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/recommendations/vrn.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
547
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/retry.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m
|
|
552
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/auth/useAuth.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 34[2mms[22m[39m
|
|
553
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuotesStatusSelector.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[32m 144[2mms[22m[39m
|
|
554
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/getRequestHostname.test.ts [2m([22m[2m12 tests[22m[2m)[22m[32m 8[2mms[22m[39m
|
|
555
|
+
[32m✓[39m [30m[43m node [49m[39m test/server/rootSpan.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 72[2mms[22m[39m
|
|
556
|
+
[32m✓[39m [30m[43m node [49m[39m test/pages/api/preview.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 12[2mms[22m[39m
|
|
557
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/account/Layout.test.tsx [2m([22m[2m6 tests[22m[2m)[22m[32m 80[2mms[22m[39m
|
|
558
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/validateSessionRefreshToken.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
559
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/search/SearchInput.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 17[2mms[22m[39m
|
|
560
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/recommendations/vrn.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
561
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/retry.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 15[2mms[22m[39m
|
|
548
562
|
[32m✓[39m [30m[43m node [49m[39m test/server/cms/index.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
549
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/getB2BSessionClaims.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m
|
|
550
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/unlockResponse.test.ts [2m([22m[2m11 tests[22m[2m)[22m[32m
|
|
551
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
552
|
-
[32m✓[39m [30m[43m node [49m[39m test/sdk/recommendations/useRecommendationUserId.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m
|
|
553
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
554
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
555
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
556
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils/
|
|
557
|
-
[32m✓[39m [30m[43m node [49m[39m test/utils
|
|
558
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
559
|
-
[32m✓[39m [30m[43m node [49m[39m test/components/auth/ProfileChallenge.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m
|
|
560
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
561
|
-
[32m✓[39m [30m[43m node [49m[39m test/
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
[2m
|
|
565
|
-
[2m
|
|
566
|
-
[2m
|
|
567
|
-
[2m Duration [22m 43.29s[2m (transform 10.74s, setup 0ms, import 37.56s, tests 15.12s, environment 54.08s)[22m
|
|
563
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/getB2BSessionClaims.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
564
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/unlockResponse.test.ts [2m([22m[2m11 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
565
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/recommendations/useRecommendations.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
566
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/recommendations/useRecommendationUserId.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 190[2mms[22m[39m
|
|
567
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/isLocalHost.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
568
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/account/OrganizationDrawerBody.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 57[2mms[22m[39m
|
|
569
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/userOrderStatus.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
570
|
+
[32m✓[39m [30m[43m node [49m[39m test/utils/getCookie.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
|
571
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/analytics/utils.test.ts [2m([22m[2m2 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
572
|
+
[32m✓[39m [30m[43m node [49m[39m test/instrumentation.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 5[2mms[22m[39m
|
|
573
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/auth/ProfileChallenge.test.tsx [2m([22m[2m3 tests[22m[2m)[22m[32m 42[2mms[22m[39m
|
|
574
|
+
[32m✓[39m [30m[43m node [49m[39m test/sdk/account/clearPersistedSessionState.test.ts [2m([22m[2m1 test[22m[2m)[22m[32m 13[2mms[22m[39m
|
|
575
|
+
[32m✓[39m [30m[43m node [49m[39m test/components/quotes/MyAccountQuoteStatusBadge.test.tsx [2m([22m[2m2 tests[22m[2m)[22m[32m 32[2mms[22m[39m
|
|
576
|
+
|
|
577
|
+
[2m Test Files [22m [1m[32m90 passed[39m[22m[90m (90)[39m
|
|
578
|
+
[2m Tests [22m [1m[32m723 passed[39m[22m[90m (723)[39m
|
|
579
|
+
[2m Start at [22m 14:52:14
|
|
580
|
+
[2m Duration [22m 46.99s[2m (transform 11.84s, setup 0ms, import 41.77s, tests 16.67s, environment 57.05s)[22m
|
|
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/` —
|
|
36
|
-
- `pages/` —
|
|
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.
|
|
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/
|
|
75
|
-
"@faststore/lighthouse": "4.7.0-dev.
|
|
76
|
-
"@faststore/sdk": "4.7.0-dev.
|
|
77
|
-
"@faststore/
|
|
78
|
-
"@faststore/
|
|
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
|
-
|
|
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
|
-
[
|
|
163
|
+
[class~="section-account-order-status"] {
|
|
136
164
|
grid-column: span 2;
|
|
137
165
|
}
|
|
138
166
|
|
|
139
|
-
[
|
|
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
|
-
|
|
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
|
package/src/pages/[slug]/p.tsx
CHANGED
|
@@ -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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
@@ -57,7 +57,11 @@ function Page({
|
|
|
57
57
|
|
|
58
58
|
<Layout accountName={accountName}>
|
|
59
59
|
{sections && sections.length > 0 && (
|
|
60
|
-
<RenderSectionsBase
|
|
60
|
+
<RenderSectionsBase
|
|
61
|
+
skipLazyLoading
|
|
62
|
+
sections={sections}
|
|
63
|
+
components={COMPONENTS}
|
|
64
|
+
/>
|
|
61
65
|
)}
|
|
62
66
|
</Layout>
|
|
63
67
|
</RenderSections>
|
|
@@ -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
|
-
|
|
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,
|
|
@@ -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
|
+
})
|