@faststore/core 4.9.3-dev.2 → 4.10.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
 
2
- > @faststore/core@4.9.3-dev.1 generate /home/runner/work/faststore/faststore/packages/core
2
+ > @faststore/core@4.9.3-dev.2 generate /home/runner/work/faststore/faststore/packages/core
3
3
  > pnpm run gen-types && pnpm run cache-graphql
4
4
 
5
5
 
6
- > @faststore/core@4.9.3-dev.1 gen-types /home/runner/work/faststore/faststore/packages/core
6
+ > @faststore/core@4.9.3-dev.2 gen-types /home/runner/work/faststore/faststore/packages/core
7
7
  > node ../cli/bin/run generate-types .
8
8
 
9
9
  [STARTED] Parse Configuration
@@ -19,7 +19,7 @@
19
19
  [COMPLETED] Generate to @generated/
20
20
  [COMPLETED] Generate outputs
21
21
 
22
- > @faststore/core@4.9.3-dev.1 cache-graphql /home/runner/work/faststore/faststore/packages/core
22
+ > @faststore/core@4.9.3-dev.2 cache-graphql /home/runner/work/faststore/faststore/packages/core
23
23
  > node ../cli/bin/run cache-graphql --config=./discovery.config.default.js --queries=./@generated/persisted-documents.json
24
24
 
25
25
  [Info] - Config file location: /home/runner/work/faststore/faststore/packages/core/discovery.config.default.js
@@ -1,5 +1,5 @@
1
1
 
2
- > @faststore/core@4.9.3-dev.1 test /home/runner/work/faststore/faststore/packages/core
2
+ > @faststore/core@4.9.3-dev.2 test /home/runner/work/faststore/faststore/packages/core
3
3
  > vitest run
4
4
 
5
5
 
@@ -7,27 +7,32 @@
7
7
 
8
8
  Not implemented: navigation to another Document
9
9
  Not implemented: navigation to another Document
10
+ ✓  browser  test/sdk/ui/useScrollRestoration.browser.test.ts (43 tests) 1434ms
10
11
  Browserslist: browsers data (caniuse-lite) is 8 months old. Please run:
11
12
  npx update-browserslist-db@latest
12
13
  Why you should do it regularly: https://github.com/browserslist/update-db#readme
13
- ✓  browser  test/sdk/ui/useScrollRestoration.browser.test.ts (43 tests) 1011ms
14
- ✓  browser  test/components/account/ContractSwitcher.browser.test.tsx (22 tests) 2790ms
15
- ✓ shows an error state when the list fails to load  760ms
16
- ✓ requires selecting a contract before Confirm is enabled, then switches (REQ-03)  308ms
17
- ✓  browser  test/components/account/cards/MyAccountListCards.browser.test.tsx (16 tests) 3127ms
18
- ✓ shows the tab bar and renders Personal cards by default  1504ms
19
- ✓ paginates when there are more than 25 cards  504ms
20
- ✓  browser  test/components/account/orders/BuyingPolicyAlert.browser.test.tsx (13 tests) 2487ms
21
- ✓ opens the reject confirmation modal with the default English copy  805ms
22
- ✓ opens the reject confirmation modal with CMS-provided labels  387ms
23
- ✓  browser  test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx (13 tests) 457ms
24
- ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (6 tests) 22ms
25
- ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (10 tests) 1931ms
26
- ✓ renders the menu view with contract details and manage footer  1038ms
27
- ✓ opens the contract switcher and returns to the menu on back  359ms
28
- ✓  browser  test/sdk/localization/useBindingSelector.browser.test.tsx (9 tests) 121ms
29
- ✓  browser  test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx (8 tests) 83ms
30
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 92ms
14
+ ✓  browser  test/components/account/cards/MyAccountListCards.browser.test.tsx (16 tests) 2768ms
15
+ ✓ shows the tab bar and renders Personal cards by default  1334ms
16
+ ✓ paginates when there are more than 25 cards  315ms
17
+ ✓  browser  test/components/account/ContractSwitcher.browser.test.tsx (22 tests) 3857ms
18
+ ✓ shows an error state when the list fails to load  966ms
19
+ ✓ requires selecting a contract before Confirm is enabled, then switches (REQ-03)  381ms
20
+ ✓ keeps Confirm disabled when the selected contract is filtered out  377ms
21
+ ✓ honors CMS-provided labels for the header, current session, search, footer, and available count  331ms
22
+ ✓  browser  test/components/account/orders/BuyingPolicyAlert.browser.test.tsx (13 tests) 2749ms
23
+ ✓ opens the reject confirmation modal with the default English copy  884ms
24
+ ✓ opens the reject confirmation modal with CMS-provided labels  430ms
25
+ ✓ shows the default English toast when a policy is rejected  330ms
26
+ ✓  browser  test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx (13 tests) 352ms
27
+ ✓  browser  test/components/ui/RichText/RichText.browser.test.tsx (8 tests) 383ms
28
+ ✓  browser  test/components/account/OrganizationDrawer.browser.test.tsx (10 tests) 2194ms
29
+ ✓ renders the menu view with contract details and manage footer  1242ms
30
+ ✓ opens the contract switcher and returns to the menu on back  388ms
31
+ ✓ resets to the menu when the drawer closes  314ms
32
+ ✓  browser  test/sdk/session/reconcileSessionLocale.browser.test.ts (6 tests) 21ms
33
+ ✓  browser  test/sdk/localization/useBindingSelector.browser.test.tsx (9 tests) 72ms
34
+ ✓  browser  test/components/ui/ProductGallery/GalleryPageHeightLock.browser.test.tsx (8 tests) 66ms
35
+ ✓  browser  test/sdk/recommendations/useRecommendationShelf.browser.test.tsx (12 tests) 162ms
31
36
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx
32
37
  Error in optimistic validation: TypeError: Failed to parse URL from /api/graphql?operationName=ValidateCartMutation&operationHash=3c71b0d3fc2bade14df68abfa271858beb361b12
33
38
   at node:internal/deps/undici/undici:16416:13
@@ -57,10 +62,10 @@ Browserslist: browsers data (caniuse-lite) is 8 months old. Please run:
57
62
  stderr | test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx > RecommendationShelf > renders nothing when there is an error
58
63
  Error fetching recommendations undefined boom for campaign vrn:recommendations:acc:rec-top-items-v2:campaign-1
59
64
 
60
- ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 119ms
61
- ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (7 tests) 9ms
62
- ✓  browser  test/components/CartSidebar/CartSidebar.browser.test.tsx (5 tests) 83ms
63
- ✓  browser  test/components/sections/Account/AccountOrderDetails.browser.test.tsx (4 tests) 69ms
65
+ ✓  browser  test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx (8 tests) 86ms
66
+ ✓  browser  test/components/CartSidebar/CartSidebar.browser.test.tsx (5 tests) 76ms
67
+ ✓  browser  test/sdk/session/getInitialSession.browser.test.ts (7 tests) 19ms
68
+ ✓  browser  test/components/sections/Account/AccountOrderDetails.browser.test.tsx (4 tests) 145ms
64
69
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > posts the password and redirects to the requested return path
65
70
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
66
71
 
@@ -146,8 +151,6 @@ act(() => {
146
151
  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
147
152
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
148
153
 
149
- ✓  browser  test/components/navigation/Navbar.browser.test.tsx (3 tests) 100ms
150
- ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (8 tests) 508ms
151
154
  stderr | test/pages/password-protection.browser.test.tsx > PasswordProtectionLogin > uses the fallback invalid-password message when API omits an error
152
155
  Warning: An update to PasswordProtectionLogin inside a test was not wrapped in act(...).
153
156
 
@@ -292,8 +295,11 @@ act(() => {
292
295
  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
293
296
  at PasswordProtectionLogin (/home/runner/work/faststore/faststore/packages/core/src/pages/password-protection/index.tsx:19:69)
294
297
 
295
- ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 983ms
296
- ✓ renders the password protection form  410ms
298
+ ✓  browser  test/pages/password-protection.browser.test.tsx (6 tests) 932ms
299
+ ✓ renders the password protection form  488ms
300
+ ✓  browser  test/components/navigation/Navbar.browser.test.tsx (3 tests) 70ms
301
+ ✓  browser  test/components/account/OrganizationDrawerFooter.browser.test.tsx (8 tests) 488ms
302
+ ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (5 tests) 45ms
297
303
  stderr | test/components/cms/RenderSections.browser.test.tsx
298
304
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
299
305
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -302,9 +308,9 @@ This ensures that you're testing the behavior the user would see in the browser.
302
308
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
303
309
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
304
310
 
305
- ✓  browser  test/sdk/account/useSwitchContract.browser.test.ts (5 tests) 56ms
306
- ✓  browser  test/components/navigation/NavbarSlider.browser.test.tsx (2 tests) 42ms
307
- ✓  browser  test/components/cms/RenderSections.browser.test.tsx (5 tests) 92ms
311
+ ✓  browser  test/components/navigation/NavbarSlider.browser.test.tsx (2 tests) 31ms
312
+ ✓  browser  test/components/cms/RenderSections.browser.test.tsx (5 tests) 81ms
313
+ ✓  browser  test/components/account/Menu.browser.test.tsx (6 tests) 338ms
308
314
  stderr | test/sdk/localization/store-url.browser.test.ts
309
315
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
310
316
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -313,15 +319,14 @@ This ensures that you're testing the behavior the user would see in the browser.
313
319
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
314
320
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
315
321
 
316
- ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 6ms
317
- ✓  browser  test/components/account/Menu.browser.test.tsx (6 tests) 385ms
318
- ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 42ms
319
- ✓  browser  test/components/templates/SearchPage/SearchWrapper.browser.test.tsx (3 tests) 35ms
320
- ✓  browser  test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx (2 tests) 128ms
321
- ✓  browser  test/components/sections/Account/AccountListQuotes.browser.test.tsx (4 tests) 135ms
322
- ✓  browser  test/components/navigation/NavbarAccountArea.browser.test.tsx (4 tests) 78ms
323
- ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 22ms
324
- ✓  browser  test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx (3 tests) 54ms
322
+ ✓  browser  test/sdk/localization/store-url.browser.test.ts (3 tests) 4ms
323
+ ✓  browser  test/sdk/account/useAvailableContracts.browser.test.ts (6 tests) 37ms
324
+ ✓  browser  test/components/templates/SearchPage/SearchWrapper.browser.test.tsx (3 tests) 36ms
325
+ ✓  browser  test/sdk/recommendations/useRecommendationShelf.sessionFallback.browser.test.tsx (2 tests) 83ms
326
+ ✓  browser  test/components/sections/Account/AccountListQuotes.browser.test.tsx (4 tests) 126ms
327
+ ✓  browser  test/components/navigation/NavbarAccountArea.browser.test.tsx (4 tests) 72ms
328
+ ✓  browser  test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx (3 tests) 73ms
329
+ ✓  browser  test/sdk/account/changeContractToken.browser.test.ts (9 tests) 19ms
325
330
  stderr | test/components/account/doLogout.browser.test.ts
326
331
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
327
332
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -330,9 +335,9 @@ This ensures that you're testing the behavior the user would see in the browser.
330
335
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
331
336
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
332
337
 
333
- ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 39ms
334
- ✓  browser  test/components/ui/LocalizationButton.browser.test.tsx (3 tests) 48ms
335
- ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (5 tests) 359ms
338
+ ✓  browser  test/components/account/doLogout.browser.test.ts (1 test) 25ms
339
+ ✓  browser  test/components/ui/LocalizationButton.browser.test.tsx (3 tests) 56ms
340
+ ✓  browser  test/components/account/OrganizationDrawerHeader.browser.test.tsx (5 tests) 378ms
336
341
  stderr | test/sdk/account/clearPersistedSessionState.browser.test.ts
337
342
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
338
343
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -341,14 +346,15 @@ This ensures that you're testing the behavior the user would see in the browser.
341
346
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
342
347
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
343
348
 
344
- ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (4 tests) 19ms
345
- ✓  browser  test/components/sections/Account/AccountOrderDelivery.browser.test.tsx (2 tests) 102ms
346
- ✓  browser  test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx (2 tests) 104ms
347
- ✓  browser  test/components/sections/Account/AccountOrderStatus.browser.test.tsx (3 tests) 144ms
348
- ✓  browser  test/components/region/useRegion.browser.test.ts (1 test) 17ms
349
- ✓  browser  test/components/sections/Account/AccountListCards.browser.test.tsx (3 tests) 92ms
350
- ✓  browser  test/components/sections/Account/AccountOrdersList.browser.test.tsx (2 tests) 30ms
351
- ✓  browser  test/components/account/orders/SummaryCard.browser.test.tsx (2 tests) 105ms
349
+ ✓  browser  test/sdk/account/clearPersistedSessionState.browser.test.ts (4 tests) 13ms
350
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordion.browser.test.tsx (2 tests) 90ms
351
+ ✓  browser  test/components/sections/Account/AccountOrderDelivery.browser.test.tsx (2 tests) 87ms
352
+ ✓  browser  test/components/sections/Account/AccountOrderStatus.browser.test.tsx (3 tests) 96ms
353
+ ✓  browser  test/components/region/useRegion.browser.test.ts (1 test) 23ms
354
+ ✓  browser  test/components/sections/Account/AccountListCards.browser.test.tsx (3 tests) 72ms
355
+ ✓  browser  test/components/sections/Account/AccountOrdersList.browser.test.tsx (2 tests) 29ms
356
+ ✓  browser  test/components/account/orders/SummaryCard.browser.test.tsx (2 tests) 89ms
357
+ ✓  browser  test/components/account/OrganizationSignInButton.browser.test.tsx (2 tests) 242ms
352
358
  stderr | test/sdk/session/sessionStore.browser.test.ts
353
359
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
354
360
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -357,9 +363,7 @@ This ensures that you're testing the behavior the user would see in the browser.
357
363
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
358
364
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
359
365
 
360
- ✓  browser  test/sdk/session/sessionStore.browser.test.ts (2 tests) 8ms
361
- ✓  browser  test/components/account/OrganizationSignInButton.browser.test.tsx (2 tests) 270ms
362
- ✓  browser  test/components/account/orders/DeliveryOptionAccordionDeliveryInfo.browser.test.tsx (1 test) 31ms
366
+ ✓  browser  test/sdk/session/sessionStore.browser.test.ts (2 tests) 7ms
363
367
  stderr | test/sdk/session/validateSession.browser.test.ts
364
368
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
365
369
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -368,14 +372,15 @@ This ensures that you're testing the behavior the user would see in the browser.
368
372
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
369
373
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
370
374
 
371
- ✓  browser  test/sdk/session/validateSession.browser.test.ts (1 test) 7ms
372
- ✓  browser  test/components/account/orders/StatusCard.browser.test.tsx (2 tests) 107ms
373
- ✓  browser  test/utils/isLocalHost.browser.test.ts (3 tests) 4ms
374
- ✓  browser  test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts (1 test) 3ms
375
- ✓  browser  test/components/account/orders/DeliveryCard.browser.test.tsx (1 test) 63ms
376
- ✓  browser  test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx (1 test) 51ms
377
- ✓  browser  test/components/skeletons/ProductGridSkeleton.browser.test.tsx (2 tests) 45ms
378
- ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 31ms
375
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordionDeliveryInfo.browser.test.tsx (1 test) 62ms
376
+ ✓  browser  test/sdk/session/validateSession.browser.test.ts (1 test) 6ms
377
+ ✓  browser  test/components/account/orders/StatusCard.browser.test.tsx (2 tests) 106ms
378
+ ✓  browser  test/utils/isLocalHost.browser.test.ts (3 tests) 5ms
379
+ ✓  browser  test/sdk/session/reconcileSessionLocale.disabled.browser.test.ts (1 test) 4ms
380
+ ✓  browser  test/components/account/orders/DeliveryCard.browser.test.tsx (1 test) 42ms
381
+ ✓  browser  test/components/account/orders/DeliveryOptionAccordionProducts.browser.test.tsx (1 test) 50ms
382
+ ✓  browser  test/components/skeletons/ProductGridSkeleton.browser.test.tsx (2 tests) 43ms
383
+ ✓  node  test/utils/localization/bindingPaths.test.ts (71 tests) 32ms
379
384
  stderr | test/sdk/account/refreshToken.browser.test.ts
380
385
  Error in optimistic validation: TypeError: Cannot read properties of undefined (reading 'read')
381
386
  at waitForSessionValidated (/home/runner/work/faststore/faststore/packages/core/src/sdk/cart/waitForSessionValidated.ts:16:32)
@@ -384,14 +389,14 @@ This ensures that you're testing the behavior the user would see in the browser.
384
389
  at a (/home/runner/work/faststore/faststore/packages/sdk/dist/es/index.mjs:357:23)
385
390
   at processTicksAndRejections (node:internal/process/task_queues:103:5)
386
391
 
387
- ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 6ms
388
- ✓  node  test/server/password-protection-service.test.ts (22 tests) 37ms
389
- ✓  node  test/pages/api/switch-contract.test.ts (25 tests) 92ms
390
- ✓  browser  test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx (1 test) 230ms
391
- ✓  node  test/utils/match-url.test.ts (10 tests) 9ms
392
+ ✓  browser  test/sdk/account/refreshToken.browser.test.ts (1 test) 5ms
393
+ ✓  node  test/server/password-protection-service.test.ts (22 tests) 36ms
394
+ ✓  node  test/pages/api/switch-contract.test.ts (25 tests) 80ms
395
+ ✓  node  test/utils/match-url.test.ts (10 tests) 12ms
396
+ ✓  browser  test/components/account/orders/FilterFacetPendingApproval.browser.test.tsx (1 test) 224ms
392
397
  ✓  node  test/sdk/localization/useBindingSelector.test.tsx (22 tests) 15ms
393
- ✓  node  test/pages/api/unlock.test.ts (15 tests) 141ms
394
- ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (14 tests) 12ms
398
+ ✓  node  test/pages/api/unlock.test.ts (15 tests) 125ms
399
+ ✓  node  test/sdk/session/syncSalesChannelFromOrderForm.test.ts (14 tests) 16ms
395
400
  stderr | test/components/search/SearchInputComponent.test.tsx
396
401
  Warning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?
397
402
 
@@ -455,24 +460,23 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
455
460
  at Suspense
456
461
  at Wrapper (/home/runner/work/faststore/faststore/packages/core/test/components/search/SearchInputComponent.test.tsx:106:20)
457
462
 
458
- ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 12ms
459
- ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 94ms
460
- ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (15 tests) 474ms
461
- ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 48ms
463
+ ✓  node  test/components/search/SearchInputComponent.test.tsx (6 tests) 86ms
464
+ ✓  node  test/sdk/localization/bindingSelector.test.ts (18 tests) 11ms
465
+ ✓  node  test/components/quotes/MyAccountListQuotesTable.test.tsx (15 tests) 440ms
466
+ ✓  node  test/sdk/search/useSearchHistory.test.ts (13 tests) 43ms
467
+ ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (11 tests) 834ms
468
+ ✓ navigates with the search term on blur  361ms
469
+ ✓ navigates without a label param when the search is cleared  365ms
462
470
  stderr | test/pages/pvt/account/cards/index.test.ts > Cards page getServerSideProps > redirects to /pvt/account/403 when the query fails with 401/403
463
471
  { extensions: { status: 401 } }
464
472
 
465
473
  stderr | test/pages/pvt/account/cards/index.test.ts > Cards page getServerSideProps > renders the error state (no redirect) when the query fails with a non-auth status
466
474
  { extensions: { status: 502 } }
467
475
 
468
- ✓  node  test/pages/pvt/account/cards/index.test.ts (8 tests) 11ms
469
- ✓  node  test/components/quotes/MyAccountListQuotes.test.tsx (11 tests) 832ms
470
- ✓ navigates with the search term on blur  358ms
471
- ✓ navigates without a label param when the search is cleared  360ms
472
- ✓  node  test/components/quotes/quoteFilters.test.ts (26 tests) 29ms
476
+ ✓  node  test/pages/pvt/account/cards/index.test.ts (8 tests) 12ms
477
+ ✓  node  test/components/quotes/quoteFilters.test.ts (26 tests) 38ms
478
+ ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 15ms
473
479
  ✓  node  test/utils/productJsonLd.test.ts (36 tests) 14ms
474
- ✓  node  test/utils/cookieCacheBusting.test.ts (10 tests) 17ms
475
- ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 22ms
476
480
  stderr | test/pages/api/graphql.test.ts > /api/graphql error status propagation > propagates the upstream 400 from a wrapped BadRequestError instead of 500
477
481
  Graphql execution returned with error: [
478
482
  {
@@ -582,73 +586,75 @@ Warning: React does not recognize the `providerProps` prop on a DOM element. If
582
586
  at trace (file:///home/runner/work/faststore/faststore/node_modules/.pnpm/vitest@4.0.18_@opentelemetry+api@1.9.0_@types+node@20.19.13_jiti@2.6.1_jsdom@27.4.0_sas_13be941c57d55f08f06eeaf1c180ddfe/node_modules/vitest/dist/chunks/test.B8ej_ZHS.js:239:21)
583
587
  at runTest (file:///home/runner/work/faststore/faststore/node_modules/.pnpm/@vitest+runner@4.0.18/node_modules/@vitest/runner/dist/index.js:1653:12)
584
588
 
585
- ✓  node  test/pages/api/graphql.test.ts (19 tests) 31ms
586
- ✓  node  test/utils/clearCookies.test.ts (20 tests) 28ms
587
- ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 5ms
589
+ ✓  node  test/pages/api/graphql.test.ts (19 tests) 33ms
590
+ ✓  node  test/experimental/myAccountCmsServerSideProps.test.ts (5 tests) 9ms
591
+ ✓  node  test/utils/clearCookies.test.ts (20 tests) 20ms
592
+ ✓  node  test/utils/multipleTemplates.test.ts (8 tests) 7ms
588
593
  stderr | test/sdk/account/useSetPassword.test.ts > useSetPassword > maps wrongcredentials error responses to the existing message
589
594
  Set password request failed: Bad Request
590
595
 
591
- ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 31ms
592
- ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 43ms
596
+ ✓  node  test/sdk/account/useSetPassword.test.ts (6 tests) 32ms
597
+ ✓  node  test/sdk/orderEntry/useOrderEntryOperation.test.ts (8 tests) 51ms
593
598
  ✓  node  test/utils/localization/localizeRedirectDestination.test.ts (11 tests) 7ms
594
- ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (8 tests) 226ms
595
- ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (14 tests) 190ms
599
+ ✓  node  test/components/quotes/MyAccountQuotesFilterSlider.test.tsx (8 tests) 160ms
596
600
  ✓  node  test/utils/localization/hreflang.test.ts (8 tests) 7ms
597
- ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (13 tests) 8ms
598
- ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (10 tests) 124ms
599
- ✓  node  test/sdk/redirects/index.test.ts (7 tests) 51ms
600
- ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 58ms
601
- ✓  node  test/proxy.localization.test.ts (5 tests) 35ms
602
- ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 49ms
601
+ ✓  node  test/components/quotes/MyAccountQuotesStatusSelector.test.tsx (14 tests) 209ms
602
+ ✓  node  test/sdk/account/getMyAccountRoutes.test.ts (13 tests) 9ms
603
+ ✓  node  test/components/quotes/MyAccountQuotesSelectedTags.test.tsx (10 tests) 106ms
604
+ ✓  node  test/sdk/redirects/index.test.ts (7 tests) 38ms
605
+ ✓  node  test/sdk/orderEntry/useOrderEntryUpload.test.ts (8 tests) 53ms
606
+ ✓  node  test/proxy.localization.test.ts (5 tests) 45ms
607
+ ✓  node  test/sdk/orderEntry/useOrderEntry.test.ts (8 tests) 50ms
608
+ ✓  node  test/sdk/graphql/retryPolicy.test.ts (18 tests) 21ms
603
609
  ✓  node  test/components/account/orders/getDeliveryOptionLabel.test.ts (21 tests) 7ms
604
- ✓  node  test/sdk/graphql/retryPolicy.test.ts (18 tests) 23ms
605
610
  ✓  node  test/server/cms/myAccountDefaultSections.test.ts (9 tests) 8ms
606
- ✓  node  test/utils/productJsonLdRendering.test.tsx (5 tests) 12ms
607
- ✓  node  test/utils/trustedForwardedHost.test.ts (27 tests) 8ms
608
- ✓  node  test/server/index.test.ts (7 tests) 746ms
609
- ✓  node  test/proxy.test.ts (4 tests) 23ms
610
- ✓  node  test/server/webops-api.test.ts (9 tests) 14ms
611
- ✓  node  test/sdk/localization/store-url.test.ts (4 tests) 5ms
612
- ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 77ms
613
- ✓  node  test/components/account/orders/StatusCard.test.ts (3 tests) 32ms
614
- ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 41ms
615
- ✓  node  test/sdk/cart/waitForSessionValidated.test.ts (5 tests) 13ms
616
- ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 12ms
611
+ ✓  node  test/utils/productJsonLdRendering.test.tsx (5 tests) 14ms
612
+ ✓  node  test/utils/trustedForwardedHost.test.ts (27 tests) 6ms
613
+ ✓  node  test/sdk/analytics/useStartRecommendationSession.test.ts (6 tests) 37ms
614
+ ✓  node  test/server/index.test.ts (7 tests) 1350ms
615
+ ✓ should handle options and execute  765ms
616
+ ✓  node  test/sdk/orderEntry/useOrderFormItems.test.ts (7 tests) 50ms
617
+ ✓  node  test/server/webops-api.test.ts (9 tests) 35ms
618
+ ✓  node  test/proxy.test.ts (4 tests) 19ms
619
+ ✓  node  test/components/account/orders/StatusCard.test.ts (3 tests) 30ms
620
+ ✓  node  test/sdk/localization/store-url.test.ts (4 tests) 7ms
621
+ ✓  node  test/sdk/cart/waitForSessionValidated.test.ts (5 tests) 16ms
617
622
  ✓  node  test/server/content/service.test.ts (5 tests) 4ms
623
+ ✓  node  test/sdk/account/refreshToken.test.ts (5 tests) 7ms
618
624
  ✓  node  test/server/cms/global.test.ts (3 tests) 6ms
619
- ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 44ms
620
- ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 5ms
621
- ✓  node  test/components/account/Layout.test.tsx (6 tests) 77ms
622
- ✓  node  test/server/rootSpan.test.ts (4 tests) 48ms
625
+ ✓  node  test/sdk/auth/useAuth.test.ts (5 tests) 29ms
626
+ ✓  node  test/utils/getRequestHostname.test.ts (12 tests) 7ms
627
+ ✓  node  test/components/account/Layout.test.tsx (6 tests) 87ms
628
+ ✓  node  test/server/rootSpan.test.ts (4 tests) 45ms
623
629
  ✓  node  test/pages/api/preview.test.ts (2 tests) 9ms
624
- ✓  node  test/components/search/SearchInput.test.ts (6 tests) 13ms
625
- ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 6ms
626
- ✓  node  test/sdk/recommendations/vrn.test.ts (5 tests) 6ms
627
- ✓  node  test/components/region/useRegion.test.ts (1 test) 24ms
628
- ✓  node  test/server/cms/index.test.ts (2 tests) 4ms
629
- ✓  node  test/utils/retry.test.ts (5 tests) 17ms
630
- ✓  node  test/sdk/account/getB2BSessionClaims.test.ts (4 tests) 7ms
631
- ✓  node  test/sdk/recommendations/useRecommendationUserId.test.ts (4 tests) 194ms
632
- ✓  node  test/utils/userOrderStatus.test.ts (4 tests) 5ms
633
- ✓  node  test/utils/unlockResponse.test.ts (11 tests) 5ms
634
- ✓  node  test/sdk/recommendations/useRecommendations.test.ts (2 tests) 6ms
635
- ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 43ms
630
+ ✓  node  test/components/search/SearchInput.test.ts (6 tests) 7ms
631
+ ✓  node  test/utils/validateSessionRefreshToken.test.ts (6 tests) 4ms
632
+ ✓  node  test/sdk/recommendations/vrn.test.ts (5 tests) 4ms
633
+ ✓  node  test/server/cms/index.test.ts (2 tests) 6ms
634
+ ✓  node  test/components/region/useRegion.test.ts (1 test) 22ms
635
+ ✓  node  test/utils/retry.test.ts (5 tests) 18ms
636
+ ✓  node  test/sdk/account/getB2BSessionClaims.test.ts (4 tests) 6ms
637
+ ✓  node  test/utils/userOrderStatus.test.ts (4 tests) 4ms
638
+ ✓  node  test/sdk/recommendations/useRecommendationUserId.test.ts (4 tests) 198ms
639
+ ✓  node  test/utils/unlockResponse.test.ts (11 tests) 8ms
640
+ ✓  node  test/sdk/recommendations/useRecommendations.test.ts (2 tests) 5ms
641
+ ✓  node  test/components/account/OrganizationDrawerBody.test.tsx (3 tests) 41ms
636
642
  ✓  node  test/utils/isLocalHost.test.ts (7 tests) 5ms
637
643
  ✓  node  test/sdk/session/toSessionInput.test.ts (3 tests) 5ms
638
- ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (4 tests) 49ms
639
- ✓  node  test/components/account/orders/getOrderTotalLabel.test.ts (6 tests) 4ms
640
- ✓  node  test/components/account/orders/listOrdersLabels.test.ts (2 tests) 3ms
641
- ✓  node  test/utils/getCookie.test.ts (4 tests) 5ms
644
+ ✓  node  test/components/quotes/MyAccountQuoteStatusBadge.test.tsx (4 tests) 36ms
645
+ ✓  node  test/components/account/orders/listOrdersLabels.test.ts (2 tests) 4ms
646
+ ✓  node  test/components/account/orders/getOrderTotalLabel.test.ts (6 tests) 3ms
642
647
  ✓  node  test/sdk/session/isSignInAreaResolved.test.ts (3 tests) 4ms
643
- ✓  node  test/utils/filterChannel.test.ts (2 tests) 4ms
644
- ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 43ms
645
- ✓  node  test/instrumentation.test.ts (3 tests) 5ms
646
- ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 3ms
648
+ ✓  node  test/utils/getCookie.test.ts (4 tests) 5ms
649
+ ✓  node  test/utils/filterChannel.test.ts (2 tests) 5ms
650
+ ✓  node  test/components/auth/ProfileChallenge.test.tsx (3 tests) 33ms
651
+ ✓  node  test/sdk/analytics/utils.test.ts (2 tests) 4ms
652
+ ✓  node  test/instrumentation.test.ts (3 tests) 6ms
647
653
  ✓  node  test/components/account/orders/orderDetailsLabels.test.ts (1 test) 4ms
648
- ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 8ms
654
+ ✓  node  test/sdk/account/clearPersistedSessionState.test.ts (1 test) 10ms
649
655
 
650
-  Test Files  130 passed (130)
651
-  Tests  1032 passed (1032)
652
-  Start at  00:35:08
653
-  Duration  62.92s (transform 13.32s, setup 0ms, import 56.50s, tests 21.42s, environment 78.13s)
656
+  Test Files  131 passed (131)
657
+  Tests  1040 passed (1040)
658
+  Start at  16:50:57
659
+  Duration  64.14s (transform 16.34s, setup 0ms, import 57.63s, tests 23.46s, environment 79.54s)
654
660
 
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.10.0-dev.0](https://github.com/vtex/faststore/compare/v4.9.3-dev.2...v4.10.0-dev.0) (2026-10-08)
7
+
8
+ ### Features
9
+
10
+ - render images in RichText via a custom Lexical image node ([#3448](https://github.com/vtex/faststore/issues/3448)) ([a7c85b8](https://github.com/vtex/faststore/commit/a7c85b8478a64994b123f81ee08add594daa7b2f))
11
+
6
12
  ## [4.9.3-dev.2](https://github.com/vtex/faststore/compare/v4.9.3-dev.1...v4.9.3-dev.2) (2026-10-07)
7
13
 
8
14
  ### Bug Fixes
@@ -92,7 +92,7 @@
92
92
  "title": "Privacy Policy Disclaimer",
93
93
  "type": "string",
94
94
  "widget": {
95
- "ui:widget": "draftjs-rich-text"
95
+ "ui:widget": "draftjs-rich-text-v2"
96
96
  }
97
97
  },
98
98
  "emailInputLabel": {
@@ -39,7 +39,7 @@
39
39
  "title": "Privacy Policy Disclaimer",
40
40
  "type": "string",
41
41
  "widget": {
42
- "ui:widget": "draftjs-rich-text"
42
+ "ui:widget": "draftjs-rich-text-v2"
43
43
  }
44
44
  },
45
45
  "emailInputLabel": {
package/index.ts CHANGED
@@ -12,6 +12,9 @@ export * from './src/typings/overrides'
12
12
  export { getOverriddenSection } from './src/sdk/overrides/getOverriddenSection'
13
13
  export { getMyAccountRoutes } from './src/sdk/account/getMyAccountRoutes'
14
14
 
15
+ export { RichText } from './src/components/ui/RichText'
16
+ export type { RichTextProps } from './src/components/ui/RichText'
17
+
15
18
  // Overridable Sections
16
19
  export { default as AlertSection } from './src/components/sections/Alert'
17
20
  export { default as BannerTextSection } from './src/components/sections/BannerText'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faststore/core",
3
- "version": "4.9.3-dev.2",
3
+ "version": "4.10.0-dev.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -71,11 +71,11 @@
71
71
  "style-loader": "^3.3.1",
72
72
  "swr": "^2.2.5",
73
73
  "use-sync-external-store": "^1.6.0",
74
- "@faststore/api": "4.9.3-dev.2",
75
- "@faststore/diagnostics": "4.9.3-dev.2",
76
- "@faststore/lighthouse": "4.9.3-dev.2",
77
- "@faststore/sdk": "4.9.3-dev.2",
78
- "@faststore/ui": "4.9.3-dev.2"
74
+ "@faststore/api": "4.10.0-dev.0",
75
+ "@faststore/diagnostics": "4.10.0-dev.0",
76
+ "@faststore/lighthouse": "4.10.0-dev.0",
77
+ "@faststore/ui": "4.10.0-dev.0",
78
+ "@faststore/sdk": "4.10.0-dev.0"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@cypress/code-coverage": "^3.12.1",
@@ -26,6 +26,7 @@ import {
26
26
  } from 'lexical'
27
27
  import { RichText as UIRichText } from '@faststore/ui'
28
28
  import { isContentPlatformSource } from 'src/server/content/utils'
29
+ import { RichTextImageNode } from './RichTextImageNode'
29
30
 
30
31
  export interface RichTextProps {
31
32
  /**
@@ -124,6 +125,7 @@ function lexicalToHtml(content: string) {
124
125
  ListItemNode,
125
126
  CodeNode,
126
127
  HorizontalRuleNode,
128
+ RichTextImageNode,
127
129
  ],
128
130
  onError: (error) => {
129
131
  throw error
@@ -0,0 +1,163 @@
1
+ import {
2
+ DecoratorNode,
3
+ type DOMExportOutput,
4
+ type EditorConfig,
5
+ type LexicalEditor,
6
+ type NodeKey,
7
+ type SerializedLexicalNode,
8
+ type Spread,
9
+ } from 'lexical'
10
+
11
+ export type ImageAlignment = 'left' | 'center' | 'right'
12
+
13
+ export type SerializedRichTextImageNode = Spread<
14
+ {
15
+ type: 'image'
16
+ version: 1
17
+ src: string
18
+ altText: string
19
+ alignment: ImageAlignment
20
+ widthPercent: number
21
+ },
22
+ SerializedLexicalNode
23
+ >
24
+
25
+ const VALID_ALIGNMENTS = new Set<ImageAlignment>(['left', 'center', 'right'])
26
+ const DEFAULT_ALIGNMENT: ImageAlignment = 'left'
27
+ const DEFAULT_WIDTH_PERCENT = 100
28
+ const RICH_TEXT_CONTENT_MAX_WIDTH = 720
29
+ const DEFAULT_IMAGE_QUALITY = 8
30
+
31
+ function resolveAlignment(alignment: unknown): ImageAlignment {
32
+ return VALID_ALIGNMENTS.has(alignment as ImageAlignment)
33
+ ? (alignment as ImageAlignment)
34
+ : DEFAULT_ALIGNMENT
35
+ }
36
+
37
+ function resolveWidthPercent(widthPercent: unknown): number {
38
+ if (typeof widthPercent !== 'number' || widthPercent <= 0) {
39
+ return DEFAULT_WIDTH_PERCENT
40
+ }
41
+
42
+ return Math.min(widthPercent, DEFAULT_WIDTH_PERCENT)
43
+ }
44
+
45
+ function withVtexResizeParams(src: string, widthPercent: number): string {
46
+ if (!src.includes('vtexassets') || !src.includes('/assets')) {
47
+ return src
48
+ }
49
+
50
+ try {
51
+ const url = new URL(src)
52
+ const targetWidth = Math.max(
53
+ 1,
54
+ Math.round((RICH_TEXT_CONTENT_MAX_WIDTH * widthPercent) / 100)
55
+ )
56
+
57
+ url.searchParams.set('width', targetWidth.toString())
58
+ url.searchParams.set('aspect', 'true')
59
+ url.searchParams.set('quality', DEFAULT_IMAGE_QUALITY.toString())
60
+
61
+ return url.toString()
62
+ } catch {
63
+ return src
64
+ }
65
+ }
66
+
67
+ export class RichTextImageNode extends DecoratorNode<null> {
68
+ __src: string
69
+ __altText: string
70
+ __alignment: ImageAlignment
71
+ __widthPercent: number
72
+
73
+ constructor(
74
+ src: string,
75
+ altText: string,
76
+ alignment: ImageAlignment = DEFAULT_ALIGNMENT,
77
+ widthPercent: number = DEFAULT_WIDTH_PERCENT,
78
+ key?: NodeKey
79
+ ) {
80
+ super(key)
81
+ this.__src = src
82
+ this.__altText = altText
83
+ this.__alignment = resolveAlignment(alignment)
84
+ this.__widthPercent = resolveWidthPercent(widthPercent)
85
+ }
86
+
87
+ static getType(): string {
88
+ return 'image'
89
+ }
90
+
91
+ static clone(node: RichTextImageNode): RichTextImageNode {
92
+ return new RichTextImageNode(
93
+ node.__src,
94
+ node.__altText,
95
+ node.__alignment,
96
+ node.__widthPercent,
97
+ node.__key
98
+ )
99
+ }
100
+
101
+ static importDOM(): null {
102
+ return null
103
+ }
104
+
105
+ static importJSON(
106
+ serializedNode: SerializedRichTextImageNode
107
+ ): RichTextImageNode {
108
+ return new RichTextImageNode(
109
+ typeof serializedNode.src === 'string' ? serializedNode.src : '',
110
+ typeof serializedNode.altText === 'string' ? serializedNode.altText : '',
111
+ serializedNode.alignment,
112
+ serializedNode.widthPercent
113
+ )
114
+ }
115
+
116
+ exportJSON(): SerializedRichTextImageNode {
117
+ return {
118
+ type: 'image',
119
+ version: 1,
120
+ src: this.__src,
121
+ altText: this.__altText,
122
+ alignment: this.__alignment,
123
+ widthPercent: this.__widthPercent,
124
+ }
125
+ }
126
+
127
+ isInline(): boolean {
128
+ return false
129
+ }
130
+
131
+ createDOM(_config: EditorConfig, _editor: LexicalEditor): HTMLElement {
132
+ return document.createElement('div')
133
+ }
134
+
135
+ updateDOM(): boolean {
136
+ return false
137
+ }
138
+
139
+ decorate(): null {
140
+ return null
141
+ }
142
+
143
+ exportDOM(_editor: LexicalEditor): DOMExportOutput {
144
+ if (!this.__src) {
145
+ return { element: null }
146
+ }
147
+
148
+ const wrapper = document.createElement('div')
149
+ wrapper.dataset.fsRichTextImageWrapper = ''
150
+ wrapper.dataset.fsRichTextImageAlignment = this.__alignment
151
+
152
+ const img = document.createElement('img')
153
+ img.dataset.fsRichTextImage = ''
154
+ img.src = withVtexResizeParams(this.__src, this.__widthPercent)
155
+ img.alt = this.__altText
156
+ img.setAttribute('loading', 'lazy')
157
+ img.style.width = `${this.__widthPercent}%`
158
+
159
+ wrapper.append(img)
160
+
161
+ return { element: wrapper }
162
+ }
163
+ }
@@ -0,0 +1,321 @@
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import { render } from '@testing-library/react'
6
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
7
+
8
+ const isContentPlatformSourceMock = vi.hoisted(() => vi.fn(() => true))
9
+
10
+ vi.mock('src/server/content/utils', () => ({
11
+ isContentPlatformSource: isContentPlatformSourceMock,
12
+ }))
13
+
14
+ import { RichText } from '../../../../src/components/ui/RichText/RichText'
15
+
16
+ function textNode(text: string) {
17
+ return {
18
+ detail: 0,
19
+ format: 0,
20
+ mode: 'normal',
21
+ style: '',
22
+ text,
23
+ type: 'text',
24
+ version: 1,
25
+ }
26
+ }
27
+
28
+ function paragraphNode(children: unknown[]) {
29
+ return {
30
+ children,
31
+ direction: 'ltr',
32
+ format: '',
33
+ indent: 0,
34
+ type: 'paragraph',
35
+ version: 1,
36
+ }
37
+ }
38
+
39
+ function headingNode(text: string, tag = 'h2') {
40
+ return {
41
+ children: [textNode(text)],
42
+ direction: 'ltr',
43
+ format: '',
44
+ indent: 0,
45
+ type: 'heading',
46
+ version: 1,
47
+ tag,
48
+ }
49
+ }
50
+
51
+ function linkNode(url: string, text: string) {
52
+ return {
53
+ children: [textNode(text)],
54
+ direction: 'ltr',
55
+ format: '',
56
+ indent: 0,
57
+ type: 'link',
58
+ version: 1,
59
+ rel: null,
60
+ target: null,
61
+ title: null,
62
+ url,
63
+ }
64
+ }
65
+
66
+ function listNode(itemText: string) {
67
+ return {
68
+ children: [
69
+ {
70
+ children: [textNode(itemText)],
71
+ direction: 'ltr',
72
+ format: '',
73
+ indent: 0,
74
+ type: 'listitem',
75
+ version: 1,
76
+ value: 1,
77
+ },
78
+ ],
79
+ direction: 'ltr',
80
+ format: '',
81
+ indent: 0,
82
+ type: 'list',
83
+ version: 1,
84
+ listType: 'bullet',
85
+ start: 1,
86
+ tag: 'ul',
87
+ }
88
+ }
89
+
90
+ function imageNode(overrides: Record<string, unknown> = {}) {
91
+ return {
92
+ type: 'image',
93
+ version: 1,
94
+ src: 'https://storeaccount.vtexassets.com/assets/vtex.file-manager-graphql/images/abc123.png',
95
+ altText: 'A nice picture',
96
+ alignment: 'center',
97
+ widthPercent: 60,
98
+ ...overrides,
99
+ }
100
+ }
101
+
102
+ function lexicalContent(children: unknown[]) {
103
+ return JSON.stringify({
104
+ root: {
105
+ children,
106
+ direction: 'ltr',
107
+ format: '',
108
+ indent: 0,
109
+ type: 'root',
110
+ version: 1,
111
+ },
112
+ })
113
+ }
114
+
115
+ function draftJsContent(text: string) {
116
+ return JSON.stringify({
117
+ blocks: [
118
+ {
119
+ key: '1',
120
+ text,
121
+ type: 'unstyled',
122
+ depth: 0,
123
+ inlineStyleRanges: [],
124
+ entityRanges: [],
125
+ data: {},
126
+ },
127
+ ],
128
+ entityMap: {},
129
+ })
130
+ }
131
+
132
+ describe('RichText', () => {
133
+ beforeEach(() => {
134
+ isContentPlatformSourceMock.mockReturnValue(true)
135
+ })
136
+
137
+ it('renders an image node with the correct src, alt, alignment and width', () => {
138
+ const { container } = render(
139
+ <RichText content={lexicalContent([imageNode()])} />
140
+ )
141
+
142
+ const wrapper = container.querySelector('[data-fs-rich-text-image-wrapper]')
143
+ const img = container.querySelector<HTMLImageElement>(
144
+ '[data-fs-rich-text-image]'
145
+ )
146
+
147
+ expect(wrapper?.getAttribute('data-fs-rich-text-image-alignment')).toBe(
148
+ 'center'
149
+ )
150
+ expect(img?.getAttribute('alt')).toBe('A nice picture')
151
+ expect(img?.getAttribute('loading')).toBe('lazy')
152
+ expect(img?.getAttribute('style')).toBe('width: 60%;')
153
+
154
+ const src = img?.getAttribute('src') ?? ''
155
+ expect(src).toContain('width=432')
156
+ expect(src).toContain('aspect=true')
157
+ expect(src).toContain('quality=8')
158
+ })
159
+
160
+ it('renders images together with text, headings, links and lists in document order', () => {
161
+ const { container } = render(
162
+ <RichText
163
+ content={lexicalContent([
164
+ headingNode('Title'),
165
+ paragraphNode([
166
+ textNode('See '),
167
+ linkNode('https://example.com', 'this link'),
168
+ ]),
169
+ listNode('An item'),
170
+ imageNode(),
171
+ ])}
172
+ />
173
+ )
174
+
175
+ const html = container.innerHTML
176
+
177
+ expect(container.querySelector('h2')?.textContent).toBe('Title')
178
+ expect(
179
+ container.querySelector('a[href="https://example.com"]')
180
+ ).toBeTruthy()
181
+ expect(
182
+ container.querySelector('[data-fs-rich-text-list="true"]')
183
+ ).toBeTruthy()
184
+ expect(container.querySelector('[data-fs-rich-text-image]')).toBeTruthy()
185
+
186
+ const headingIndex = html.indexOf('<h2')
187
+ const linkIndex = html.indexOf('href="https://example.com"')
188
+ const listIndex = html.indexOf('data-fs-rich-text-list')
189
+ const imageIndex = html.indexOf('data-fs-rich-text-image-wrapper')
190
+
191
+ expect(headingIndex).toBeLessThan(linkIndex)
192
+ expect(linkIndex).toBeLessThan(listIndex)
193
+ expect(listIndex).toBeLessThan(imageIndex)
194
+ })
195
+
196
+ it('drops an image node with a missing src without breaking the rest of the content', () => {
197
+ const { container } = render(
198
+ <RichText
199
+ content={lexicalContent([
200
+ paragraphNode([textNode('Before')]),
201
+ imageNode({ src: '' }),
202
+ paragraphNode([textNode('After')]),
203
+ ])}
204
+ />
205
+ )
206
+
207
+ expect(container.querySelector('[data-fs-rich-text-image]')).toBeNull()
208
+ expect(
209
+ container.querySelector('[data-fs-rich-text-image-wrapper]')
210
+ ).toBeNull()
211
+ expect(container.textContent).toContain('Before')
212
+ expect(container.textContent).toContain('After')
213
+ })
214
+
215
+ it('does not throw and skips an image node with an unsupported/unexpected shape', () => {
216
+ expect(() =>
217
+ render(
218
+ <RichText
219
+ content={lexicalContent([
220
+ paragraphNode([textNode('Still here')]),
221
+ {
222
+ type: 'image',
223
+ version: 2,
224
+ imageUrl: 'https://storeaccount.vtexassets.com/assets/x.png',
225
+ },
226
+ ])}
227
+ />
228
+ )
229
+ ).not.toThrow()
230
+
231
+ const { container } = render(
232
+ <RichText
233
+ content={lexicalContent([
234
+ paragraphNode([textNode('Still here')]),
235
+ {
236
+ type: 'image',
237
+ version: 2,
238
+ imageUrl: 'https://storeaccount.vtexassets.com/assets/x.png',
239
+ },
240
+ ])}
241
+ />
242
+ )
243
+
244
+ expect(container.querySelector('[data-fs-rich-text-image]')).toBeNull()
245
+ expect(container.textContent).toContain('Still here')
246
+ })
247
+
248
+ it('renders multiple images, defaulting alignment/width when omitted and passing through non-VTEX URLs unmodified', () => {
249
+ const { container } = render(
250
+ <RichText
251
+ content={lexicalContent([
252
+ imageNode({ alignment: 'right', widthPercent: 30 }),
253
+ imageNode({
254
+ src: 'https://storeaccount.vtexassets.com/assets/vtex.file-manager-graphql/images/no-layout.png',
255
+ alignment: undefined,
256
+ widthPercent: undefined,
257
+ }),
258
+ imageNode({
259
+ src: 'https://cdn.example.com/external.png',
260
+ alignment: 'center',
261
+ widthPercent: 80,
262
+ }),
263
+ ])}
264
+ />
265
+ )
266
+
267
+ const wrappers = container.querySelectorAll(
268
+ '[data-fs-rich-text-image-wrapper]'
269
+ )
270
+ expect(wrappers).toHaveLength(3)
271
+
272
+ expect(wrappers[0].getAttribute('data-fs-rich-text-image-alignment')).toBe(
273
+ 'right'
274
+ )
275
+
276
+ expect(wrappers[1].getAttribute('data-fs-rich-text-image-alignment')).toBe(
277
+ 'left'
278
+ )
279
+ expect(
280
+ wrappers[1]
281
+ .querySelector('[data-fs-rich-text-image]')
282
+ ?.getAttribute('style')
283
+ ).toBe('width: 100%;')
284
+
285
+ const externalImg = wrappers[2].querySelector('[data-fs-rich-text-image]')
286
+ expect(externalImg?.getAttribute('src')).toBe(
287
+ 'https://cdn.example.com/external.png'
288
+ )
289
+ })
290
+
291
+ it('clamps widthPercent to 100 so the image never overflows its container', () => {
292
+ const { container } = render(
293
+ <RichText content={lexicalContent([imageNode({ widthPercent: 150 })])} />
294
+ )
295
+
296
+ const img = container.querySelector('[data-fs-rich-text-image]')
297
+ expect(img?.getAttribute('style')).toBe('width: 100%;')
298
+ })
299
+
300
+ it('keeps rendering existing hCMS Draft.js content unaffected', () => {
301
+ isContentPlatformSourceMock.mockReturnValue(false)
302
+
303
+ const { container } = render(
304
+ <RichText content={draftJsContent('Hello legacy content')} />
305
+ )
306
+
307
+ expect(container.textContent).toContain('Hello legacy content')
308
+ expect(container.querySelector('[data-fs-rich-text-image]')).toBeNull()
309
+ })
310
+
311
+ it('keeps rendering existing Lexical content without images unaffected', () => {
312
+ const { container } = render(
313
+ <RichText
314
+ content={lexicalContent([paragraphNode([textNode('No images here')])])}
315
+ />
316
+ )
317
+
318
+ expect(container.textContent).toContain('No images here')
319
+ expect(container.querySelector('[data-fs-rich-text-image]')).toBeNull()
320
+ })
321
+ })