@faststore/core 4.5.0-dev.6 → 4.5.0-dev.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
 
2
- > @faststore/core@4.5.0-dev.5 generate /home/runner/work/faststore/faststore/packages/core
2
+ > @faststore/core@4.5.0-dev.6 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.5.0-dev.5 gen-types /home/runner/work/faststore/faststore/packages/core
6
+ > @faststore/core@4.5.0-dev.6 gen-types /home/runner/work/faststore/faststore/packages/core
7
7
  > node ../cli/bin/run generate-types .
8
8
 
9
9
  [STARTED] Parse Configuration
@@ -19,7 +19,7 @@
19
19
  [COMPLETED] Generate to /home/runner/work/faststore/faststore/packages/core/@generated/
20
20
  [COMPLETED] Generate outputs
21
21
 
22
- > @faststore/core@4.5.0-dev.5 cache-graphql /home/runner/work/faststore/faststore/packages/core
22
+ > @faststore/core@4.5.0-dev.6 cache-graphql /home/runner/work/faststore/faststore/packages/core
23
23
  > node ../cli/bin/run cache-graphql --config=./discovery.config.default.js --queries=./@generated/persisted-documents.json
24
24
 
25
25
  [Info] - Config file location: /home/runner/work/faststore/faststore/packages/core/discovery.config.default.js
@@ -1,11 +1,11 @@
1
1
 
2
- > @faststore/core@4.5.0-dev.5 test /home/runner/work/faststore/faststore/packages/core
2
+ > @faststore/core@4.5.0-dev.6 test /home/runner/work/faststore/faststore/packages/core
3
3
  > vitest run
4
4
 
5
5
 
6
6
   RUN  v4.0.7 /home/runner/work/faststore/faststore/packages/core
7
7
 
8
- ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (5 tests) 28ms
8
+ ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (5 tests) 46ms
9
9
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
10
10
  Error in optimistic validation: TypeError: Failed to parse URL from /api/graphql?operationName=ValidateCartMutation&operationHash=8199d03debb0a6751ec53d8ee8987b78d0292acb
11
11
   at node:internal/deps/undici/undici:16416:13
@@ -32,12 +32,15 @@
32
32
  }
33
33
  }
34
34
 
35
+ Browserslist: browsers data (caniuse-lite) is 6 months old. Please run:
36
+ npx update-browserslist-db@latest
37
+ Why you should do it regularly: https://github.com/browserslist/update-db#readme
35
38
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx > RecommendationShelf > renders nothing when there is an error
36
39
  Error fetching recommendations undefined boom for campaign vrn:recommendations:acc:rec-top-items-v2:campaign-1
37
40
 
38
- ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 311ms
39
- ✓  browser  test/components/account/ContractSwitcher.browser.test.tsx (13 tests) 1942ms
40
- ✓ shows an error state when the list fails to load  487ms
41
+ ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 237ms
42
+ ✓  browser  test/components/account/ContractSwitcher.browser.test.tsx (13 tests) 1804ms
43
+ ✓ shows an error state when the list fails to load  571ms
41
44
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > posts the password and redirects to the requested return path
42
45
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
43
46
 
@@ -219,7 +222,6 @@ act(() => {
219
222
  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
220
223
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
221
224
 
222
- ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (6 tests) 34ms
223
225
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > shows service unavailable when the login request fails
224
226
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
225
227
 
@@ -268,14 +270,12 @@ act(() => {
268
270
  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
269
271
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
270
272
 
271
- ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 2030ms
272
- ✓ renders the password protection form  836ms
273
- ✓ posts the password and redirects to the requested return path  316ms
274
- ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (5 tests) 1955ms
275
- ✓ renders the menu view with contract details and manage footer  957ms
276
- ✓ opens the contract switcher and returns to the menu on back  413ms
277
- ✓ resets to the menu when the drawer closes  310ms
278
- ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 46ms
273
+ ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 1941ms
274
+ ✓ renders the password protection form  822ms
275
+ ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (6 tests) 25ms
276
+ ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 59ms
277
+ ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (5 tests) 1651ms
278
+ ✓ renders the menu view with contract details and manage footer  974ms
279
279
  stderr | test/sdk/localization/store-url.browser.test.ts
280
280
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
281
281
  at validateCart (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/index.ts:121:27)
@@ -283,9 +283,9 @@ This ensures that you're testing the behavior the user would see in the browser.
283
283
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:354:23)
284
284
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
285
285
 
286
- ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 32ms
287
- ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 71ms
288
- ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (4 tests) 34ms
286
+ ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 36ms
287
+ ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 91ms
288
+ ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (4 tests) 53ms
289
289
  stderr | test/components/account/doLogout.browser.test.ts
290
290
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
291
291
  at validateCart (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/index.ts:121:27)
@@ -293,8 +293,8 @@ This ensures that you're testing the behavior the user would see in the browser.
293
293
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:354:23)
294
294
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
295
295
 
296
- ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 43ms
297
- ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (3 tests) 280ms
296
+ ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 24ms
297
+ ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (3 tests) 249ms
298
298
  stderr | test/sdk/account/clearPersistedSessionState.browser.test.ts
299
299
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
300
300
  at validateCart (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/index.ts:121:27)
@@ -302,12 +302,12 @@ This ensures that you're testing the behavior the user would see in the browser.
302
302
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:354:23)
303
303
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
304
304
 
305
- ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (1 test) 9ms
306
- ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (3 tests) 338ms
307
- ✓  browser  test/utils/isLocalHost.browser.test.ts (3 tests) 5ms
308
- ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 19ms
305
+ ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (3 tests) 248ms
306
+ ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (1 test) 8ms
307
+ ✓  browser  test/utils/isLocalHost.browser.test.ts (3 tests) 4ms
308
+ ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 23ms
309
309
  ✓  browser  test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts (1 test) 3ms
310
- ✓  node  test/server/password-protection-service.test.ts (22 tests) 42ms
310
+ ✓  node  test/server/password-protection-service.test.ts (22 tests) 30ms
311
311
  stderr | test/sdk/account/refreshToken.browser.test.ts
312
312
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
313
313
  at validateCart (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/index.ts:121:27)
@@ -315,11 +315,11 @@ This ensures that you're testing the behavior the user would see in the browser.
315
315
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:354:23)
316
316
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
317
317
 
318
- ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 5ms
319
- ✓  node  test/utils/match-url.test.ts (10 tests) 8ms
320
- ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 16ms
321
- ✓  node  test/pages/api/unlock.test.ts (15 tests) 308ms
322
- ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 11ms
318
+ ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 6ms
319
+ ✓  node  test/utils/match-url.test.ts (10 tests) 10ms
320
+ ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 10ms
321
+ ✓  node  test/pages/api/unlock.test.ts (15 tests) 201ms
322
+ ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 6ms
323
323
  stderr | test/components/search/SearchInputComponent.test.tsx
324
324
  Warning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?
325
325
 
@@ -384,22 +384,22 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
384
384
  at Wrapper (/home/runner/work/faststore/faststore/packages/core/test/components/search/SearchInputComponent.test.tsx:106:20)
385
385
 
386
386
  ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 73ms
387
- ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 59ms
388
- ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 11ms
389
- ✓  node  test/utils/clearCookies.test.ts (20 tests) 23ms
387
+ ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 13ms
388
+ ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 51ms
389
+ ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 9ms
390
+ ✓  node  test/utils/clearCookies.test.ts (20 tests) 27ms
390
391
  stderr | test/sdk/account/useSetPassword.test.ts > useSetPassword > maps wrongcredentials error responses to the existing message
391
392
  Set password request failed: Bad Request
392
393
 
393
- ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 38ms
394
- ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (12 tests) 365ms
395
- ✓  node  test/components/quotes/quoteFilters.test.ts (23 tests) 25ms
396
- ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 7ms
397
- ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (9 tests) 835ms
398
- ✓ navigates with the search term on blur  360ms
399
- ✓ navigates without a label param when the search is cleared  358ms
400
- ✓  node  test/utils/localization/localizeRedirectDestination.test.ts (11 tests) 7ms
394
+ ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 48ms
395
+ ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (12 tests) 354ms
396
+ ✓  node  test/components/quotes/quoteFilters.test.ts (23 tests) 32ms
397
+ ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 4ms
398
+ ✓  node  test/utils/localization/localizeRedirectDestination.test.ts (11 tests) 4ms
399
+ ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (9 tests) 833ms
400
+ ✓ navigates with the search term on blur  363ms
401
+ ✓ navigates without a label param when the search is cleared  356ms
401
402
  ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 43ms
402
- ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 44ms
403
403
  stderr | test/pages/api/graphql.test.ts > /api/graphql error status propagation > propagates the upstream 400 from a wrapped BadRequestError instead of 500
404
404
  Graphql execution returned with error: [
405
405
  {
@@ -446,47 +446,48 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
446
446
  { message: 'bad', status: 400, type: 'BadRequestError' }
447
447
  ]
448
448
 
449
- ✓  node  test/pages/api/graphql.test.ts (10 tests) 31ms
450
- ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 40ms
451
- ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (5 tests) 153ms
452
- ✓  node  test/proxy.localization.test.ts (5 tests) 45ms
453
- ✓  node  test/server/index.test.ts (7 tests) 1256ms
454
- ✓ should return a valid merged GraphQL schema  306ms
455
- ✓ should handle options and execute  689ms
456
- ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 48ms
457
- ✓  node  test/proxy.test.ts (4 tests) 51ms
458
- ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (8 tests) 100ms
449
+ ✓  node  test/pages/api/graphql.test.ts (10 tests) 24ms
450
+ ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 52ms
451
+ ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 58ms
452
+ ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (5 tests) 145ms
453
+ ✓  node  test/proxy.localization.test.ts (5 tests) 73ms
454
+ ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (10 tests) 5ms
455
+ ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 37ms
456
+ ✓  node  test/server/index.test.ts (7 tests) 1241ms
457
+ ✓ should handle options and execute  743ms
458
+ ✓  node  test/proxy.test.ts (4 tests) 25ms
459
459
  ✓  node  test/server/webops-api.test.ts (9 tests) 22ms
460
- ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 9ms
461
- ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 44ms
460
+ ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (8 tests) 72ms
461
+ ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 7ms
462
+ ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 37ms
463
+ ✓  node  test/server/cms/global.test.ts (3 tests) 3ms
462
464
  ✓  node  test/server/content/service.test.ts (5 tests) 5ms
463
- ✓  node  test/server/cms/global.test.ts (3 tests) 5ms
464
- ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (8 tests) 8ms
465
465
  ✓  node  test/sdk/localization/store-url.test.ts (1 test) 4ms
466
- ✓  node  test/sdk/analytics/hasEnabledRecommendationShelf.test.ts (6 tests) 5ms
467
- ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 5ms
468
- ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 38ms
469
- ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (6 tests) 129ms
470
- ✓  node  test/pages/api/preview.test.ts (2 tests) 9ms
471
- ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 4ms
472
- ✓  node  test/components/search/SearchInput.test.ts (6 tests) 7ms
473
- ✓  node  test/components/RecommendationShelf/vrn.test.ts (5 tests) 6ms
474
- ✓  node  test/server/cms/index.test.ts (2 tests) 4ms
475
- ✓  node  test/utils/retry.test.ts (5 tests) 14ms
466
+ ✓  node  test/sdk/analytics/hasEnabledRecommendationShelf.test.ts (6 tests) 4ms
467
+ ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 6ms
468
+ ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 29ms
469
+ ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (6 tests) 135ms
470
+ ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 3ms
471
+ ✓  node  test/pages/api/preview.test.ts (2 tests) 16ms
472
+ ✓  node  test/components/search/SearchInput.test.ts (6 tests) 8ms
473
+ ✓  node  test/components/RecommendationShelf/vrn.test.ts (5 tests) 5ms
474
+ ✓  node  test/server/cms/index.test.ts (2 tests) 8ms
475
+ ✓  node  test/utils/retry.test.ts (5 tests) 13ms
476
476
  ✓  node  test/components/account/Layout.test.tsx (4 tests) 39ms
477
477
  ✓  node  test/components/RecommendationShelf/useRecommendationUserId.test.ts (4 tests) 190ms
478
- ✓  node  test/utils/unlockResponse.test.ts (11 tests) 6ms
479
- ✓  node  test/components/RecommendationShelf/useRecommendations.test.ts (2 tests) 4ms
480
- ✓  node  test/utils/isLocalHost.test.ts (7 tests) 5ms
481
- ✓  node  test/utils/userOrderStatus.test.ts (3 tests) 5ms
482
- ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 51ms
483
- ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 8ms
484
- ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 44ms
485
- ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 11ms
486
- ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (2 tests) 24ms
487
-
488
-  Test Files  71 passed (71)
489
-  Tests  558 passed (558)
490
-  Start at  20:03:32
491
-  Duration  36.20s (transform 8.20s, setup 0ms, collect 28.89s, tests 11.53s, environment 47.49s, prepare 1.20s)
478
+ ✓  node  test/utils/unlockResponse.test.ts (11 tests) 5ms
479
+ ✓  node  test/server/cms/myAccountDefaultSections.test.ts (5 tests) 4ms
480
+ ✓  node  test/components/RecommendationShelf/useRecommendations.test.ts (2 tests) 5ms
481
+ ✓  node  test/utils/isLocalHost.test.ts (7 tests) 6ms
482
+ ✓  node  test/utils/userOrderStatus.test.ts (3 tests) 4ms
483
+ ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 40ms
484
+ ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 3ms
485
+ ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 26ms
486
+ ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 13ms
487
+ ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (2 tests) 23ms
488
+
489
+  Test Files  73 passed (73)
490
+  Tests  570 passed (570)
491
+  Start at  21:42:33
492
+  Duration  33.01s (transform 7.79s, setup 0ms, collect 26.92s, tests 10.65s, environment 41.89s, prepare 1.09s)
492
493
 
package/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [4.5.0-dev.7](https://github.com/vtex/faststore/compare/v4.5.0-dev.6...v4.5.0-dev.7) (2026-08-03)
7
+
8
+ ### Features
9
+
10
+ - CMS-driven custom My Account pages ([#3411](https://github.com/vtex/faststore/issues/3411)) ([e38b278](https://github.com/vtex/faststore/commit/e38b2780bb7386a8ef7b83a9e3de5956bf6de50e))
11
+
6
12
  # [4.5.0-dev.6](https://github.com/vtex/faststore/compare/v4.5.0-dev.5...v4.5.0-dev.6) (2026-07-28)
7
13
 
8
14
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "4.5.0-dev.6",
3
+ "version": "4.5.0-dev.7",
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.5.0-dev.6",
75
- "@faststore/api": "4.5.0-dev.6",
76
- "@faststore/lighthouse": "4.5.0-dev.6",
77
- "@faststore/sdk": "4.5.0-dev.6",
78
- "@faststore/ui": "4.5.0-dev.6"
74
+ "@faststore/lighthouse": "4.5.0-dev.7",
75
+ "@faststore/diagnostics": "4.5.0-dev.7",
76
+ "@faststore/api": "4.5.0-dev.7",
77
+ "@faststore/ui": "4.5.0-dev.7",
78
+ "@faststore/sdk": "4.5.0-dev.7"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@cypress/code-coverage": "^3.12.1",
@@ -0,0 +1,146 @@
1
+ import { gql } from '@generated/gql'
2
+ import type {
3
+ ServerAccountPageQueryQuery,
4
+ ServerAccountPageQueryQueryVariables,
5
+ } from '@generated/graphql'
6
+ import type { Locator } from '@vtex/client-cms'
7
+ import type { GetServerSideProps } from 'next'
8
+
9
+ import { getGlobalSectionsData } from 'src/components/cms/GlobalSections'
10
+ import { getIsRepresentative } from 'src/sdk/account/getIsRepresentative'
11
+ import type { AccountNavigationLabels } from 'src/sdk/account/getMyAccountRoutes'
12
+ import { validateUser } from 'src/sdk/account/validateUser'
13
+ import { execute } from 'src/server'
14
+ import { fetchMyAccountPageContent } from 'src/server/cms/fetchMyAccountPageContent'
15
+ import { injectGlobalSections } from 'src/server/cms/global'
16
+ import { extractAccountNavigationData } from 'src/server/cms/myAccountDefaultSections'
17
+ import { localizeRedirectDestination } from 'src/utils/localization/localizeRedirectDestination'
18
+ import { withLocaleValidationSSR } from 'src/utils/localization/withLocaleValidation'
19
+ import { getMyAccountRedirect } from 'src/utils/myAccountRedirect'
20
+ import storeConfig from '../../discovery.config'
21
+ import type { MyAccountProps } from './myAccountServerSideProps'
22
+
23
+ export type MyAccountCmsPageProps = MyAccountProps & {
24
+ pageSections: Array<{
25
+ name: string
26
+ data: Record<string, unknown>
27
+ $componentKey?: string
28
+ }>
29
+ navigationLabels: AccountNavigationLabels
30
+ }
31
+
32
+ const query = gql(`
33
+ query ServerAccountPageQuery {
34
+ accountProfile {
35
+ name
36
+ }
37
+ }
38
+ `)
39
+
40
+ /**
41
+ * Reusable SSR factory for CMS-driven custom My Account pages.
42
+ * Keeps the same auth/redirect guards as `myAccountServerSideProps`.
43
+ */
44
+ export function myAccountCmsServerSideProps(
45
+ contentType: string,
46
+ pagePath: string
47
+ ): GetServerSideProps<MyAccountCmsPageProps, Record<string, string>, Locator> {
48
+ const handler: GetServerSideProps<
49
+ MyAccountCmsPageProps,
50
+ Record<string, string>,
51
+ Locator
52
+ > = async (context) => {
53
+ const contentContext = {
54
+ previewData: context.previewData,
55
+ locale: context.locale,
56
+ }
57
+
58
+ const validationResult = await validateUser(context)
59
+
60
+ if (!validationResult.isValid && !validationResult.needsRefresh) {
61
+ return {
62
+ redirect: {
63
+ destination: localizeRedirectDestination('/login', context),
64
+ permanent: false,
65
+ },
66
+ }
67
+ }
68
+
69
+ if (!validationResult.isValid && validationResult.needsRefresh) {
70
+ const currentPath = context.req.url || '/pvt/account'
71
+ return {
72
+ redirect: {
73
+ destination: localizeRedirectDestination(
74
+ `/pvt/account/403?from=${encodeURIComponent(currentPath)}`,
75
+ context
76
+ ),
77
+ permanent: false,
78
+ },
79
+ }
80
+ }
81
+
82
+ const { isFaststoreMyAccountEnabled, redirect } = getMyAccountRedirect({
83
+ query: context.query,
84
+ })
85
+
86
+ if (!isFaststoreMyAccountEnabled) {
87
+ return { redirect }
88
+ }
89
+
90
+ const isRepresentative = getIsRepresentative({
91
+ headers: context.req.headers as Record<string, string>,
92
+ account: storeConfig.api.storeId,
93
+ })
94
+
95
+ const [
96
+ globalSectionsPromise,
97
+ globalSectionsHeaderPromise,
98
+ globalSectionsFooterPromise,
99
+ ] = getGlobalSectionsData(contentContext)
100
+
101
+ const [
102
+ pageContent,
103
+ account,
104
+ globalSections,
105
+ globalSectionsHeader,
106
+ globalSectionsFooter,
107
+ ] = await Promise.all([
108
+ fetchMyAccountPageContent(contentType, contentContext, pagePath),
109
+ execute<
110
+ ServerAccountPageQueryQueryVariables,
111
+ ServerAccountPageQueryQuery
112
+ >(
113
+ {
114
+ variables: {},
115
+ operation: query,
116
+ },
117
+ { headers: { ...context.req.headers } }
118
+ ),
119
+ globalSectionsPromise,
120
+ globalSectionsHeaderPromise,
121
+ globalSectionsFooterPromise,
122
+ ])
123
+
124
+ const { pageSections, navigationData } = extractAccountNavigationData(
125
+ pageContent.sections
126
+ )
127
+
128
+ const globalSectionsResult = injectGlobalSections({
129
+ globalSections,
130
+ globalSectionsHeader,
131
+ globalSectionsFooter,
132
+ })
133
+
134
+ return {
135
+ props: {
136
+ globalSections: globalSectionsResult,
137
+ accountName: account.data?.accountProfile?.name ?? '',
138
+ isRepresentative,
139
+ pageSections,
140
+ navigationLabels: navigationData as AccountNavigationLabels,
141
+ },
142
+ }
143
+ }
144
+
145
+ return withLocaleValidationSSR(handler)
146
+ }
@@ -3,6 +3,12 @@ export interface Route {
3
3
  title: string
4
4
  /* Accessible path within My Account */
5
5
  route: string
6
+ /**
7
+ * When set, the page body renders from this CMS content-type.
8
+ * Presence of this field is the single opt-in for a CMS-driven page.
9
+ * Pure CMS vs hybrid is derived by the CLI from whether a code body exists.
10
+ */
11
+ contentType?: string
6
12
  }
7
13
 
8
14
  export type AccountNavigationLabels = Partial<{
@@ -1,4 +1,4 @@
1
- export type MyAccountContentType =
1
+ export type NativeMyAccountContentType =
2
2
  | 'myAccountProfile'
3
3
  | 'myAccountOrders'
4
4
  | 'myAccountOrderDetails'
@@ -6,13 +6,16 @@ export type MyAccountContentType =
6
6
  | 'myAccountSecurity'
7
7
  | 'myAccountUnauthorized'
8
8
 
9
+ /** Store content-types are opaque strings; native literals keep autocompletion. */
10
+ export type MyAccountContentType = NativeMyAccountContentType | (string & {})
11
+
9
12
  export type DefaultMyAccountSection = {
10
13
  name: string
11
14
  $componentKey: string
12
15
  data: Record<string, unknown>
13
16
  }
14
17
 
15
- const DEFAULT_SECTION_KEYS: Record<MyAccountContentType, string[]> = {
18
+ const DEFAULT_SECTION_KEYS: Record<NativeMyAccountContentType, string[]> = {
16
19
  myAccountProfile: ['AccountNavigation', 'AccountProfile'],
17
20
  myAccountOrders: ['AccountNavigation', 'AccountOrdersList'],
18
21
  myAccountOrderDetails: [
@@ -34,7 +37,9 @@ const DEFAULT_SECTION_KEYS: Record<MyAccountContentType, string[]> = {
34
37
  export function getDefaultMyAccountSections(
35
38
  contentType: MyAccountContentType
36
39
  ): DefaultMyAccountSection[] {
37
- return (DEFAULT_SECTION_KEYS[contentType] ?? []).map((key) => ({
40
+ return (
41
+ DEFAULT_SECTION_KEYS[contentType as NativeMyAccountContentType] ?? []
42
+ ).map((key) => ({
38
43
  name: key,
39
44
  $componentKey: key,
40
45
  data: {},
@@ -1,6 +1,16 @@
1
1
  import storeConfig from 'discovery.config'
2
2
  import type { ParsedUrlQuery } from 'node:querystring'
3
3
 
4
+ type MyAccountRedirect = { destination: string; permanent: boolean }
5
+
6
+ /**
7
+ * Discriminated union so callers that guard on `isFaststoreMyAccountEnabled`
8
+ * get a non-nullable `redirect`.
9
+ */
10
+ export type MyAccountRedirectResult =
11
+ | { isFaststoreMyAccountEnabled: false; redirect: MyAccountRedirect }
12
+ | { isFaststoreMyAccountEnabled: true; redirect: null }
13
+
4
14
  /**
5
15
  * Check if the Faststore My Account feature flag is enabled.
6
16
  * If not, redirect to the legacy My Account URL with the query parameters.
@@ -8,10 +18,9 @@ import type { ParsedUrlQuery } from 'node:querystring'
8
18
  * @param {ParsedUrlQuery} query - The query parameters from the request.
9
19
  * @returns {Object} - An object containing the feature flag status and redirect information.
10
20
  */
11
- export function getMyAccountRedirect({ query }: { query: ParsedUrlQuery }): {
12
- isFaststoreMyAccountEnabled: boolean
13
- redirect: { destination: string; permanent: boolean } | null
14
- } {
21
+ export function getMyAccountRedirect({
22
+ query,
23
+ }: { query: ParsedUrlQuery }): MyAccountRedirectResult {
15
24
  const isFaststoreMyAccountEnabled =
16
25
  storeConfig.experimental?.enableFaststoreMyAccount
17
26
 
@@ -29,8 +38,8 @@ export function getMyAccountRedirect({ query }: { query: ParsedUrlQuery }): {
29
38
  permanent: false,
30
39
  }
31
40
 
32
- return { isFaststoreMyAccountEnabled, redirect }
41
+ return { isFaststoreMyAccountEnabled: false, redirect }
33
42
  }
34
43
 
35
- return { isFaststoreMyAccountEnabled, redirect: null }
44
+ return { isFaststoreMyAccountEnabled: true, redirect: null }
36
45
  }
@@ -0,0 +1,221 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+
3
+ const mockValidateUser = vi.fn()
4
+ const mockGetMyAccountRedirect = vi.fn()
5
+ const mockGetIsRepresentative = vi.fn()
6
+ const mockFetchMyAccountPageContent = vi.fn()
7
+ const mockGetGlobalSectionsData = vi.fn()
8
+ const mockExecute = vi.fn()
9
+ const mockInjectGlobalSections = vi.fn()
10
+ const mockLocalizeRedirectDestination = vi.fn(
11
+ (destination: string) => destination
12
+ )
13
+
14
+ vi.mock('src/sdk/account/validateUser', () => ({
15
+ validateUser: (...args: unknown[]) => mockValidateUser(...args),
16
+ }))
17
+
18
+ vi.mock('src/utils/myAccountRedirect', () => ({
19
+ getMyAccountRedirect: (...args: unknown[]) =>
20
+ mockGetMyAccountRedirect(...args),
21
+ }))
22
+
23
+ vi.mock('src/sdk/account/getIsRepresentative', () => ({
24
+ getIsRepresentative: (...args: unknown[]) => mockGetIsRepresentative(...args),
25
+ }))
26
+
27
+ vi.mock('src/server/cms/fetchMyAccountPageContent', () => ({
28
+ fetchMyAccountPageContent: (...args: unknown[]) =>
29
+ mockFetchMyAccountPageContent(...args),
30
+ }))
31
+
32
+ vi.mock('src/components/cms/GlobalSections', () => ({
33
+ getGlobalSectionsData: (...args: unknown[]) =>
34
+ mockGetGlobalSectionsData(...args),
35
+ }))
36
+
37
+ vi.mock('src/server', () => ({
38
+ execute: (...args: unknown[]) => mockExecute(...args),
39
+ }))
40
+
41
+ vi.mock('src/server/cms/global', () => ({
42
+ injectGlobalSections: (...args: unknown[]) =>
43
+ mockInjectGlobalSections(...args),
44
+ }))
45
+
46
+ vi.mock('src/utils/localization/localizeRedirectDestination', () => ({
47
+ localizeRedirectDestination: (...args: unknown[]) =>
48
+ mockLocalizeRedirectDestination(...(args as [string])),
49
+ }))
50
+
51
+ vi.mock('src/utils/localization/withLocaleValidation', () => ({
52
+ withLocaleValidationSSR: <T>(fn: T) => fn,
53
+ }))
54
+
55
+ vi.mock('../../../discovery.config', () => ({
56
+ default: { api: { storeId: 'teststore' } },
57
+ }))
58
+
59
+ vi.mock('@generated/gql', () => ({
60
+ gql: (source: string) => source,
61
+ }))
62
+
63
+ import { myAccountCmsServerSideProps } from '../../../src/experimental/myAccountCmsServerSideProps'
64
+
65
+ function buildContext(overrides: Record<string, unknown> = {}) {
66
+ return {
67
+ previewData: undefined,
68
+ locale: 'en-US',
69
+ query: {},
70
+ req: {
71
+ url: '/pvt/account/wishlist',
72
+ headers: {},
73
+ },
74
+ ...overrides,
75
+ } as never
76
+ }
77
+
78
+ describe('myAccountCmsServerSideProps', () => {
79
+ beforeEach(() => {
80
+ vi.clearAllMocks()
81
+ mockGetIsRepresentative.mockReturnValue(false)
82
+ mockGetGlobalSectionsData.mockReturnValue([
83
+ Promise.resolve({ sections: [], settings: {} }),
84
+ Promise.resolve({ sections: [] }),
85
+ Promise.resolve({ sections: [] }),
86
+ ])
87
+ mockInjectGlobalSections.mockReturnValue({
88
+ sections: [{ name: 'Children', data: {} }],
89
+ settings: {},
90
+ })
91
+ mockExecute.mockResolvedValue({
92
+ data: { accountProfile: { name: 'Ada' } },
93
+ errors: undefined,
94
+ })
95
+ })
96
+
97
+ it('redirects to login when the user is invalid and does not need refresh', async () => {
98
+ mockValidateUser.mockResolvedValue({
99
+ isValid: false,
100
+ needsRefresh: false,
101
+ })
102
+
103
+ const result = await myAccountCmsServerSideProps(
104
+ 'myAccountWishlist',
105
+ '/pvt/account/wishlist'
106
+ )(buildContext())
107
+
108
+ expect(result).toEqual({
109
+ redirect: { destination: '/login', permanent: false },
110
+ })
111
+ })
112
+
113
+ it('redirects to 403 when the user is invalid and needs refresh', async () => {
114
+ mockValidateUser.mockResolvedValue({
115
+ isValid: false,
116
+ needsRefresh: true,
117
+ })
118
+
119
+ const result = await myAccountCmsServerSideProps(
120
+ 'myAccountWishlist',
121
+ '/pvt/account/wishlist'
122
+ )(buildContext())
123
+
124
+ expect(result).toEqual({
125
+ redirect: {
126
+ destination: `/pvt/account/403?from=${encodeURIComponent('/pvt/account/wishlist')}`,
127
+ permanent: false,
128
+ },
129
+ })
130
+ })
131
+
132
+ it('returns the My Account disabled redirect', async () => {
133
+ mockValidateUser.mockResolvedValue({ isValid: true })
134
+ mockGetMyAccountRedirect.mockReturnValue({
135
+ isFaststoreMyAccountEnabled: false,
136
+ redirect: { destination: '/login', permanent: false },
137
+ })
138
+
139
+ const result = await myAccountCmsServerSideProps(
140
+ 'myAccountWishlist',
141
+ '/pvt/account/wishlist'
142
+ )(buildContext())
143
+
144
+ expect(result).toEqual({
145
+ redirect: { destination: '/login', permanent: false },
146
+ })
147
+ })
148
+
149
+ it('returns pageSections, navigationLabels and accountName on success', async () => {
150
+ mockValidateUser.mockResolvedValue({ isValid: true })
151
+ mockGetMyAccountRedirect.mockReturnValue({
152
+ isFaststoreMyAccountEnabled: true,
153
+ redirect: { destination: '/login', permanent: false },
154
+ })
155
+ mockFetchMyAccountPageContent.mockResolvedValue({
156
+ sections: [
157
+ {
158
+ name: 'AccountNavigation',
159
+ $componentKey: 'AccountNavigation',
160
+ data: { profileLabel: 'Profile' },
161
+ },
162
+ {
163
+ name: 'HelloAccount',
164
+ $componentKey: 'HelloAccount',
165
+ data: { label: 'Hi' },
166
+ },
167
+ ],
168
+ settings: {},
169
+ })
170
+
171
+ const result = await myAccountCmsServerSideProps(
172
+ 'myAccountWishlist',
173
+ '/pvt/account/wishlist'
174
+ )(buildContext())
175
+
176
+ expect(result).toMatchObject({
177
+ props: {
178
+ accountName: 'Ada',
179
+ isRepresentative: false,
180
+ navigationLabels: { profileLabel: 'Profile' },
181
+ pageSections: [
182
+ {
183
+ name: 'HelloAccount',
184
+ $componentKey: 'HelloAccount',
185
+ data: { label: 'Hi' },
186
+ },
187
+ ],
188
+ },
189
+ })
190
+ expect(mockFetchMyAccountPageContent).toHaveBeenCalledWith(
191
+ 'myAccountWishlist',
192
+ { previewData: undefined, locale: 'en-US' },
193
+ '/pvt/account/wishlist'
194
+ )
195
+ })
196
+
197
+ it('returns empty pageSections when the content-type is unpublished', async () => {
198
+ mockValidateUser.mockResolvedValue({ isValid: true })
199
+ mockGetMyAccountRedirect.mockReturnValue({
200
+ isFaststoreMyAccountEnabled: true,
201
+ redirect: { destination: '/login', permanent: false },
202
+ })
203
+ mockFetchMyAccountPageContent.mockResolvedValue({
204
+ sections: [],
205
+ settings: {},
206
+ })
207
+
208
+ const result = await myAccountCmsServerSideProps(
209
+ 'myAccountWishlist',
210
+ '/pvt/account/wishlist'
211
+ )(buildContext())
212
+
213
+ expect(result).toMatchObject({
214
+ props: {
215
+ pageSections: [],
216
+ navigationLabels: {},
217
+ accountName: 'Ada',
218
+ },
219
+ })
220
+ })
221
+ })
@@ -73,6 +73,45 @@ describe('getMyAccountRoutes', () => {
73
73
  expect(result.find((r) => r.route === QUOTES_ROUTE)?.title).toBe('Quotes')
74
74
  })
75
75
 
76
+ it('preserves contentType through getMyAccountRoutes', () => {
77
+ const result = getMyAccountRoutes({
78
+ routes: [
79
+ {
80
+ title: 'Wishlist',
81
+ route: '/pvt/account/wishlist',
82
+ contentType: 'myAccountWishlist',
83
+ },
84
+ ],
85
+ })
86
+
87
+ const wishlist = result.find((r) => r.route === '/pvt/account/wishlist')
88
+ expect(wishlist?.contentType).toBe('myAccountWishlist')
89
+ expect(wishlist?.title).toBe('Wishlist')
90
+ })
91
+
92
+ it('keeps custom-route titles when CMS labels override native routes (FR-011)', () => {
93
+ const result = getMyAccountRoutes({
94
+ routes: [
95
+ {
96
+ title: 'Wishlist',
97
+ route: '/pvt/account/wishlist',
98
+ contentType: 'myAccountWishlist',
99
+ },
100
+ ],
101
+ labels: { profileLabel: 'Meu Perfil', ordersLabel: 'Meus Pedidos' },
102
+ })
103
+
104
+ expect(result.find((r) => r.route === PROFILE_ROUTE)?.title).toBe(
105
+ 'Meu Perfil'
106
+ )
107
+ expect(result.find((r) => r.route === ORDERS_ROUTE)?.title).toBe(
108
+ 'Meus Pedidos'
109
+ )
110
+ expect(result.find((r) => r.route === '/pvt/account/wishlist')?.title).toBe(
111
+ 'Wishlist'
112
+ )
113
+ })
114
+
76
115
  it('marks Quotes as a B2B-only route', () => {
77
116
  expect(ROUTES_ONLY_FOR_B2B_MEMBERS).toContain(QUOTES_ROUTE)
78
117
  })
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import {
4
+ getDefaultMyAccountSections,
5
+ withDefaultMyAccountSections,
6
+ } from '../../../src/server/cms/myAccountDefaultSections'
7
+
8
+ describe('getDefaultMyAccountSections', () => {
9
+ it('returns the native section list for myAccountProfile', () => {
10
+ expect(getDefaultMyAccountSections('myAccountProfile')).toEqual([
11
+ {
12
+ name: 'AccountNavigation',
13
+ $componentKey: 'AccountNavigation',
14
+ data: {},
15
+ },
16
+ { name: 'AccountProfile', $componentKey: 'AccountProfile', data: {} },
17
+ ])
18
+ })
19
+
20
+ it('returns an empty list for store (unknown) content-types', () => {
21
+ expect(getDefaultMyAccountSections('myAccountWishlist')).toEqual([])
22
+ })
23
+ })
24
+
25
+ describe('withDefaultMyAccountSections', () => {
26
+ it('returns published sections when present', () => {
27
+ const sections = [
28
+ {
29
+ name: 'HelloAccount',
30
+ $componentKey: 'HelloAccount',
31
+ data: { label: 'Hi' },
32
+ },
33
+ ]
34
+
35
+ expect(withDefaultMyAccountSections('myAccountWishlist', sections)).toEqual(
36
+ sections
37
+ )
38
+ })
39
+
40
+ it('falls back to defaults for native types when sections are empty', () => {
41
+ expect(withDefaultMyAccountSections('myAccountSecurity', [])).toEqual(
42
+ getDefaultMyAccountSections('myAccountSecurity')
43
+ )
44
+ })
45
+
46
+ it('falls back to [] for store types when sections are empty', () => {
47
+ expect(withDefaultMyAccountSections('myAccountWishlist', null)).toEqual([])
48
+ })
49
+ })