@capillarytech/creatives-library 8.0.242-alpha.1 → 8.0.242-alpha.11

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.
Files changed (255) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/config/app.js +1 -1
  4. package/constants/unified.js +2 -2
  5. package/initialReducer.js +0 -2
  6. package/package.json +1 -1
  7. package/services/api.js +5 -10
  8. package/services/tests/api.test.js +0 -18
  9. package/translations/en.json +4 -3
  10. package/utils/common.js +6 -5
  11. package/utils/commonUtils.js +1 -14
  12. package/utils/imageUrlUpload.js +141 -0
  13. package/utils/tests/commonUtil.test.js +0 -224
  14. package/utils/transformTemplateConfig.js +10 -0
  15. package/v2Components/CapDeviceContent/index.js +56 -61
  16. package/v2Components/CapImageUpload/constants.js +2 -0
  17. package/v2Components/CapImageUpload/index.js +65 -16
  18. package/v2Components/CapImageUpload/index.scss +4 -1
  19. package/v2Components/CapImageUpload/messages.js +5 -1
  20. package/v2Components/CapImageUrlUpload/constants.js +26 -0
  21. package/v2Components/CapImageUrlUpload/index.js +365 -0
  22. package/v2Components/CapImageUrlUpload/index.scss +35 -0
  23. package/v2Components/CapImageUrlUpload/messages.js +47 -0
  24. package/v2Components/CapTagList/index.js +1 -6
  25. package/v2Components/CapTagListWithInput/index.js +1 -5
  26. package/v2Components/CapTagListWithInput/messages.js +1 -1
  27. package/v2Components/CapWhatsappCTA/tests/index.test.js +0 -5
  28. package/v2Components/ErrorInfoNote/index.js +72 -412
  29. package/v2Components/ErrorInfoNote/messages.js +0 -22
  30. package/v2Components/ErrorInfoNote/style.scss +2 -279
  31. package/v2Components/HtmlEditor/HTMLEditor.js +89 -210
  32. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +133 -1132
  33. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +12 -17
  34. package/v2Components/HtmlEditor/_htmlEditor.scss +23 -8
  35. package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
  36. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +101 -13
  37. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +139 -148
  38. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +1 -2
  39. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  40. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
  41. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +0 -1
  42. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +7 -4
  43. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +45 -35
  44. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +3 -1
  45. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  46. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +6 -7
  47. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +6 -3
  48. package/v2Components/HtmlEditor/components/PreviewPane/index.js +11 -10
  49. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  50. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +62 -87
  51. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +31 -49
  52. package/v2Components/HtmlEditor/constants.js +20 -29
  53. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +16 -373
  54. package/v2Components/HtmlEditor/hooks/useEditorContent.js +2 -5
  55. package/v2Components/HtmlEditor/hooks/useInAppContent.js +146 -88
  56. package/v2Components/HtmlEditor/index.js +1 -1
  57. package/v2Components/HtmlEditor/messages.js +85 -95
  58. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +101 -99
  59. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +25 -23
  60. package/v2Components/HtmlEditor/utils/validationAdapter.js +41 -34
  61. package/v2Components/MobilePushPreviewV2/index.js +7 -32
  62. package/v2Components/TemplatePreview/_templatePreview.scss +24 -44
  63. package/v2Components/TemplatePreview/index.js +32 -47
  64. package/v2Components/TemplatePreview/messages.js +0 -4
  65. package/v2Components/TestAndPreviewSlidebox/index.js +25 -31
  66. package/v2Containers/App/constants.js +5 -0
  67. package/v2Containers/BeeEditor/index.js +80 -82
  68. package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +4 -3
  69. package/v2Containers/CreativesContainer/SlideBoxContent.js +118 -148
  70. package/v2Containers/CreativesContainer/SlideBoxFooter.js +3 -9
  71. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -2
  72. package/v2Containers/CreativesContainer/constants.js +2 -1
  73. package/v2Containers/CreativesContainer/index.js +41 -173
  74. package/v2Containers/CreativesContainer/messages.js +4 -4
  75. package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +210 -0
  76. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +354 -38
  77. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +0 -36
  78. package/v2Containers/Email/actions.js +0 -7
  79. package/v2Containers/Email/constants.js +1 -5
  80. package/v2Containers/Email/index.js +0 -13
  81. package/v2Containers/Email/messages.js +0 -32
  82. package/v2Containers/Email/reducer.js +1 -12
  83. package/v2Containers/Email/sagas.js +6 -41
  84. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +0 -2
  85. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +7 -193
  86. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +74 -40
  87. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +67 -2
  88. package/v2Containers/EmailWrapper/constants.js +0 -2
  89. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +67 -436
  90. package/v2Containers/EmailWrapper/index.js +23 -99
  91. package/v2Containers/EmailWrapper/messages.js +1 -61
  92. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +1 -26
  93. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +77 -111
  94. package/v2Containers/InApp/actions.js +0 -7
  95. package/v2Containers/InApp/constants.js +4 -20
  96. package/v2Containers/InApp/index.js +357 -800
  97. package/v2Containers/InApp/index.scss +3 -4
  98. package/v2Containers/InApp/messages.js +3 -7
  99. package/v2Containers/InApp/reducer.js +3 -21
  100. package/v2Containers/InApp/sagas.js +9 -29
  101. package/v2Containers/InApp/selectors.js +5 -25
  102. package/v2Containers/InApp/tests/index.test.js +50 -154
  103. package/v2Containers/InApp/tests/reducer.test.js +0 -34
  104. package/v2Containers/InApp/tests/sagas.test.js +9 -61
  105. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +12 -12
  106. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +8 -8
  107. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +100 -77
  108. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +72 -63
  109. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +184 -150
  110. package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +16 -12
  111. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +32 -28
  112. package/v2Containers/TagList/index.js +1 -67
  113. package/v2Containers/Templates/ChannelTypeIllustration.js +13 -1
  114. package/v2Containers/Templates/_templates.scss +202 -56
  115. package/v2Containers/Templates/actions.js +2 -1
  116. package/v2Containers/Templates/constants.js +1 -0
  117. package/v2Containers/Templates/index.js +278 -128
  118. package/v2Containers/Templates/messages.js +24 -4
  119. package/v2Containers/Templates/reducer.js +2 -0
  120. package/v2Containers/Templates/tests/index.test.js +10 -0
  121. package/v2Containers/TemplatesV2/index.js +8 -1
  122. package/v2Containers/TemplatesV2/messages.js +4 -0
  123. package/v2Containers/WebPush/Create/components/BrandIconSection.js +108 -0
  124. package/v2Containers/WebPush/Create/components/ButtonForm.js +172 -0
  125. package/v2Containers/WebPush/Create/components/ButtonItem.js +101 -0
  126. package/v2Containers/WebPush/Create/components/ButtonList.js +145 -0
  127. package/v2Containers/WebPush/Create/components/ButtonsLinksSection.js +164 -0
  128. package/v2Containers/WebPush/Create/components/ButtonsLinksSection.test.js +463 -0
  129. package/v2Containers/WebPush/Create/components/FormActions.js +54 -0
  130. package/v2Containers/WebPush/Create/components/FormActions.test.js +163 -0
  131. package/v2Containers/WebPush/Create/components/MediaSection.js +142 -0
  132. package/v2Containers/WebPush/Create/components/MediaSection.test.js +341 -0
  133. package/v2Containers/WebPush/Create/components/MessageSection.js +103 -0
  134. package/v2Containers/WebPush/Create/components/MessageSection.test.js +268 -0
  135. package/v2Containers/WebPush/Create/components/NotificationTitleSection.js +87 -0
  136. package/v2Containers/WebPush/Create/components/NotificationTitleSection.test.js +210 -0
  137. package/v2Containers/WebPush/Create/components/TemplateNameSection.js +54 -0
  138. package/v2Containers/WebPush/Create/components/TemplateNameSection.test.js +143 -0
  139. package/v2Containers/WebPush/Create/components/__snapshots__/ButtonsLinksSection.test.js.snap +86 -0
  140. package/v2Containers/WebPush/Create/components/__snapshots__/FormActions.test.js.snap +16 -0
  141. package/v2Containers/WebPush/Create/components/__snapshots__/MediaSection.test.js.snap +41 -0
  142. package/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +54 -0
  143. package/v2Containers/WebPush/Create/components/__snapshots__/NotificationTitleSection.test.js.snap +37 -0
  144. package/v2Containers/WebPush/Create/components/__snapshots__/TemplateNameSection.test.js.snap +21 -0
  145. package/v2Containers/WebPush/Create/components/_buttons.scss +246 -0
  146. package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +554 -0
  147. package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +607 -0
  148. package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +633 -0
  149. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +666 -0
  150. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +74 -0
  151. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +78 -0
  152. package/v2Containers/WebPush/Create/hooks/useButtonManagement.js +138 -0
  153. package/v2Containers/WebPush/Create/hooks/useButtonManagement.test.js +406 -0
  154. package/v2Containers/WebPush/Create/hooks/useCharacterCount.js +30 -0
  155. package/v2Containers/WebPush/Create/hooks/useCharacterCount.test.js +151 -0
  156. package/v2Containers/WebPush/Create/hooks/useImageUpload.js +104 -0
  157. package/v2Containers/WebPush/Create/hooks/useImageUpload.test.js +538 -0
  158. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +122 -0
  159. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +633 -0
  160. package/v2Containers/WebPush/Create/index.js +1056 -0
  161. package/v2Containers/WebPush/Create/index.scss +134 -0
  162. package/v2Containers/WebPush/Create/messages.js +203 -0
  163. package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +228 -0
  164. package/v2Containers/WebPush/Create/preview/NotificationContainer.js +294 -0
  165. package/v2Containers/WebPush/Create/preview/PreviewContent.js +90 -0
  166. package/v2Containers/WebPush/Create/preview/PreviewControls.js +305 -0
  167. package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +23 -0
  168. package/v2Containers/WebPush/Create/preview/WebPushPreview.js +150 -0
  169. package/v2Containers/WebPush/Create/preview/assets/Light.svg +53 -0
  170. package/v2Containers/WebPush/Create/preview/assets/Top.svg +5 -0
  171. package/v2Containers/WebPush/Create/preview/assets/android-arrow-down.svg +9 -0
  172. package/v2Containers/WebPush/Create/preview/assets/android-arrow-up.svg +9 -0
  173. package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
  174. package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
  175. package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +106 -0
  176. package/v2Containers/WebPush/Create/preview/assets/iOS.svg +26 -0
  177. package/v2Containers/WebPush/Create/preview/assets/macos-arrow-down-icon.svg +9 -0
  178. package/v2Containers/WebPush/Create/preview/assets/macos-triple-dot-icon.svg +9 -0
  179. package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +18 -0
  180. package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +29 -0
  181. package/v2Containers/WebPush/Create/preview/assets/windows-close-icon.svg +9 -0
  182. package/v2Containers/WebPush/Create/preview/assets/windows-triple-dot-icon.svg +9 -0
  183. package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +47 -0
  184. package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +141 -0
  185. package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +45 -0
  186. package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +68 -0
  187. package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +61 -0
  188. package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +99 -0
  189. package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +733 -0
  190. package/v2Containers/WebPush/Create/preview/components/tests/WindowsChromeExpanded.test.js +571 -0
  191. package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +81 -0
  192. package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/WindowsChromeExpanded.test.js.snap +81 -0
  193. package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +50 -0
  194. package/v2Containers/WebPush/Create/preview/constants.js +637 -0
  195. package/v2Containers/WebPush/Create/preview/notification-container.scss +79 -0
  196. package/v2Containers/WebPush/Create/preview/preview.scss +351 -0
  197. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +370 -0
  198. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +12 -0
  199. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +12 -0
  200. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +12 -0
  201. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +47 -0
  202. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +11 -0
  203. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +11 -0
  204. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +11 -0
  205. package/v2Containers/WebPush/Create/preview/styles/_base.scss +207 -0
  206. package/v2Containers/WebPush/Create/preview/styles/_ios.scss +153 -0
  207. package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +107 -0
  208. package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +101 -0
  209. package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +229 -0
  210. package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +909 -0
  211. package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +1081 -0
  212. package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +723 -0
  213. package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +943 -0
  214. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +131 -0
  215. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +112 -0
  216. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +144 -0
  217. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +129 -0
  218. package/v2Containers/WebPush/Create/utils/payloadBuilder.js +94 -0
  219. package/v2Containers/WebPush/Create/utils/payloadBuilder.test.js +390 -0
  220. package/v2Containers/WebPush/Create/utils/previewUtils.js +89 -0
  221. package/v2Containers/WebPush/Create/utils/urlValidation.js +115 -0
  222. package/v2Containers/WebPush/Create/utils/urlValidation.test.js +449 -0
  223. package/v2Containers/WebPush/Create/utils/validation.js +75 -0
  224. package/v2Containers/WebPush/Create/utils/validation.test.js +283 -0
  225. package/v2Containers/WebPush/actions.js +60 -0
  226. package/v2Containers/WebPush/constants.js +128 -0
  227. package/v2Containers/WebPush/index.js +2 -0
  228. package/v2Containers/WebPush/reducer.js +104 -0
  229. package/v2Containers/WebPush/sagas.js +119 -0
  230. package/v2Containers/WebPush/selectors.js +65 -0
  231. package/v2Containers/WebPush/tests/reducer.test.js +863 -0
  232. package/v2Containers/WebPush/tests/sagas.test.js +566 -0
  233. package/v2Containers/WebPush/tests/selectors.test.js +843 -0
  234. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +528 -431
  235. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +0 -254
  236. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +0 -362
  237. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +0 -51
  238. package/v2Containers/BeePopupEditor/constants.js +0 -10
  239. package/v2Containers/BeePopupEditor/index.js +0 -193
  240. package/v2Containers/BeePopupEditor/tests/index.test.js +0 -627
  241. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +0 -1045
  242. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +0 -376
  243. package/v2Containers/InApp/__tests__/sagas.test.js +0 -363
  244. package/v2Containers/InApp/tests/selectors.test.js +0 -612
  245. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +0 -162
  246. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +0 -267
  247. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +0 -9
  248. package/v2Containers/InAppWrapper/constants.js +0 -16
  249. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +0 -473
  250. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +0 -198
  251. package/v2Containers/InAppWrapper/index.js +0 -148
  252. package/v2Containers/InAppWrapper/messages.js +0 -49
  253. package/v2Containers/InappAdvance/index.js +0 -1099
  254. package/v2Containers/InappAdvance/index.scss +0 -10
  255. package/v2Containers/InappAdvance/tests/index.test.js +0 -448
@@ -5,51 +5,33 @@
5
5
  */
6
6
 
7
7
  import React from 'react';
8
- import {
9
- render, screen, fireEvent, act, waitFor,
10
- } from '@testing-library/react';
8
+ import { render, screen, fireEvent, act, waitFor } from '@testing-library/react';
11
9
  import '@testing-library/jest-dom';
12
10
  import { IntlProvider } from 'react-intl';
13
11
  import HTMLEditor from '../HTMLEditor';
14
12
 
15
- // Options to control CodeEditorPane mock behavior
16
- const mockCodeEditorOptions = {
17
- includeInsertText: true,
18
- insertTextThrows: false,
19
- setRef: true,
20
- navigateToLineThrows: false,
21
- includeNavigateToLine: true,
22
- };
23
-
24
-
25
13
  // Mock useLayoutEffect to behave like useEffect in tests
26
14
  React.useLayoutEffect = React.useEffect;
27
15
 
28
16
  // Mock browser APIs
29
17
  global.IntersectionObserver = class IntersectionObserver {
30
- constructor() { }
31
-
32
- disconnect() { }
33
-
34
- observe() { }
35
-
36
- unobserve() { }
18
+ constructor() {}
19
+ disconnect() {}
20
+ observe() {}
21
+ unobserve() {}
37
22
  };
38
23
 
39
24
  global.ResizeObserver = class ResizeObserver {
40
- constructor() { }
41
-
42
- disconnect() { }
43
-
44
- observe() { }
45
-
46
- unobserve() { }
25
+ constructor() {}
26
+ disconnect() {}
27
+ observe() {}
28
+ unobserve() {}
47
29
  };
48
30
 
49
31
  // Setup window.matchMedia mock
50
32
  Object.defineProperty(window, 'matchMedia', {
51
33
  writable: true,
52
- value: jest.fn().mockImplementation((query) => ({
34
+ value: jest.fn().mockImplementation(query => ({
53
35
  matches: false,
54
36
  media: query,
55
37
  onchange: null,
@@ -106,97 +88,60 @@ window.getComputedStyle = jest.fn(() => ({
106
88
  jest.mock('@capillarytech/cap-ui-library/CapNotification', () => ({
107
89
  warning: jest.fn(),
108
90
  error: jest.fn(),
109
- success: jest.fn(),
91
+ success: jest.fn()
110
92
  }));
111
93
 
112
94
  // Mock components
113
- jest.mock('../components/EditorToolbar', () => function MockEditorToolbar(props) {
114
- return (
115
- <div data-testid="editor-toolbar">
116
- <button onClick={props.onToggleFullscreen}>Toggle Fullscreen</button>
117
- <button onClick={() => props.onLabelInsert && props.onLabelInsert('test-label', 10)}>
95
+ jest.mock('../components/EditorToolbar', () => {
96
+ return function MockEditorToolbar(props) {
97
+ return (
98
+ <div data-testid="editor-toolbar">
99
+ <button onClick={props.onToggleFullscreen}>Toggle Fullscreen</button>
100
+ <button onClick={() => props.onLabelInsert && props.onLabelInsert('test-label', 10)}>
118
101
  Insert Label
119
- </button>
120
- <button onClick={() => props.onLabelInsert && props.onLabelInsert('test-label', null)}>
121
- Insert Label (Null Position)
122
- </button>
123
- <button onClick={() => props.onSave && props.onSave()}>
102
+ </button>
103
+ <button onClick={() => props.onSave && props.onSave()}>
124
104
  Save
125
- </button>
126
- </div>
127
- );
105
+ </button>
106
+ </div>
107
+ );
108
+ };
128
109
  });
129
110
 
130
- jest.mock('../components/DeviceToggle', () => function MockDeviceToggle(props) {
131
- return (
132
- <div data-testid="device-toggle">
133
- <button onClick={() => props.onDeviceChange && props.onDeviceChange('android')}>
111
+ jest.mock('../components/DeviceToggle', () => {
112
+ return function MockDeviceToggle(props) {
113
+ return (
114
+ <div data-testid="device-toggle">
115
+ <button onClick={() => props.onDeviceChange && props.onDeviceChange('android')}>
134
116
  Android
135
- </button>
136
- <button onClick={() => props.onDeviceChange && props.onDeviceChange('ios')}>
117
+ </button>
118
+ <button onClick={() => props.onDeviceChange && props.onDeviceChange('ios')}>
137
119
  iOS
138
- </button>
139
- </div>
140
- );
120
+ </button>
121
+ </div>
122
+ );
123
+ };
141
124
  });
142
125
 
143
- jest.mock('../components/SplitContainer', () => function MockSplitContainer({ children }) {
144
- return <div data-testid="split-container">{children}</div>;
126
+ jest.mock('../components/SplitContainer', () => {
127
+ return function MockSplitContainer({ children }) {
128
+ return <div data-testid="split-container">{children}</div>;
129
+ };
145
130
  });
146
131
 
147
-
148
132
  jest.mock('../components/CodeEditorPane', () => {
149
133
  const React = require('react');
150
- const { useEditorContext } = require('../components/common/EditorContext');
151
- return React.forwardRef((props, ref) => {
134
+ return React.forwardRef(function MockCodeEditorPane(props, ref) {
152
135
  const [value, setValue] = React.useState('');
153
- // Get validation from context
154
- let validation = null;
155
- try {
156
- const context = useEditorContext();
157
- validation = context?.validation;
158
- } catch (e) {
159
- // Context not available, use props or null
160
- }
161
-
162
- React.useImperativeHandle(ref, () => {
163
- if (!mockCodeEditorOptions.setRef) {
164
- return null;
165
- }
166
-
167
- const methods = {
168
- focus: jest.fn(),
169
- getCursor: jest.fn(() => 0),
170
- getValue: jest.fn(() => value),
171
- setValue: jest.fn((newValue) => setValue(newValue)),
172
- };
173
-
174
- if (mockCodeEditorOptions.includeNavigateToLine) {
175
- methods.navigateToLine = jest.fn(() => {
176
- if (mockCodeEditorOptions.navigateToLineThrows) {
177
- throw new Error('Navigation failed');
178
- }
179
- });
180
- }
181
-
182
- if (mockCodeEditorOptions.includeInsertText) {
183
- methods.insertText = jest.fn(() => {
184
- if (mockCodeEditorOptions.insertTextThrows) {
185
- throw new Error('Insert failed');
186
- }
187
- });
188
- }
189
-
190
- return methods;
191
- });
192
136
 
193
- // Import ValidationErrorDisplay mock - use dynamic require to get the mocked version
194
- let ValidationErrorDisplay;
195
- try {
196
- ValidationErrorDisplay = require('../components/ValidationErrorDisplay');
197
- } catch (e) {
198
- ValidationErrorDisplay = () => null;
199
- }
137
+ React.useImperativeHandle(ref, () => ({
138
+ insertText: jest.fn(),
139
+ focus: jest.fn(),
140
+ getCursor: jest.fn(() => 0),
141
+ getValue: jest.fn(() => value),
142
+ setValue: jest.fn((newValue) => setValue(newValue)),
143
+ navigateToLine: jest.fn(),
144
+ }));
200
145
 
201
146
  return (
202
147
  <div data-testid="code-editor-pane">
@@ -210,47 +155,36 @@ jest.mock('../components/CodeEditorPane', () => {
210
155
  }}
211
156
  data-testid="editor-textarea"
212
157
  />
213
- <button
214
- onClick={() => props.onContextChange && props.onContextChange('test-context')}
215
- data-testid="trigger-context-change"
216
- >
217
- Trigger Context Change
218
- </button>
219
- {validation && (
220
- <ValidationErrorDisplay
221
- validation={validation}
222
- onErrorClick={props.onErrorClick}
223
- />
224
- )}
225
158
  </div>
226
159
  );
227
160
  });
228
161
  });
229
162
 
230
- jest.mock('../components/PreviewPane', () => function MockPreviewPane(props) {
231
- return (
232
- <div data-testid="preview-pane" data-fullscreen={props.isFullscreenMode}>
163
+ jest.mock('../components/PreviewPane', () => {
164
+ return function MockPreviewPane(props) {
165
+ return (
166
+ <div data-testid="preview-pane" data-fullscreen={props.isFullscreenMode}>
233
167
  Preview Content
234
- </div>
235
- );
168
+ </div>
169
+ );
170
+ };
236
171
  });
237
172
 
238
- jest.mock('../components/ValidationErrorDisplay', () => function MockValidationErrorDisplay({ validation, onErrorClick }) {
239
- // Match actual component behavior: check if validation has errors
240
- if (!validation || validation.isValidating) return null;
241
- const allIssues = validation.getAllIssues?.() || [];
242
- if (allIssues.length === 0) return null;
243
- return (
244
- <div data-testid="validation-error-display">
245
- <div>Validation Errors</div>
246
- <button onClick={() => onErrorClick && onErrorClick({ line: 5, column: 10 })}>
173
+ jest.mock('../components/ValidationErrorDisplay', () => {
174
+ return function MockValidationErrorDisplay({ validation, onErrorClick }) {
175
+ if (!validation || validation.isClean?.()) return null;
176
+ return (
177
+ <div data-testid="validation-error-display">
178
+ <div>Validation Errors</div>
179
+ <button onClick={() => onErrorClick && onErrorClick({ line: 5, column: 10 })}>
247
180
  Error at Line 5
248
- </button>
249
- <button onClick={() => onErrorClick && onErrorClick({ line: null })}>
181
+ </button>
182
+ <button onClick={() => onErrorClick && onErrorClick({ line: null })}>
250
183
  Error without Line
251
- </button>
252
- </div>
253
- );
184
+ </button>
185
+ </div>
186
+ );
187
+ };
254
188
  });
255
189
 
256
190
  // Mock hooks - Return complete hook objects with all required methods
@@ -263,8 +197,8 @@ jest.mock('../hooks/useEditorContent', () => ({
263
197
  isLoading: false,
264
198
  isDirty: false,
265
199
  hasContent: true,
266
- getContentSize: jest.fn(() => 20),
267
- }),
200
+ getContentSize: jest.fn(() => 20)
201
+ })
268
202
  }));
269
203
 
270
204
  jest.mock('../hooks/useInAppContent', () => ({
@@ -272,7 +206,7 @@ jest.mock('../hooks/useInAppContent', () => ({
272
206
  content: '<p>Android content</p>',
273
207
  deviceContent: {
274
208
  android: '<p>Android content</p>',
275
- ios: '<p>iOS content</p>',
209
+ ios: '<p>iOS content</p>'
276
210
  },
277
211
  activeDevice: 'android',
278
212
  keepContentSame: false,
@@ -286,8 +220,8 @@ jest.mock('../hooks/useInAppContent', () => ({
286
220
  getContentSize: () => 20,
287
221
  isLoading: false,
288
222
  isDirty: false,
289
- hasContent: true,
290
- }),
223
+ hasContent: true
224
+ })
291
225
  }));
292
226
 
293
227
  jest.mock('../hooks/useLayoutState', () => ({
@@ -319,8 +253,8 @@ jest.mock('../hooks/useLayoutState', () => ({
319
253
  // Layout constraints
320
254
  minPaneSize: 20,
321
255
  maxPaneSize: 80,
322
- gutterSize: 10,
323
- }),
256
+ gutterSize: 10
257
+ })
324
258
  }));
325
259
 
326
260
  // Mock useValidation - need to use a variable that gets assigned in the factory
@@ -331,11 +265,11 @@ jest.mock('../hooks/useValidation', () => {
331
265
  isValidating: false,
332
266
  getAllIssues: () => [],
333
267
  isClean: () => true,
334
- summary: { totalErrors: 0, totalWarnings: 0 },
268
+ summary: { totalErrors: 0, totalWarnings: 0 }
335
269
  }));
336
270
 
337
271
  return {
338
- useValidation: (...args) => mockUseValidationImpl(...args),
272
+ useValidation: (...args) => mockUseValidationImpl(...args)
339
273
  };
340
274
  });
341
275
 
@@ -363,7 +297,7 @@ describe('HTMLEditor', () => {
363
297
  initialContent: '<p>Initial content</p>',
364
298
  onChange: jest.fn(),
365
299
  onSave: jest.fn(),
366
- variant: 'email',
300
+ variant: 'email'
367
301
  };
368
302
 
369
303
  describe('Loading State', () => {
@@ -433,7 +367,7 @@ describe('HTMLEditor', () => {
433
367
 
434
368
  // Context should reflect fullscreen mode change
435
369
  const previewPanes = screen.getAllByTestId('preview-pane');
436
- expect(previewPanes.some((pane) => pane.getAttribute('data-fullscreen') === 'true')).toBe(true);
370
+ expect(previewPanes.some(pane => pane.getAttribute('data-fullscreen') === 'true')).toBe(true);
437
371
  });
438
372
  });
439
373
 
@@ -539,44 +473,10 @@ describe('HTMLEditor', () => {
539
473
  expect(screen.getByTestId('device-toggle')).toBeInTheDocument();
540
474
  });
541
475
 
542
- it('converts string initialContent to device-specific format for inapp variant', () => {
543
- // This test covers lines 104-109 in HTMLEditor.js
544
- const mockUseInAppContent = require('../hooks/useInAppContent').useInAppContent;
545
- const originalUseInAppContent = jest.requireActual('../hooks/useInAppContent').useInAppContent;
546
-
547
- let capturedInitialContent = null;
548
- jest.spyOn(require('../hooks/useInAppContent'), 'useInAppContent').mockImplementation((initialContent) => {
549
- capturedInitialContent = initialContent;
550
- return originalUseInAppContent(initialContent, {});
551
- });
552
-
553
- render(
554
- <TestWrapper>
555
- <HTMLEditor
556
- {...defaultProps}
557
- variant="inapp"
558
- initialContent="<p>String content</p>"
559
- />
560
- </TestWrapper>
561
- );
562
-
563
- act(() => {
564
- jest.runAllTimers();
565
- });
566
-
567
- // Verify that string content was converted to device-specific format
568
- expect(capturedInitialContent).toEqual({
569
- android: '<p>String content</p>',
570
- ios: '<p>String content</p>',
571
- });
572
-
573
- jest.restoreAllMocks();
574
- });
575
-
576
476
  it('handles object initialContent for inapp variant', () => {
577
477
  const deviceContent = {
578
478
  android: '<p>Android content</p>',
579
- ios: '<p>iOS content</p>',
479
+ ios: '<p>iOS content</p>'
580
480
  };
581
481
 
582
482
  render(
@@ -596,42 +496,6 @@ describe('HTMLEditor', () => {
596
496
  expect(screen.getByTestId('device-toggle')).toBeInTheDocument();
597
497
  });
598
498
 
599
- it('uses provided device-specific content for inapp variant', () => {
600
- // This test covers lines 110-113 in HTMLEditor.js
601
- const deviceContent = {
602
- android: '<p>Android content</p>',
603
- ios: '<p>iOS content</p>',
604
- };
605
-
606
- const mockUseInAppContent = require('../hooks/useInAppContent').useInAppContent;
607
- const originalUseInAppContent = jest.requireActual('../hooks/useInAppContent').useInAppContent;
608
-
609
- let capturedInitialContent = null;
610
- jest.spyOn(require('../hooks/useInAppContent'), 'useInAppContent').mockImplementation((initialContent) => {
611
- capturedInitialContent = initialContent;
612
- return originalUseInAppContent(initialContent, {});
613
- });
614
-
615
- render(
616
- <TestWrapper>
617
- <HTMLEditor
618
- {...defaultProps}
619
- variant="inapp"
620
- initialContent={deviceContent}
621
- />
622
- </TestWrapper>
623
- );
624
-
625
- act(() => {
626
- jest.runAllTimers();
627
- });
628
-
629
- // Verify that object content was passed as-is
630
- expect(capturedInitialContent).toEqual(deviceContent);
631
-
632
- jest.restoreAllMocks();
633
- });
634
-
635
499
  it('handles inapp variant with layoutType', () => {
636
500
  render(
637
501
  <TestWrapper>
@@ -686,7 +550,7 @@ describe('HTMLEditor', () => {
686
550
  // Should open fullscreen modal - now there are 2 preview panes (main + fullscreen modal)
687
551
  const previewPanes = screen.getAllByTestId('preview-pane');
688
552
  expect(previewPanes.length).toBeGreaterThan(1);
689
- expect(previewPanes.some((pane) => pane.getAttribute('data-fullscreen') === 'true')).toBe(true);
553
+ expect(previewPanes.some(pane => pane.getAttribute('data-fullscreen') === 'true')).toBe(true);
690
554
  });
691
555
 
692
556
  it('renders fullscreen modal with correct components', () => {
@@ -704,7 +568,7 @@ describe('HTMLEditor', () => {
704
568
  fireEvent.click(toggleButton);
705
569
 
706
570
  const previewPanes = screen.getAllByTestId('preview-pane');
707
- expect(previewPanes.some((pane) => pane.getAttribute('data-fullscreen') === 'true')).toBe(true);
571
+ expect(previewPanes.some(pane => pane.getAttribute('data-fullscreen') === 'true')).toBe(true);
708
572
  });
709
573
 
710
574
  it('renders fullscreen modal for inapp variant', () => {
@@ -742,7 +606,7 @@ describe('HTMLEditor', () => {
742
606
  fireEvent.click(toggleButton);
743
607
 
744
608
  // Should have multiple preview panes
745
- const previewPanes = screen.getAllByTestId('preview-pane');
609
+ let previewPanes = screen.getAllByTestId('preview-pane');
746
610
  expect(previewPanes.length).toBeGreaterThan(1);
747
611
 
748
612
  // Close fullscreen (button should still be available to close)
@@ -803,7 +667,7 @@ describe('HTMLEditor', () => {
803
667
  isValidating: false,
804
668
  getAllIssues: () => [{ message: 'Test error', severity: 'error' }],
805
669
  isClean: () => false,
806
- summary: { totalErrors: 1, totalWarnings: 0 },
670
+ summary: { totalErrors: 1, totalWarnings: 0 }
807
671
  });
808
672
 
809
673
  render(
@@ -824,7 +688,7 @@ describe('HTMLEditor', () => {
824
688
  isValidating: false,
825
689
  getAllIssues: () => [],
826
690
  isClean: () => true,
827
- summary: { totalErrors: 0, totalWarnings: 0 },
691
+ summary: { totalErrors: 0, totalWarnings: 0 }
828
692
  });
829
693
 
830
694
  render(
@@ -845,7 +709,7 @@ describe('HTMLEditor', () => {
845
709
  it('handles readOnly prop', () => {
846
710
  render(
847
711
  <TestWrapper>
848
- <HTMLEditor {...defaultProps} readOnly />
712
+ <HTMLEditor {...defaultProps} readOnly={true} />
849
713
  </TestWrapper>
850
714
  );
851
715
 
@@ -993,7 +857,7 @@ describe('HTMLEditor', () => {
993
857
  describe('Error Handling', () => {
994
858
  it('handles missing editorRef gracefully', () => {
995
859
  // Mock console.warn to avoid noise in tests
996
- const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
860
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
997
861
 
998
862
  render(
999
863
  <TestWrapper>
@@ -1020,7 +884,7 @@ describe('HTMLEditor', () => {
1020
884
  focus: jest.fn(),
1021
885
  getCursor: jest.fn(() => 0),
1022
886
  // Missing insertText method
1023
- },
887
+ }
1024
888
  };
1025
889
 
1026
890
  // Mock the ref to return an editor without insertText
@@ -1055,7 +919,7 @@ describe('HTMLEditor', () => {
1055
919
  insertText: jest.fn(() => { throw new Error('Insert failed'); }),
1056
920
  focus: jest.fn(),
1057
921
  getCursor: jest.fn(() => 0),
1058
- },
922
+ }
1059
923
  };
1060
924
 
1061
925
  const TestComponent = () => {
@@ -1121,7 +985,7 @@ describe('HTMLEditor', () => {
1121
985
  current: {
1122
986
  navigateToLine: jest.fn(() => { throw new Error('Navigation failed'); }),
1123
987
  focus: jest.fn(),
1124
- },
988
+ }
1125
989
  };
1126
990
 
1127
991
  const TestComponent = () => {
@@ -1206,8 +1070,8 @@ describe('HTMLEditor', () => {
1206
1070
  onContentChange={jest.fn()}
1207
1071
  className="test-class"
1208
1072
  readOnly={false}
1209
- showFullscreenButton
1210
- autoSave
1073
+ showFullscreenButton={true}
1074
+ autoSave={true}
1211
1075
  autoSaveInterval={30000}
1212
1076
  />
1213
1077
  </TestWrapper>
@@ -1239,7 +1103,7 @@ describe('HTMLEditor', () => {
1239
1103
  <TestWrapper>
1240
1104
  <HTMLEditor
1241
1105
  variant="inapp"
1242
- autoSave
1106
+ autoSave={true}
1243
1107
  autoSaveInterval={15000}
1244
1108
  onSave={jest.fn()}
1245
1109
  onContentChange={jest.fn()}
@@ -1276,10 +1140,10 @@ describe('HTMLEditor', () => {
1276
1140
  enableRealTime: true,
1277
1141
  debounceMs: 500,
1278
1142
  enableSanitization: true,
1279
- securityLevel: 'standard',
1143
+ securityLevel: 'standard'
1280
1144
  }),
1281
1145
  expect.any(Function), // formatSanitizerMessage
1282
- expect.any(Function) // formatValidatorMessage
1146
+ expect.any(Function) // formatValidatorMessage
1283
1147
  );
1284
1148
  });
1285
1149
 
@@ -1302,10 +1166,10 @@ describe('HTMLEditor', () => {
1302
1166
  enableRealTime: true,
1303
1167
  debounceMs: 500,
1304
1168
  enableSanitization: true,
1305
- securityLevel: 'standard',
1169
+ securityLevel: 'standard'
1306
1170
  }),
1307
1171
  expect.any(Function), // formatSanitizerMessage
1308
- expect.any(Function) // formatValidatorMessage
1172
+ expect.any(Function) // formatValidatorMessage
1309
1173
  );
1310
1174
  });
1311
1175
 
@@ -1371,7 +1235,7 @@ describe('HTMLEditor', () => {
1371
1235
  enableRealTime: true,
1372
1236
  debounceMs: 500,
1373
1237
  enableSanitization: true,
1374
- securityLevel: 'standard',
1238
+ securityLevel: 'standard'
1375
1239
  },
1376
1240
  expect.any(Function),
1377
1241
  expect.any(Function)
@@ -1462,8 +1326,8 @@ describe('HTMLEditor', () => {
1462
1326
  <div className="html-editor html-editor--email">
1463
1327
  <CustomToolbar
1464
1328
  onLabelInsert={handleLabelInsert}
1465
- onToggleFullscreen={() => { }}
1466
- onSave={() => { }}
1329
+ onToggleFullscreen={() => {}}
1330
+ onSave={() => {}}
1467
1331
  />
1468
1332
  <div data-testid="split-container">
1469
1333
  <div data-testid="code-editor-pane" ref={editorRef}>
@@ -1561,7 +1425,7 @@ describe('HTMLEditor', () => {
1561
1425
  isValidating: false,
1562
1426
  getAllIssues: () => [{ message: 'Test error', severity: 'error' }],
1563
1427
  isClean: () => false,
1564
- summary: { totalErrors: 1, totalWarnings: 0 },
1428
+ summary: { totalErrors: 1, totalWarnings: 0 }
1565
1429
  });
1566
1430
 
1567
1431
  // Create a custom validation display that triggers the error click handler
@@ -1637,7 +1501,7 @@ describe('HTMLEditor', () => {
1637
1501
  current: {
1638
1502
  navigateToLine: jest.fn(),
1639
1503
  focus: jest.fn(),
1640
- },
1504
+ }
1641
1505
  };
1642
1506
 
1643
1507
  // Mock validation to show errors
@@ -1645,7 +1509,7 @@ describe('HTMLEditor', () => {
1645
1509
  isValidating: false,
1646
1510
  getAllIssues: () => [{ message: 'Test error', severity: 'error' }],
1647
1511
  isClean: () => false,
1648
- summary: { totalErrors: 1, totalWarnings: 0 },
1512
+ summary: { totalErrors: 1, totalWarnings: 0 }
1649
1513
  });
1650
1514
 
1651
1515
  const TestComponent = () => {
@@ -1679,7 +1543,7 @@ describe('HTMLEditor', () => {
1679
1543
  current: {
1680
1544
  focus: jest.fn(),
1681
1545
  // Missing navigateToLine method
1682
- },
1546
+ }
1683
1547
  };
1684
1548
 
1685
1549
  const TestComponent = () => {
@@ -1759,64 +1623,41 @@ describe('HTMLEditor', () => {
1759
1623
  });
1760
1624
 
1761
1625
  describe('Loading State Coverage (Lines 343-349)', () => {
1762
- it('shows loading state when content is null', () => {
1763
- // Temporarily override the mock to return null content
1764
- const originalUseEditorContent = require('../hooks/useEditorContent').useEditorContent;
1765
- require('../hooks/useEditorContent').useEditorContent = jest.fn(() => null);
1766
- require('../hooks/useLayoutState').useLayoutState = jest.fn(() => ({
1767
- splitSizes: [50, 50],
1768
- splitSize: 50,
1769
- viewMode: 'desktop',
1770
- mobileWidth: 375,
1771
- isFullscreen: false,
1772
- isResizing: false,
1773
- updateSplitSizes: jest.fn(),
1774
- setSplitSize: jest.fn(),
1775
- setViewMode: jest.fn(),
1776
- toggleViewMode: jest.fn(),
1777
- setMobileWidth: jest.fn(),
1778
- toggleFullscreen: jest.fn(),
1779
- resetLayout: jest.fn(),
1780
- setResizingState: jest.fn(),
1781
- handleResize: jest.fn(),
1782
- handleKeyboardShortcut: jest.fn(),
1783
- isMobileView: false,
1784
- isDesktopView: true,
1785
- minPaneSize: 20,
1786
- maxPaneSize: 80,
1787
- gutterSize: 10,
1788
- }));
1626
+ // Create separate test components to isolate mock effects
1627
+ const LoadingTestComponent = ({ mockContent = null, mockLayout = null }) => {
1628
+ // Mock the hooks inline for this specific test
1629
+ const useEditorContentMock = jest.fn(() => mockContent);
1630
+ const useInAppContentMock = jest.fn(() => mockContent);
1631
+ const useLayoutStateMock = jest.fn(() => mockLayout);
1632
+
1633
+ // Replace the hooks temporarily
1634
+ React.useMemo(() => {
1635
+ require('../hooks/useEditorContent').useEditorContent = useEditorContentMock;
1636
+ require('../hooks/useInAppContent').useInAppContent = useInAppContentMock;
1637
+ require('../hooks/useLayoutState').useLayoutState = useLayoutStateMock;
1638
+ }, []);
1639
+
1640
+ return <HTMLEditor {...defaultProps} />;
1641
+ };
1789
1642
 
1643
+ it('shows loading state when content is null', () => {
1790
1644
  render(
1791
1645
  <TestWrapper>
1792
- <HTMLEditor {...defaultProps} />
1646
+ <LoadingTestComponent mockContent={null} mockLayout={{ splitSizes: [50, 50] }} />
1793
1647
  </TestWrapper>
1794
1648
  );
1795
1649
 
1796
1650
  // Should show loading state
1797
1651
  expect(screen.getByText('Initializing HTML Editor...')).toBeInTheDocument();
1798
-
1799
- // Restore original mock
1800
- require('../hooks/useEditorContent').useEditorContent = originalUseEditorContent;
1801
1652
  });
1802
1653
 
1803
1654
  it('shows loading state when layout is null', () => {
1804
- // Temporarily override the mock to return null layout
1805
- require('../hooks/useLayoutState').useLayoutState = jest.fn(() => null);
1806
- require('../hooks/useEditorContent').useEditorContent = jest.fn(() => ({
1807
- content: '<p>Test</p>',
1808
- updateContent: jest.fn(),
1809
- saveContent: jest.fn(),
1810
- markAsSaved: jest.fn(),
1811
- isLoading: false,
1812
- isDirty: false,
1813
- hasContent: true,
1814
- getContentSize: jest.fn(() => 20),
1815
- }));
1816
-
1817
1655
  render(
1818
1656
  <TestWrapper>
1819
- <HTMLEditor {...defaultProps} />
1657
+ <LoadingTestComponent
1658
+ mockContent={{ content: '<p>Test</p>' }}
1659
+ mockLayout={null}
1660
+ />
1820
1661
  </TestWrapper>
1821
1662
  );
1822
1663
 
@@ -1825,12 +1666,9 @@ describe('HTMLEditor', () => {
1825
1666
  });
1826
1667
 
1827
1668
  it('shows loading state when both content and layout are null', () => {
1828
- require('../hooks/useEditorContent').useEditorContent = jest.fn(() => null);
1829
- require('../hooks/useLayoutState').useLayoutState = jest.fn(() => null);
1830
-
1831
1669
  render(
1832
1670
  <TestWrapper>
1833
- <HTMLEditor {...defaultProps} />
1671
+ <LoadingTestComponent mockContent={null} mockLayout={null} />
1834
1672
  </TestWrapper>
1835
1673
  );
1836
1674
 
@@ -1838,43 +1676,12 @@ describe('HTMLEditor', () => {
1838
1676
  });
1839
1677
 
1840
1678
  it('renders normally when both content and layout are available', () => {
1841
- require('../hooks/useEditorContent').useEditorContent = jest.fn(() => ({
1842
- content: '<p>Test</p>',
1843
- updateContent: jest.fn(),
1844
- saveContent: jest.fn(),
1845
- markAsSaved: jest.fn(),
1846
- isLoading: false,
1847
- isDirty: false,
1848
- hasContent: true,
1849
- getContentSize: jest.fn(() => 20),
1850
- }));
1851
- require('../hooks/useLayoutState').useLayoutState = jest.fn(() => ({
1852
- splitSizes: [50, 50],
1853
- splitSize: 50,
1854
- viewMode: 'desktop',
1855
- mobileWidth: 375,
1856
- isFullscreen: false,
1857
- isResizing: false,
1858
- updateSplitSizes: jest.fn(),
1859
- setSplitSize: jest.fn(),
1860
- setViewMode: jest.fn(),
1861
- toggleViewMode: jest.fn(),
1862
- setMobileWidth: jest.fn(),
1863
- toggleFullscreen: jest.fn(),
1864
- resetLayout: jest.fn(),
1865
- setResizingState: jest.fn(),
1866
- handleResize: jest.fn(),
1867
- handleKeyboardShortcut: jest.fn(),
1868
- isMobileView: false,
1869
- isDesktopView: true,
1870
- minPaneSize: 20,
1871
- maxPaneSize: 80,
1872
- gutterSize: 10,
1873
- }));
1874
-
1875
1679
  render(
1876
1680
  <TestWrapper>
1877
- <HTMLEditor {...defaultProps} />
1681
+ <LoadingTestComponent
1682
+ mockContent={{ content: '<p>Test</p>' }}
1683
+ mockLayout={{ splitSizes: [50, 50] }}
1684
+ />
1878
1685
  </TestWrapper>
1879
1686
  );
1880
1687
 
@@ -1895,7 +1702,7 @@ describe('HTMLEditor', () => {
1895
1702
  isValidating: false,
1896
1703
  getAllIssues: () => [{ message: 'Test error', severity: 'error' }],
1897
1704
  isClean: () => false,
1898
- summary: { totalErrors: 1, totalWarnings: 0 },
1705
+ summary: { totalErrors: 1, totalWarnings: 0 }
1899
1706
  });
1900
1707
 
1901
1708
  render(
@@ -1927,7 +1734,7 @@ describe('HTMLEditor', () => {
1927
1734
  isValidating: false,
1928
1735
  getAllIssues: () => [{ message: 'Test error', severity: 'error' }],
1929
1736
  isClean: () => false,
1930
- summary: { totalErrors: 1, totalWarnings: 0 },
1737
+ summary: { totalErrors: 1, totalWarnings: 0 }
1931
1738
  });
1932
1739
 
1933
1740
  render(
@@ -1998,811 +1805,5 @@ describe('HTMLEditor', () => {
1998
1805
  unmount();
1999
1806
  });
2000
1807
  });
1808
+ });
2001
1809
 
2002
- describe('handleContextChange Coverage', () => {
2003
- it('calls onContextChange when provided instead of making API call', () => {
2004
- const onContextChange = jest.fn();
2005
- const globalActions = {
2006
- fetchSchemaForEntity: jest.fn(),
2007
- };
2008
-
2009
- render(
2010
- <TestWrapper>
2011
- <HTMLEditor
2012
- {...defaultProps}
2013
- onContextChange={onContextChange}
2014
- globalActions={globalActions}
2015
- location={{ query: { type: 'embedded' } }}
2016
- />
2017
- </TestWrapper>
2018
- );
2019
-
2020
- act(() => {
2021
- jest.runAllTimers();
2022
- });
2023
-
2024
- // Wait for component to render
2025
- const codeEditorPane = screen.queryByTestId('code-editor-pane');
2026
- if (!codeEditorPane) {
2027
- // Component might be in loading state, wait a bit more
2028
- act(() => {
2029
- jest.runAllTimers();
2030
- });
2031
- }
2032
-
2033
- // The CodeEditorPane would call onContextChange
2034
- // We verify that onContextChange is passed and would be called
2035
- expect(onContextChange).toBeDefined();
2036
-
2037
- // If onContextChange is provided, globalActions.fetchSchemaForEntity should not be called
2038
- // This is tested by ensuring onContextChange is used instead
2039
- });
2040
-
2041
- it('makes API call when onContextChange is not provided but globalActions is available', () => {
2042
- const globalActions = {
2043
- fetchSchemaForEntity: jest.fn(),
2044
- };
2045
-
2046
- render(
2047
- <TestWrapper>
2048
- <HTMLEditor
2049
- {...defaultProps}
2050
- onContextChange={null}
2051
- globalActions={globalActions}
2052
- location={{ query: { type: 'embedded' } }}
2053
- />
2054
- </TestWrapper>
2055
- );
2056
-
2057
- act(() => {
2058
- jest.runAllTimers();
2059
- });
2060
-
2061
- // Wait for component to render
2062
- const toolbar = screen.queryByTestId('editor-toolbar');
2063
- if (!toolbar) {
2064
- act(() => {
2065
- jest.runAllTimers();
2066
- });
2067
- }
2068
-
2069
- // The handleContextChange would be called by CodeEditorPane
2070
- // We verify globalActions is available
2071
- expect(globalActions.fetchSchemaForEntity).toBeDefined();
2072
- });
2073
-
2074
- it('does not make API call when globalActions is not available', () => {
2075
- render(
2076
- <TestWrapper>
2077
- <HTMLEditor
2078
- {...defaultProps}
2079
- onContextChange={null}
2080
- globalActions={null}
2081
- location={{ query: { type: 'embedded' } }}
2082
- />
2083
- </TestWrapper>
2084
- );
2085
-
2086
- act(() => {
2087
- jest.runAllTimers();
2088
- });
2089
-
2090
- // Wait for component to render - might be in loading state
2091
- const toolbar = screen.queryByTestId('editor-toolbar');
2092
- const loading = screen.queryByText('Initializing HTML Editor...');
2093
-
2094
- // Component should render (either loaded or loading)
2095
- expect(toolbar || loading).toBeTruthy();
2096
- });
2097
-
2098
- it('uses SMS layout for INAPP variant in handleContextChange', () => {
2099
- const globalActions = {
2100
- fetchSchemaForEntity: jest.fn(),
2101
- };
2102
-
2103
- render(
2104
- <TestWrapper>
2105
- <HTMLEditor
2106
- {...defaultProps}
2107
- variant="inapp"
2108
- onContextChange={null}
2109
- globalActions={globalActions}
2110
- location={{ query: { type: 'embedded' } }}
2111
- />
2112
- </TestWrapper>
2113
- );
2114
-
2115
- act(() => {
2116
- jest.runAllTimers();
2117
- });
2118
-
2119
- // Wait for component to render
2120
- const deviceToggle = screen.queryByTestId('device-toggle');
2121
- const loading = screen.queryByText('Initializing HTML Editor...');
2122
-
2123
- // Component should render (either loaded or loading)
2124
- expect(deviceToggle || loading).toBeTruthy();
2125
- });
2126
-
2127
- it('handles context change with ALL context type', () => {
2128
- const globalActions = {
2129
- fetchSchemaForEntity: jest.fn(),
2130
- };
2131
-
2132
- render(
2133
- <TestWrapper>
2134
- <HTMLEditor
2135
- {...defaultProps}
2136
- onContextChange={null}
2137
- globalActions={globalActions}
2138
- location={{ query: { type: 'embedded' } }}
2139
- />
2140
- </TestWrapper>
2141
- );
2142
-
2143
- act(() => {
2144
- jest.runAllTimers();
2145
- });
2146
-
2147
- // Component should render
2148
- const toolbar = screen.queryByTestId('editor-toolbar');
2149
- const loading = screen.queryByText('Initializing HTML Editor...');
2150
- expect(toolbar || loading).toBeTruthy();
2151
- });
2152
-
2153
- it('handles context change with embedded type', () => {
2154
- const globalActions = {
2155
- fetchSchemaForEntity: jest.fn(),
2156
- };
2157
-
2158
- render(
2159
- <TestWrapper>
2160
- <HTMLEditor
2161
- {...defaultProps}
2162
- onContextChange={null}
2163
- globalActions={globalActions}
2164
- location={{ query: { type: 'embedded', module: 'test' } }}
2165
- />
2166
- </TestWrapper>
2167
- );
2168
-
2169
- act(() => {
2170
- jest.runAllTimers();
2171
- });
2172
-
2173
- // Component should render
2174
- const toolbar = screen.queryByTestId('editor-toolbar');
2175
- const loading = screen.queryByText('Initializing HTML Editor...');
2176
- expect(toolbar || loading).toBeTruthy();
2177
- });
2178
- });
2179
-
2180
- describe('handleLabelInsert Coverage', () => {
2181
- it('handles label insert when position is null and editor is ready', () => {
2182
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2183
-
2184
- render(
2185
- <TestWrapper>
2186
- <HTMLEditor {...defaultProps} />
2187
- </TestWrapper>
2188
- );
2189
-
2190
- act(() => {
2191
- jest.runAllTimers();
2192
- });
2193
-
2194
- // Wait for component to render
2195
- const insertButton = screen.queryByText('Insert Label (Null Position)');
2196
- if (insertButton) {
2197
- fireEvent.click(insertButton);
2198
-
2199
- // Should attempt to insert via editor ref
2200
- // The mock editor has insertText method, so it should work
2201
- expect(CapNotification.success).toHaveBeenCalled();
2202
- }
2203
-
2204
- const codeEditorPane = screen.queryByTestId('code-editor-pane');
2205
- const loading = screen.queryByText('Initializing HTML Editor...');
2206
- expect(codeEditorPane || loading).toBeTruthy();
2207
- });
2208
-
2209
- it('shows warning when editor is not available for label insert', () => {
2210
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2211
-
2212
- // Mock CodeEditorPane to return null ref
2213
- jest.doMock('../components/CodeEditorPane', () => {
2214
- const React = require('react');
2215
- return React.forwardRef(() => {
2216
- // Return null ref
2217
- React.useImperativeHandle(null, () => null);
2218
- return <div data-testid="code-editor-pane">Editor</div>;
2219
- });
2220
- });
2221
-
2222
- render(
2223
- <TestWrapper>
2224
- <HTMLEditor {...defaultProps} />
2225
- </TestWrapper>
2226
- );
2227
-
2228
- act(() => {
2229
- jest.runAllTimers();
2230
- });
2231
-
2232
- const insertButton = screen.queryByText('Insert Label (Null Position)');
2233
- if (insertButton) {
2234
- fireEvent.click(insertButton);
2235
- // Should show warning when editor is null
2236
- expect(CapNotification.warning).toHaveBeenCalled();
2237
- }
2238
- });
2239
-
2240
- it('shows error when editor does not have insertText method', () => {
2241
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2242
-
2243
- // Mock CodeEditorPane to return editor without insertText
2244
- jest.doMock('../components/CodeEditorPane', () => {
2245
- const React = require('react');
2246
- return React.forwardRef((props, ref) => {
2247
- React.useImperativeHandle(ref, () => ({
2248
- focus: jest.fn(),
2249
- getCursor: jest.fn(() => 0),
2250
- // No insertText method
2251
- }));
2252
- return <div data-testid="code-editor-pane">Editor</div>;
2253
- });
2254
- });
2255
-
2256
- render(
2257
- <TestWrapper>
2258
- <HTMLEditor {...defaultProps} />
2259
- </TestWrapper>
2260
- );
2261
-
2262
- act(() => {
2263
- jest.runAllTimers();
2264
- });
2265
-
2266
- const insertButton = screen.queryByText('Insert Label (Null Position)');
2267
- if (insertButton) {
2268
- fireEvent.click(insertButton);
2269
- // Should show error when insertText is not available
2270
- expect(CapNotification.error).toHaveBeenCalled();
2271
- }
2272
- });
2273
-
2274
- it('handles label insert when position is provided (already inserted)', () => {
2275
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2276
-
2277
- render(
2278
- <TestWrapper>
2279
- <HTMLEditor {...defaultProps} />
2280
- </TestWrapper>
2281
- );
2282
-
2283
- act(() => {
2284
- jest.runAllTimers();
2285
- });
2286
-
2287
- // Simulate label insert with position (already inserted by CodeEditorPane)
2288
- // This would call handleLabelInsert with a valid position
2289
- const insertButton = screen.queryByText('Insert Label');
2290
- if (insertButton) {
2291
- fireEvent.click(insertButton);
2292
- // Should show success notification
2293
- expect(CapNotification.success).toHaveBeenCalled();
2294
- }
2295
- });
2296
-
2297
- it('handles error during label insert', () => {
2298
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2299
-
2300
- // Mock CodeEditorPane to throw error on insertText
2301
- jest.doMock('../components/CodeEditorPane', () => {
2302
- const React = require('react');
2303
- return React.forwardRef((props, ref) => {
2304
- React.useImperativeHandle(ref, () => ({
2305
- insertText: jest.fn(() => {
2306
- throw new Error('Insert failed');
2307
- }),
2308
- focus: jest.fn(),
2309
- getCursor: jest.fn(() => 0),
2310
- }));
2311
- return <div data-testid="code-editor-pane">Editor</div>;
2312
- });
2313
- });
2314
-
2315
- render(
2316
- <TestWrapper>
2317
- <HTMLEditor {...defaultProps} />
2318
- </TestWrapper>
2319
- );
2320
-
2321
- act(() => {
2322
- jest.runAllTimers();
2323
- });
2324
-
2325
- const insertButton = screen.queryByText('Insert Label (Null Position)');
2326
- if (insertButton) {
2327
- fireEvent.click(insertButton);
2328
- // Should show error notification
2329
- expect(CapNotification.error).toHaveBeenCalled();
2330
- }
2331
- });
2332
- });
2333
-
2334
- describe('handleSave Coverage', () => {
2335
- it('calls onSave callback when save is triggered', () => {
2336
- const onSave = jest.fn();
2337
-
2338
- render(
2339
- <TestWrapper>
2340
- <HTMLEditor {...defaultProps} onSave={onSave} />
2341
- </TestWrapper>
2342
- );
2343
-
2344
- act(() => {
2345
- jest.runAllTimers();
2346
- });
2347
-
2348
- const saveButton = screen.queryByText('Save');
2349
- if (saveButton) {
2350
- fireEvent.click(saveButton);
2351
- // onSave should be called
2352
- expect(onSave).toHaveBeenCalled();
2353
- } else {
2354
- // Component might be in loading state
2355
- const loading = screen.queryByText('Initializing HTML Editor...');
2356
- expect(loading).toBeTruthy();
2357
- }
2358
- });
2359
-
2360
- it('shows success notification on save', () => {
2361
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2362
- const onSave = jest.fn();
2363
-
2364
- render(
2365
- <TestWrapper>
2366
- <HTMLEditor {...defaultProps} onSave={onSave} />
2367
- </TestWrapper>
2368
- );
2369
-
2370
- act(() => {
2371
- jest.runAllTimers();
2372
- });
2373
-
2374
- const saveButton = screen.queryByText('Save');
2375
- if (saveButton) {
2376
- fireEvent.click(saveButton);
2377
- expect(CapNotification.success).toHaveBeenCalled();
2378
- }
2379
- });
2380
-
2381
- it('handles save error gracefully', () => {
2382
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2383
- const onSave = jest.fn(() => {
2384
- throw new Error('Save failed');
2385
- });
2386
-
2387
- render(
2388
- <TestWrapper>
2389
- <HTMLEditor {...defaultProps} onSave={onSave} />
2390
- </TestWrapper>
2391
- );
2392
-
2393
- act(() => {
2394
- jest.runAllTimers();
2395
- });
2396
-
2397
- const saveButton = screen.queryByText('Save');
2398
- if (saveButton) {
2399
- fireEvent.click(saveButton);
2400
- expect(CapNotification.error).toHaveBeenCalled();
2401
- }
2402
- });
2403
- });
2404
-
2405
- describe('handleValidationErrorClick Coverage', () => {
2406
- it('navigates to error line when editor has navigateToLine method', () => {
2407
- mockUseValidationImpl.mockReturnValueOnce({
2408
- isValidating: false,
2409
- getAllIssues: () => [{
2410
- message: 'Test error', line: 5, column: 10, severity: 'error',
2411
- }],
2412
- isClean: () => false,
2413
- summary: { totalErrors: 1, totalWarnings: 0 },
2414
- });
2415
-
2416
- render(
2417
- <TestWrapper>
2418
- <HTMLEditor {...defaultProps} />
2419
- </TestWrapper>
2420
- );
2421
-
2422
- act(() => {
2423
- jest.runAllTimers();
2424
- });
2425
-
2426
- // Click on error
2427
- const errorButton = screen.queryByText('Error at Line 5');
2428
- if (errorButton) {
2429
- fireEvent.click(errorButton);
2430
- }
2431
-
2432
- // Should attempt to navigate to line
2433
- const codeEditorPane = screen.queryByTestId('code-editor-pane');
2434
- const loading = screen.queryByText('Initializing HTML Editor...');
2435
- expect(codeEditorPane || loading).toBeTruthy();
2436
- });
2437
-
2438
- it('focuses editor when navigateToLine is not available', () => {
2439
- mockCodeEditorOptions.includeNavigateToLine = false;
2440
-
2441
- mockUseValidationImpl.mockReturnValueOnce({
2442
- isValidating: false,
2443
- getAllIssues: () => [{
2444
- message: 'Test error', line: 5, column: 10, severity: 'error',
2445
- }],
2446
- isClean: () => false,
2447
- summary: { totalErrors: 1, totalWarnings: 0 },
2448
- });
2449
-
2450
- render(
2451
- <TestWrapper>
2452
- <HTMLEditor {...defaultProps} />
2453
- </TestWrapper>
2454
- );
2455
-
2456
- act(() => {
2457
- jest.runAllTimers();
2458
- });
2459
-
2460
- // Component should render
2461
- const codeEditorPane = screen.queryByTestId('code-editor-pane');
2462
- const loading = screen.queryByText('Initializing HTML Editor...');
2463
- expect(codeEditorPane || loading).toBeTruthy();
2464
- });
2465
- });
2466
-
2467
-
2468
- describe('Additional Coverage Tests', () => {
2469
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2470
-
2471
- beforeEach(() => {
2472
- // Reset options to default
2473
- mockCodeEditorOptions.includeInsertText = true;
2474
- mockCodeEditorOptions.insertTextThrows = false;
2475
- mockCodeEditorOptions.setRef = true;
2476
- mockCodeEditorOptions.navigateToLineThrows = false;
2477
- mockCodeEditorOptions.includeNavigateToLine = true;
2478
-
2479
- // Clear specific mocks instead of all to avoid breaking other mocks
2480
- CapNotification.warning.mockClear();
2481
- CapNotification.error.mockClear();
2482
- CapNotification.success.mockClear();
2483
-
2484
- // Restore hooks that might have been corrupted by Loading State Coverage tests
2485
- // This is necessary because Loading State Coverage tests modify the require cache
2486
- // and do not restore the original mocks
2487
- require('../hooks/useEditorContent').useEditorContent = () => ({
2488
- content: '<p>Test content</p>',
2489
- updateContent: jest.fn(),
2490
- saveContent: jest.fn(),
2491
- markAsSaved: jest.fn(),
2492
- isLoading: false,
2493
- isDirty: false,
2494
- hasContent: true,
2495
- getContentSize: jest.fn(() => 20),
2496
- });
2497
-
2498
- require('../hooks/useInAppContent').useInAppContent = () => ({
2499
- content: '<p>Android content</p>',
2500
- deviceContent: {
2501
- android: '<p>Android content</p>',
2502
- ios: '<p>iOS content</p>',
2503
- },
2504
- activeDevice: 'android',
2505
- keepContentSame: false,
2506
- updateContent: jest.fn(),
2507
- saveContent: jest.fn(),
2508
- markAsSaved: jest.fn(),
2509
- switchDevice: jest.fn(),
2510
- toggleContentSync: jest.fn(),
2511
- getDeviceContent: (device) => `<p>${device} content</p>`,
2512
- setDeviceContent: jest.fn(),
2513
- getContentSize: () => 20,
2514
- isLoading: false,
2515
- isDirty: false,
2516
- hasContent: true,
2517
- });
2518
-
2519
- require('../hooks/useLayoutState').useLayoutState = () => ({
2520
- splitSizes: [50, 50],
2521
- splitSize: 50,
2522
- viewMode: 'desktop',
2523
- mobileWidth: 375,
2524
- isFullscreen: false,
2525
- isResizing: false,
2526
- updateSplitSizes: jest.fn(),
2527
- setSplitSize: jest.fn(),
2528
- setViewMode: jest.fn(),
2529
- toggleViewMode: jest.fn(),
2530
- setMobileWidth: jest.fn(),
2531
- toggleFullscreen: jest.fn(),
2532
- resetLayout: jest.fn(),
2533
- setResizingState: jest.fn(),
2534
- handleResize: jest.fn(),
2535
- handleKeyboardShortcut: jest.fn(),
2536
- isMobileView: false,
2537
- isDesktopView: true,
2538
- minPaneSize: 20,
2539
- maxPaneSize: 80,
2540
- gutterSize: 10,
2541
- });
2542
- });
2543
-
2544
- describe('handleContextChange', () => {
2545
- it('calls onContextChange prop when provided', () => {
2546
- const onContextChange = jest.fn();
2547
- const globalActions = { fetchSchemaForEntity: jest.fn() };
2548
-
2549
- render(
2550
- <TestWrapper>
2551
- <HTMLEditor
2552
- {...defaultProps}
2553
- onContextChange={onContextChange}
2554
- globalActions={globalActions}
2555
- />
2556
- </TestWrapper>
2557
- );
2558
-
2559
- act(() => {
2560
- jest.runAllTimers();
2561
- });
2562
-
2563
- fireEvent.click(screen.getByTestId('trigger-context-change'));
2564
-
2565
- expect(onContextChange).toHaveBeenCalledWith('test-context');
2566
- expect(globalActions.fetchSchemaForEntity).not.toHaveBeenCalled();
2567
- });
2568
-
2569
- it('calls globalActions.fetchSchemaForEntity when onContextChange is NOT provided', () => {
2570
- const globalActions = { fetchSchemaForEntity: jest.fn() };
2571
- const location = { query: { type: 'embedded' } };
2572
-
2573
- render(
2574
- <TestWrapper>
2575
- <HTMLEditor
2576
- {...defaultProps}
2577
- globalActions={globalActions}
2578
- location={location}
2579
- variant="email"
2580
- />
2581
- </TestWrapper>
2582
- );
2583
-
2584
- act(() => {
2585
- jest.runAllTimers();
2586
- });
2587
-
2588
- fireEvent.click(screen.getByTestId('trigger-context-change'));
2589
-
2590
- expect(globalActions.fetchSchemaForEntity).toHaveBeenCalledWith({
2591
- layout: 'EMAIL',
2592
- type: 'TAG',
2593
- context: 'test-context',
2594
- embedded: 'embedded',
2595
- });
2596
- });
2597
-
2598
- it('handles INAPP variant in handleContextChange', () => {
2599
- const globalActions = { fetchSchemaForEntity: jest.fn() };
2600
- const location = { query: { type: 'full' } };
2601
-
2602
- render(
2603
- <TestWrapper>
2604
- <HTMLEditor
2605
- {...defaultProps}
2606
- globalActions={globalActions}
2607
- location={location}
2608
- variant="inapp"
2609
- />
2610
- </TestWrapper>
2611
- );
2612
-
2613
- act(() => {
2614
- jest.runAllTimers();
2615
- });
2616
-
2617
- fireEvent.click(screen.getByTestId('trigger-context-change'));
2618
-
2619
- expect(globalActions.fetchSchemaForEntity).toHaveBeenCalledWith({
2620
- layout: 'SMS', // INAPP uses SMS layout
2621
- type: 'TAG',
2622
- context: 'test-context',
2623
- embedded: 'full',
2624
- });
2625
- });
2626
-
2627
- it('handles missing globalActions or location', () => {
2628
- const globalActions = { fetchSchemaForEntity: jest.fn() };
2629
-
2630
- // Case 1: No globalActions
2631
- const { unmount } = render(
2632
- <TestWrapper>
2633
- <HTMLEditor
2634
- {...defaultProps}
2635
- globalActions={null}
2636
- location={{}}
2637
- />
2638
- </TestWrapper>
2639
- );
2640
-
2641
- act(() => {
2642
- jest.runAllTimers();
2643
- });
2644
-
2645
- fireEvent.click(screen.getByTestId('trigger-context-change'));
2646
- expect(globalActions.fetchSchemaForEntity).not.toHaveBeenCalled();
2647
- unmount();
2648
-
2649
- // Case 2: No location
2650
- render(
2651
- <TestWrapper>
2652
- <HTMLEditor
2653
- {...defaultProps}
2654
- globalActions={globalActions}
2655
- location={null}
2656
- />
2657
- </TestWrapper>
2658
- );
2659
-
2660
- act(() => {
2661
- jest.runAllTimers();
2662
- });
2663
-
2664
- fireEvent.click(screen.getByTestId('trigger-context-change'));
2665
- expect(globalActions.fetchSchemaForEntity).not.toHaveBeenCalled();
2666
- });
2667
- });
2668
-
2669
- describe('handleLabelInsert', () => {
2670
- it('shows warning when editor is not ready (position null, no editor)', () => {
2671
- mockCodeEditorOptions.setRef = false;
2672
-
2673
- render(
2674
- <TestWrapper>
2675
- <HTMLEditor {...defaultProps} />
2676
- </TestWrapper>
2677
- );
2678
-
2679
- act(() => {
2680
- jest.runAllTimers();
2681
- });
2682
-
2683
- // Click the button that passes null position
2684
- const insertButton = screen.getByText('Insert Label (Null Position)');
2685
- fireEvent.click(insertButton);
2686
-
2687
- expect(CapNotification.warning).toHaveBeenCalledWith(
2688
- expect.objectContaining({
2689
- message: 'Failed to insert label',
2690
- description: 'Editor is not ready. Please try again.',
2691
- })
2692
- );
2693
- });
2694
-
2695
- it('shows error when editor method insertText is not available', () => {
2696
- mockCodeEditorOptions.includeInsertText = false;
2697
-
2698
- render(
2699
- <TestWrapper>
2700
- <HTMLEditor {...defaultProps} />
2701
- </TestWrapper>
2702
- );
2703
-
2704
- act(() => {
2705
- jest.runAllTimers();
2706
- });
2707
-
2708
- const insertButton = screen.getByText('Insert Label (Null Position)');
2709
- fireEvent.click(insertButton);
2710
-
2711
- // Should show error when method is missing
2712
- expect(CapNotification.error).toHaveBeenCalledWith(
2713
- expect.objectContaining({
2714
- message: 'Failed to insert label',
2715
- })
2716
- );
2717
- });
2718
-
2719
- it('successfully inserts label when position is null', () => {
2720
- mockCodeEditorOptions.includeInsertText = true;
2721
-
2722
- render(
2723
- <TestWrapper>
2724
- <HTMLEditor {...defaultProps} />
2725
- </TestWrapper>
2726
- );
2727
-
2728
- act(() => {
2729
- jest.runAllTimers();
2730
- });
2731
-
2732
- const insertButton = screen.getByText('Insert Label (Null Position)');
2733
- fireEvent.click(insertButton);
2734
-
2735
- expect(CapNotification.success).toHaveBeenCalled();
2736
- });
2737
-
2738
- it('shows error when insertText throws', () => {
2739
- mockCodeEditorOptions.includeInsertText = true;
2740
- mockCodeEditorOptions.insertTextThrows = true;
2741
-
2742
- render(
2743
- <TestWrapper>
2744
- <HTMLEditor {...defaultProps} />
2745
- </TestWrapper>
2746
- );
2747
-
2748
- act(() => {
2749
- jest.runAllTimers();
2750
- });
2751
-
2752
- const insertButton = screen.getByText('Insert Label (Null Position)');
2753
- fireEvent.click(insertButton);
2754
-
2755
- expect(CapNotification.error).toHaveBeenCalledWith(expect.objectContaining({
2756
- description: 'Insert failed',
2757
- }));
2758
- });
2759
-
2760
- it('shows success notification when position is provided (handled by CodeEditorPane)', () => {
2761
- render(
2762
- <TestWrapper>
2763
- <HTMLEditor {...defaultProps} />
2764
- </TestWrapper>
2765
- );
2766
-
2767
- act(() => {
2768
- jest.runAllTimers();
2769
- });
2770
-
2771
- // Click the button that passes a position
2772
- const insertButton = screen.getByText('Insert Label');
2773
- fireEvent.click(insertButton);
2774
-
2775
- expect(CapNotification.success).toHaveBeenCalled();
2776
- });
2777
- });
2778
-
2779
- describe('handleValidationErrorClick', () => {
2780
- it('handles error when navigateToLine throws', () => {
2781
- mockCodeEditorOptions.navigateToLineThrows = true;
2782
-
2783
- mockUseValidationImpl.mockReturnValueOnce({
2784
- isValidating: false,
2785
- getAllIssues: () => [{
2786
- message: 'Test error', line: 5, column: 10, severity: 'error',
2787
- }],
2788
- isClean: () => false,
2789
- summary: { totalErrors: 1, totalWarnings: 0 },
2790
- });
2791
-
2792
- render(
2793
- <TestWrapper>
2794
- <HTMLEditor {...defaultProps} />
2795
- </TestWrapper>
2796
- );
2797
-
2798
- act(() => {
2799
- jest.runAllTimers();
2800
- });
2801
-
2802
- // Click error button
2803
- const errorButton = screen.getByText('Error at Line 5');
2804
- fireEvent.click(errorButton);
2805
- });
2806
- });
2807
- });
2808
- });