@capillarytech/creatives-library 8.0.263 → 8.0.265

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 (280) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/constants/unified.js +1 -3
  4. package/initialReducer.js +0 -2
  5. package/package.json +1 -1
  6. package/services/api.js +0 -15
  7. package/services/tests/api.test.js +0 -34
  8. package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +35 -17
  9. package/tests/integration/TemplateCreation/api-response.js +1 -31
  10. package/tests/integration/TemplateCreation/msw-handler.js +0 -2
  11. package/utils/common.js +0 -11
  12. package/utils/commonUtils.js +5 -28
  13. package/utils/tests/commonUtil.test.js +0 -224
  14. package/utils/tests/transformerUtils.test.js +0 -297
  15. package/utils/transformTemplateConfig.js +10 -0
  16. package/utils/transformerUtils.js +0 -40
  17. package/v2Components/CapDeviceContent/index.js +56 -61
  18. package/v2Components/CapImageUpload/constants.js +0 -2
  19. package/v2Components/CapImageUpload/index.js +16 -65
  20. package/v2Components/CapImageUpload/index.scss +1 -4
  21. package/v2Components/CapImageUpload/messages.js +1 -5
  22. package/v2Components/CapTagList/index.js +1 -6
  23. package/v2Components/CapTagListWithInput/index.js +1 -5
  24. package/v2Components/CapTagListWithInput/messages.js +1 -1
  25. package/v2Components/CapWhatsappCTA/tests/index.test.js +0 -5
  26. package/v2Components/ErrorInfoNote/index.js +72 -402
  27. package/v2Components/ErrorInfoNote/messages.js +6 -32
  28. package/v2Components/ErrorInfoNote/style.scss +6 -278
  29. package/v2Components/FormBuilder/tests/index.test.js +4 -13
  30. package/v2Components/HtmlEditor/HTMLEditor.js +99 -418
  31. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +133 -1882
  32. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +16 -27
  33. package/v2Components/HtmlEditor/_htmlEditor.scss +45 -108
  34. package/v2Components/HtmlEditor/_index.lazy.scss +1 -0
  35. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +102 -23
  36. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +140 -148
  37. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +1 -2
  38. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  39. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +1 -9
  40. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +6 -31
  41. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +0 -22
  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 +10 -7
  48. package/v2Components/HtmlEditor/components/PreviewPane/index.js +43 -22
  49. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  50. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +152 -0
  51. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +0 -18
  52. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +31 -36
  53. package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +34 -46
  54. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +46 -52
  55. package/v2Components/HtmlEditor/constants.js +20 -45
  56. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +16 -373
  57. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +16 -351
  58. package/v2Components/HtmlEditor/hooks/useEditorContent.js +2 -5
  59. package/v2Components/HtmlEditor/hooks/useInAppContent.js +146 -88
  60. package/v2Components/HtmlEditor/hooks/useValidation.js +56 -213
  61. package/v2Components/HtmlEditor/index.js +1 -1
  62. package/v2Components/HtmlEditor/messages.js +94 -102
  63. package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +45 -214
  64. package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +0 -134
  65. package/v2Components/HtmlEditor/utils/contentSanitizer.js +41 -40
  66. package/v2Components/HtmlEditor/utils/htmlValidator.js +72 -71
  67. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +124 -158
  68. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +25 -23
  69. package/v2Components/HtmlEditor/utils/validationAdapter.js +41 -66
  70. package/v2Components/MobilePushPreviewV2/index.js +7 -33
  71. package/v2Components/TemplatePreview/_templatePreview.scss +24 -55
  72. package/v2Components/TemplatePreview/index.js +32 -47
  73. package/v2Components/TemplatePreview/messages.js +0 -4
  74. package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +0 -1
  75. package/v2Containers/App/constants.js +0 -5
  76. package/v2Containers/BeeEditor/index.js +90 -172
  77. package/v2Containers/CreativesContainer/SlideBoxContent.js +53 -184
  78. package/v2Containers/CreativesContainer/SlideBoxFooter.js +13 -163
  79. package/v2Containers/CreativesContainer/SlideBoxHeader.js +1 -3
  80. package/v2Containers/CreativesContainer/constants.js +0 -4
  81. package/v2Containers/CreativesContainer/index.js +46 -408
  82. package/v2Containers/CreativesContainer/messages.js +0 -12
  83. package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +0 -210
  84. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +2 -11
  85. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +50 -342
  86. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +0 -103
  87. package/v2Containers/Email/actions.js +0 -7
  88. package/v2Containers/Email/constants.js +1 -5
  89. package/v2Containers/Email/index.js +36 -237
  90. package/v2Containers/Email/messages.js +0 -32
  91. package/v2Containers/Email/reducer.js +1 -12
  92. package/v2Containers/Email/sagas.js +7 -61
  93. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +0 -2
  94. package/v2Containers/Email/tests/reducer.test.js +0 -46
  95. package/v2Containers/Email/tests/sagas.test.js +29 -320
  96. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +21 -211
  97. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +74 -40
  98. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +67 -2
  99. package/v2Containers/EmailWrapper/constants.js +0 -2
  100. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +77 -629
  101. package/v2Containers/EmailWrapper/index.js +23 -103
  102. package/v2Containers/EmailWrapper/messages.js +1 -65
  103. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +214 -0
  104. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +77 -594
  105. package/v2Containers/InApp/actions.js +0 -7
  106. package/v2Containers/InApp/constants.js +4 -20
  107. package/v2Containers/InApp/index.js +359 -802
  108. package/v2Containers/InApp/index.scss +3 -4
  109. package/v2Containers/InApp/messages.js +3 -7
  110. package/v2Containers/InApp/reducer.js +3 -21
  111. package/v2Containers/InApp/sagas.js +9 -29
  112. package/v2Containers/InApp/selectors.js +5 -25
  113. package/v2Containers/InApp/tests/index.test.js +50 -154
  114. package/v2Containers/InApp/tests/reducer.test.js +0 -34
  115. package/v2Containers/InApp/tests/sagas.test.js +9 -61
  116. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +0 -3
  117. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +0 -2
  118. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +0 -2
  119. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +0 -9
  120. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +0 -12
  121. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +0 -4
  122. package/v2Containers/TagList/index.js +19 -62
  123. package/v2Containers/Templates/ChannelTypeIllustration.js +1 -13
  124. package/v2Containers/Templates/_templates.scss +1 -265
  125. package/v2Containers/Templates/actions.js +1 -2
  126. package/v2Containers/Templates/constants.js +0 -1
  127. package/v2Containers/Templates/index.js +38 -363
  128. package/v2Containers/Templates/messages.js +0 -28
  129. package/v2Containers/Templates/reducer.js +0 -2
  130. package/v2Containers/Templates/tests/index.test.js +0 -10
  131. package/v2Containers/TemplatesV2/TemplatesV2.style.js +2 -4
  132. package/v2Containers/TemplatesV2/index.js +7 -15
  133. package/v2Containers/TemplatesV2/messages.js +0 -4
  134. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +0 -34
  135. package/utils/imageUrlUpload.js +0 -141
  136. package/v2Components/CapImageUrlUpload/constants.js +0 -26
  137. package/v2Components/CapImageUrlUpload/index.js +0 -365
  138. package/v2Components/CapImageUrlUpload/index.scss +0 -35
  139. package/v2Components/CapImageUrlUpload/messages.js +0 -47
  140. package/v2Components/ErrorInfoNote/constants.js +0 -1
  141. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -870
  142. package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +0 -6
  143. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +0 -281
  144. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +0 -295
  145. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +0 -51
  146. package/v2Components/HtmlEditor/utils/validationConstants.js +0 -38
  147. package/v2Components/MobilePushPreviewV2/constants.js +0 -6
  148. package/v2Containers/BeePopupEditor/_beePopupEditor.scss +0 -14
  149. package/v2Containers/BeePopupEditor/constants.js +0 -10
  150. package/v2Containers/BeePopupEditor/index.js +0 -194
  151. package/v2Containers/BeePopupEditor/tests/index.test.js +0 -627
  152. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +0 -1246
  153. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +0 -2472
  154. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +0 -520
  155. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +0 -956
  156. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +0 -376
  157. package/v2Containers/InApp/__tests__/sagas.test.js +0 -363
  158. package/v2Containers/InApp/tests/selectors.test.js +0 -612
  159. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +0 -151
  160. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +0 -267
  161. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +0 -23
  162. package/v2Containers/InAppWrapper/constants.js +0 -16
  163. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +0 -473
  164. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +0 -198
  165. package/v2Containers/InAppWrapper/index.js +0 -148
  166. package/v2Containers/InAppWrapper/messages.js +0 -49
  167. package/v2Containers/InappAdvance/index.js +0 -1099
  168. package/v2Containers/InappAdvance/index.scss +0 -10
  169. package/v2Containers/InappAdvance/tests/index.test.js +0 -448
  170. package/v2Containers/WebPush/Create/components/BrandIconSection.js +0 -108
  171. package/v2Containers/WebPush/Create/components/ButtonForm.js +0 -172
  172. package/v2Containers/WebPush/Create/components/ButtonItem.js +0 -101
  173. package/v2Containers/WebPush/Create/components/ButtonList.js +0 -145
  174. package/v2Containers/WebPush/Create/components/ButtonsLinksSection.js +0 -164
  175. package/v2Containers/WebPush/Create/components/ButtonsLinksSection.test.js +0 -463
  176. package/v2Containers/WebPush/Create/components/FormActions.js +0 -54
  177. package/v2Containers/WebPush/Create/components/FormActions.test.js +0 -163
  178. package/v2Containers/WebPush/Create/components/MediaSection.js +0 -142
  179. package/v2Containers/WebPush/Create/components/MediaSection.test.js +0 -341
  180. package/v2Containers/WebPush/Create/components/MessageSection.js +0 -103
  181. package/v2Containers/WebPush/Create/components/MessageSection.test.js +0 -268
  182. package/v2Containers/WebPush/Create/components/NotificationTitleSection.js +0 -87
  183. package/v2Containers/WebPush/Create/components/NotificationTitleSection.test.js +0 -210
  184. package/v2Containers/WebPush/Create/components/TemplateNameSection.js +0 -54
  185. package/v2Containers/WebPush/Create/components/TemplateNameSection.test.js +0 -143
  186. package/v2Containers/WebPush/Create/components/__snapshots__/ButtonsLinksSection.test.js.snap +0 -86
  187. package/v2Containers/WebPush/Create/components/__snapshots__/FormActions.test.js.snap +0 -16
  188. package/v2Containers/WebPush/Create/components/__snapshots__/MediaSection.test.js.snap +0 -41
  189. package/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +0 -54
  190. package/v2Containers/WebPush/Create/components/__snapshots__/NotificationTitleSection.test.js.snap +0 -37
  191. package/v2Containers/WebPush/Create/components/__snapshots__/TemplateNameSection.test.js.snap +0 -21
  192. package/v2Containers/WebPush/Create/components/_buttons.scss +0 -246
  193. package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +0 -554
  194. package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +0 -607
  195. package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +0 -633
  196. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +0 -666
  197. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +0 -74
  198. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +0 -78
  199. package/v2Containers/WebPush/Create/hooks/useButtonManagement.js +0 -138
  200. package/v2Containers/WebPush/Create/hooks/useButtonManagement.test.js +0 -406
  201. package/v2Containers/WebPush/Create/hooks/useCharacterCount.js +0 -30
  202. package/v2Containers/WebPush/Create/hooks/useCharacterCount.test.js +0 -151
  203. package/v2Containers/WebPush/Create/hooks/useImageUpload.js +0 -104
  204. package/v2Containers/WebPush/Create/hooks/useImageUpload.test.js +0 -538
  205. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -122
  206. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -633
  207. package/v2Containers/WebPush/Create/index.js +0 -1148
  208. package/v2Containers/WebPush/Create/index.scss +0 -134
  209. package/v2Containers/WebPush/Create/messages.js +0 -211
  210. package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +0 -228
  211. package/v2Containers/WebPush/Create/preview/NotificationContainer.js +0 -294
  212. package/v2Containers/WebPush/Create/preview/PreviewContent.js +0 -90
  213. package/v2Containers/WebPush/Create/preview/PreviewControls.js +0 -305
  214. package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +0 -25
  215. package/v2Containers/WebPush/Create/preview/WebPushPreview.js +0 -155
  216. package/v2Containers/WebPush/Create/preview/assets/Light.svg +0 -53
  217. package/v2Containers/WebPush/Create/preview/assets/Top.svg +0 -5
  218. package/v2Containers/WebPush/Create/preview/assets/android-arrow-down.svg +0 -9
  219. package/v2Containers/WebPush/Create/preview/assets/android-arrow-up.svg +0 -9
  220. package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
  221. package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
  222. package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +0 -106
  223. package/v2Containers/WebPush/Create/preview/assets/iOS.svg +0 -26
  224. package/v2Containers/WebPush/Create/preview/assets/macos-arrow-down-icon.svg +0 -9
  225. package/v2Containers/WebPush/Create/preview/assets/macos-triple-dot-icon.svg +0 -9
  226. package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +0 -18
  227. package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +0 -29
  228. package/v2Containers/WebPush/Create/preview/assets/windows-close-icon.svg +0 -9
  229. package/v2Containers/WebPush/Create/preview/assets/windows-triple-dot-icon.svg +0 -9
  230. package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +0 -51
  231. package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +0 -145
  232. package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +0 -45
  233. package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +0 -68
  234. package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +0 -61
  235. package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +0 -99
  236. package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +0 -733
  237. package/v2Containers/WebPush/Create/preview/components/tests/WindowsChromeExpanded.test.js +0 -571
  238. package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +0 -85
  239. package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/WindowsChromeExpanded.test.js.snap +0 -81
  240. package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +0 -50
  241. package/v2Containers/WebPush/Create/preview/constants.js +0 -637
  242. package/v2Containers/WebPush/Create/preview/notification-container.scss +0 -79
  243. package/v2Containers/WebPush/Create/preview/preview.scss +0 -358
  244. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +0 -370
  245. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +0 -12
  246. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +0 -12
  247. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +0 -12
  248. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +0 -47
  249. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +0 -11
  250. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +0 -11
  251. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +0 -11
  252. package/v2Containers/WebPush/Create/preview/styles/_base.scss +0 -207
  253. package/v2Containers/WebPush/Create/preview/styles/_ios.scss +0 -153
  254. package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +0 -107
  255. package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +0 -101
  256. package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +0 -229
  257. package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +0 -909
  258. package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +0 -1081
  259. package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +0 -723
  260. package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +0 -1327
  261. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +0 -131
  262. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +0 -112
  263. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +0 -144
  264. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +0 -129
  265. package/v2Containers/WebPush/Create/utils/payloadBuilder.js +0 -96
  266. package/v2Containers/WebPush/Create/utils/payloadBuilder.test.js +0 -396
  267. package/v2Containers/WebPush/Create/utils/previewUtils.js +0 -89
  268. package/v2Containers/WebPush/Create/utils/urlValidation.js +0 -115
  269. package/v2Containers/WebPush/Create/utils/urlValidation.test.js +0 -449
  270. package/v2Containers/WebPush/Create/utils/validation.js +0 -75
  271. package/v2Containers/WebPush/Create/utils/validation.test.js +0 -283
  272. package/v2Containers/WebPush/actions.js +0 -60
  273. package/v2Containers/WebPush/constants.js +0 -132
  274. package/v2Containers/WebPush/index.js +0 -2
  275. package/v2Containers/WebPush/reducer.js +0 -104
  276. package/v2Containers/WebPush/sagas.js +0 -119
  277. package/v2Containers/WebPush/selectors.js +0 -65
  278. package/v2Containers/WebPush/tests/reducer.test.js +0 -863
  279. package/v2Containers/WebPush/tests/sagas.test.js +0 -566
  280. package/v2Containers/WebPush/tests/selectors.test.js +0 -960
@@ -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()}
@@ -1273,14 +1137,13 @@ describe('HTMLEditor', () => {
1273
1137
  expect.any(String),
1274
1138
  expect.any(String),
1275
1139
  expect.objectContaining({
1276
- apiValidationErrors: null,
1277
1140
  enableRealTime: true,
1278
1141
  debounceMs: 500,
1279
1142
  enableSanitization: true,
1280
- securityLevel: 'standard',
1143
+ securityLevel: 'standard'
1281
1144
  }),
1282
1145
  expect.any(Function), // formatSanitizerMessage
1283
- expect.any(Function) // formatValidatorMessage
1146
+ expect.any(Function) // formatValidatorMessage
1284
1147
  );
1285
1148
  });
1286
1149
 
@@ -1300,14 +1163,13 @@ describe('HTMLEditor', () => {
1300
1163
  expect.any(String),
1301
1164
  expect.any(String),
1302
1165
  expect.objectContaining({
1303
- apiValidationErrors: null,
1304
1166
  enableRealTime: true,
1305
1167
  debounceMs: 500,
1306
1168
  enableSanitization: true,
1307
- securityLevel: 'standard',
1169
+ securityLevel: 'standard'
1308
1170
  }),
1309
1171
  expect.any(Function), // formatSanitizerMessage
1310
- expect.any(Function) // formatValidatorMessage
1172
+ expect.any(Function) // formatValidatorMessage
1311
1173
  );
1312
1174
  });
1313
1175
 
@@ -1370,11 +1232,10 @@ describe('HTMLEditor', () => {
1370
1232
  expect.any(String), // currentContent
1371
1233
  'email', // variant
1372
1234
  {
1373
- apiValidationErrors: null,
1374
1235
  enableRealTime: true,
1375
1236
  debounceMs: 500,
1376
1237
  enableSanitization: true,
1377
- securityLevel: 'standard',
1238
+ securityLevel: 'standard'
1378
1239
  },
1379
1240
  expect.any(Function),
1380
1241
  expect.any(Function)
@@ -1465,8 +1326,8 @@ describe('HTMLEditor', () => {
1465
1326
  <div className="html-editor html-editor--email">
1466
1327
  <CustomToolbar
1467
1328
  onLabelInsert={handleLabelInsert}
1468
- onToggleFullscreen={() => { }}
1469
- onSave={() => { }}
1329
+ onToggleFullscreen={() => {}}
1330
+ onSave={() => {}}
1470
1331
  />
1471
1332
  <div data-testid="split-container">
1472
1333
  <div data-testid="code-editor-pane" ref={editorRef}>
@@ -1564,7 +1425,7 @@ describe('HTMLEditor', () => {
1564
1425
  isValidating: false,
1565
1426
  getAllIssues: () => [{ message: 'Test error', severity: 'error' }],
1566
1427
  isClean: () => false,
1567
- summary: { totalErrors: 1, totalWarnings: 0 },
1428
+ summary: { totalErrors: 1, totalWarnings: 0 }
1568
1429
  });
1569
1430
 
1570
1431
  // Create a custom validation display that triggers the error click handler
@@ -1640,7 +1501,7 @@ describe('HTMLEditor', () => {
1640
1501
  current: {
1641
1502
  navigateToLine: jest.fn(),
1642
1503
  focus: jest.fn(),
1643
- },
1504
+ }
1644
1505
  };
1645
1506
 
1646
1507
  // Mock validation to show errors
@@ -1648,7 +1509,7 @@ describe('HTMLEditor', () => {
1648
1509
  isValidating: false,
1649
1510
  getAllIssues: () => [{ message: 'Test error', severity: 'error' }],
1650
1511
  isClean: () => false,
1651
- summary: { totalErrors: 1, totalWarnings: 0 },
1512
+ summary: { totalErrors: 1, totalWarnings: 0 }
1652
1513
  });
1653
1514
 
1654
1515
  const TestComponent = () => {
@@ -1682,7 +1543,7 @@ describe('HTMLEditor', () => {
1682
1543
  current: {
1683
1544
  focus: jest.fn(),
1684
1545
  // Missing navigateToLine method
1685
- },
1546
+ }
1686
1547
  };
1687
1548
 
1688
1549
  const TestComponent = () => {
@@ -1762,64 +1623,41 @@ describe('HTMLEditor', () => {
1762
1623
  });
1763
1624
 
1764
1625
  describe('Loading State Coverage (Lines 343-349)', () => {
1765
- it('shows loading state when content is null', () => {
1766
- // Temporarily override the mock to return null content
1767
- const originalUseEditorContent = require('../hooks/useEditorContent').useEditorContent;
1768
- require('../hooks/useEditorContent').useEditorContent = jest.fn(() => null);
1769
- require('../hooks/useLayoutState').useLayoutState = jest.fn(() => ({
1770
- splitSizes: [50, 50],
1771
- splitSize: 50,
1772
- viewMode: 'desktop',
1773
- mobileWidth: 375,
1774
- isFullscreen: false,
1775
- isResizing: false,
1776
- updateSplitSizes: jest.fn(),
1777
- setSplitSize: jest.fn(),
1778
- setViewMode: jest.fn(),
1779
- toggleViewMode: jest.fn(),
1780
- setMobileWidth: jest.fn(),
1781
- toggleFullscreen: jest.fn(),
1782
- resetLayout: jest.fn(),
1783
- setResizingState: jest.fn(),
1784
- handleResize: jest.fn(),
1785
- handleKeyboardShortcut: jest.fn(),
1786
- isMobileView: false,
1787
- isDesktopView: true,
1788
- minPaneSize: 20,
1789
- maxPaneSize: 80,
1790
- gutterSize: 10,
1791
- }));
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
+ };
1792
1642
 
1643
+ it('shows loading state when content is null', () => {
1793
1644
  render(
1794
1645
  <TestWrapper>
1795
- <HTMLEditor {...defaultProps} />
1646
+ <LoadingTestComponent mockContent={null} mockLayout={{ splitSizes: [50, 50] }} />
1796
1647
  </TestWrapper>
1797
1648
  );
1798
1649
 
1799
1650
  // Should show loading state
1800
1651
  expect(screen.getByText('Initializing HTML Editor...')).toBeInTheDocument();
1801
-
1802
- // Restore original mock
1803
- require('../hooks/useEditorContent').useEditorContent = originalUseEditorContent;
1804
1652
  });
1805
1653
 
1806
1654
  it('shows loading state when layout is null', () => {
1807
- // Temporarily override the mock to return null layout
1808
- require('../hooks/useLayoutState').useLayoutState = jest.fn(() => null);
1809
- require('../hooks/useEditorContent').useEditorContent = jest.fn(() => ({
1810
- content: '<p>Test</p>',
1811
- updateContent: jest.fn(),
1812
- saveContent: jest.fn(),
1813
- markAsSaved: jest.fn(),
1814
- isLoading: false,
1815
- isDirty: false,
1816
- hasContent: true,
1817
- getContentSize: jest.fn(() => 20),
1818
- }));
1819
-
1820
1655
  render(
1821
1656
  <TestWrapper>
1822
- <HTMLEditor {...defaultProps} />
1657
+ <LoadingTestComponent
1658
+ mockContent={{ content: '<p>Test</p>' }}
1659
+ mockLayout={null}
1660
+ />
1823
1661
  </TestWrapper>
1824
1662
  );
1825
1663
 
@@ -1828,12 +1666,9 @@ describe('HTMLEditor', () => {
1828
1666
  });
1829
1667
 
1830
1668
  it('shows loading state when both content and layout are null', () => {
1831
- require('../hooks/useEditorContent').useEditorContent = jest.fn(() => null);
1832
- require('../hooks/useLayoutState').useLayoutState = jest.fn(() => null);
1833
-
1834
1669
  render(
1835
1670
  <TestWrapper>
1836
- <HTMLEditor {...defaultProps} />
1671
+ <LoadingTestComponent mockContent={null} mockLayout={null} />
1837
1672
  </TestWrapper>
1838
1673
  );
1839
1674
 
@@ -1841,43 +1676,12 @@ describe('HTMLEditor', () => {
1841
1676
  });
1842
1677
 
1843
1678
  it('renders normally when both content and layout are available', () => {
1844
- require('../hooks/useEditorContent').useEditorContent = jest.fn(() => ({
1845
- content: '<p>Test</p>',
1846
- updateContent: jest.fn(),
1847
- saveContent: jest.fn(),
1848
- markAsSaved: jest.fn(),
1849
- isLoading: false,
1850
- isDirty: false,
1851
- hasContent: true,
1852
- getContentSize: jest.fn(() => 20),
1853
- }));
1854
- require('../hooks/useLayoutState').useLayoutState = jest.fn(() => ({
1855
- splitSizes: [50, 50],
1856
- splitSize: 50,
1857
- viewMode: 'desktop',
1858
- mobileWidth: 375,
1859
- isFullscreen: false,
1860
- isResizing: false,
1861
- updateSplitSizes: jest.fn(),
1862
- setSplitSize: jest.fn(),
1863
- setViewMode: jest.fn(),
1864
- toggleViewMode: jest.fn(),
1865
- setMobileWidth: jest.fn(),
1866
- toggleFullscreen: jest.fn(),
1867
- resetLayout: jest.fn(),
1868
- setResizingState: jest.fn(),
1869
- handleResize: jest.fn(),
1870
- handleKeyboardShortcut: jest.fn(),
1871
- isMobileView: false,
1872
- isDesktopView: true,
1873
- minPaneSize: 20,
1874
- maxPaneSize: 80,
1875
- gutterSize: 10,
1876
- }));
1877
-
1878
1679
  render(
1879
1680
  <TestWrapper>
1880
- <HTMLEditor {...defaultProps} />
1681
+ <LoadingTestComponent
1682
+ mockContent={{ content: '<p>Test</p>' }}
1683
+ mockLayout={{ splitSizes: [50, 50] }}
1684
+ />
1881
1685
  </TestWrapper>
1882
1686
  );
1883
1687
 
@@ -1898,7 +1702,7 @@ describe('HTMLEditor', () => {
1898
1702
  isValidating: false,
1899
1703
  getAllIssues: () => [{ message: 'Test error', severity: 'error' }],
1900
1704
  isClean: () => false,
1901
- summary: { totalErrors: 1, totalWarnings: 0 },
1705
+ summary: { totalErrors: 1, totalWarnings: 0 }
1902
1706
  });
1903
1707
 
1904
1708
  render(
@@ -1930,7 +1734,7 @@ describe('HTMLEditor', () => {
1930
1734
  isValidating: false,
1931
1735
  getAllIssues: () => [{ message: 'Test error', severity: 'error' }],
1932
1736
  isClean: () => false,
1933
- summary: { totalErrors: 1, totalWarnings: 0 },
1737
+ summary: { totalErrors: 1, totalWarnings: 0 }
1934
1738
  });
1935
1739
 
1936
1740
  render(
@@ -2001,1558 +1805,5 @@ describe('HTMLEditor', () => {
2001
1805
  unmount();
2002
1806
  });
2003
1807
  });
1808
+ });
2004
1809
 
2005
- describe('handleContextChange Coverage', () => {
2006
- it('calls onContextChange when provided instead of making API call', () => {
2007
- const onContextChange = jest.fn();
2008
- const globalActions = {
2009
- fetchSchemaForEntity: jest.fn(),
2010
- };
2011
-
2012
- render(
2013
- <TestWrapper>
2014
- <HTMLEditor
2015
- {...defaultProps}
2016
- onContextChange={onContextChange}
2017
- globalActions={globalActions}
2018
- location={{ query: { type: 'embedded' } }}
2019
- />
2020
- </TestWrapper>
2021
- );
2022
-
2023
- act(() => {
2024
- jest.runAllTimers();
2025
- });
2026
-
2027
- // Wait for component to render
2028
- const codeEditorPane = screen.queryByTestId('code-editor-pane');
2029
- if (!codeEditorPane) {
2030
- // Component might be in loading state, wait a bit more
2031
- act(() => {
2032
- jest.runAllTimers();
2033
- });
2034
- }
2035
-
2036
- // The CodeEditorPane would call onContextChange
2037
- // We verify that onContextChange is passed and would be called
2038
- expect(onContextChange).toBeDefined();
2039
-
2040
- // If onContextChange is provided, globalActions.fetchSchemaForEntity should not be called
2041
- // This is tested by ensuring onContextChange is used instead
2042
- });
2043
-
2044
- it('makes API call when onContextChange is not provided but globalActions is available', () => {
2045
- const globalActions = {
2046
- fetchSchemaForEntity: jest.fn(),
2047
- };
2048
-
2049
- render(
2050
- <TestWrapper>
2051
- <HTMLEditor
2052
- {...defaultProps}
2053
- onContextChange={null}
2054
- globalActions={globalActions}
2055
- location={{ query: { type: 'embedded' } }}
2056
- />
2057
- </TestWrapper>
2058
- );
2059
-
2060
- act(() => {
2061
- jest.runAllTimers();
2062
- });
2063
-
2064
- // Wait for component to render
2065
- const toolbar = screen.queryByTestId('editor-toolbar');
2066
- if (!toolbar) {
2067
- act(() => {
2068
- jest.runAllTimers();
2069
- });
2070
- }
2071
-
2072
- // The handleContextChange would be called by CodeEditorPane
2073
- // We verify globalActions is available
2074
- expect(globalActions.fetchSchemaForEntity).toBeDefined();
2075
- });
2076
-
2077
- it('does not make API call when globalActions is not available', () => {
2078
- render(
2079
- <TestWrapper>
2080
- <HTMLEditor
2081
- {...defaultProps}
2082
- onContextChange={null}
2083
- globalActions={null}
2084
- location={{ query: { type: 'embedded' } }}
2085
- />
2086
- </TestWrapper>
2087
- );
2088
-
2089
- act(() => {
2090
- jest.runAllTimers();
2091
- });
2092
-
2093
- // Wait for component to render - might be in loading state
2094
- const toolbar = screen.queryByTestId('editor-toolbar');
2095
- const loading = screen.queryByText('Initializing HTML Editor...');
2096
-
2097
- // Component should render (either loaded or loading)
2098
- expect(toolbar || loading).toBeTruthy();
2099
- });
2100
-
2101
- it('uses SMS layout for INAPP variant in handleContextChange', () => {
2102
- const globalActions = {
2103
- fetchSchemaForEntity: jest.fn(),
2104
- };
2105
-
2106
- render(
2107
- <TestWrapper>
2108
- <HTMLEditor
2109
- {...defaultProps}
2110
- variant="inapp"
2111
- onContextChange={null}
2112
- globalActions={globalActions}
2113
- location={{ query: { type: 'embedded' } }}
2114
- />
2115
- </TestWrapper>
2116
- );
2117
-
2118
- act(() => {
2119
- jest.runAllTimers();
2120
- });
2121
-
2122
- // Wait for component to render
2123
- const deviceToggle = screen.queryByTestId('device-toggle');
2124
- const loading = screen.queryByText('Initializing HTML Editor...');
2125
-
2126
- // Component should render (either loaded or loading)
2127
- expect(deviceToggle || loading).toBeTruthy();
2128
- });
2129
-
2130
- it('handles context change with ALL context type', () => {
2131
- const globalActions = {
2132
- fetchSchemaForEntity: jest.fn(),
2133
- };
2134
-
2135
- render(
2136
- <TestWrapper>
2137
- <HTMLEditor
2138
- {...defaultProps}
2139
- onContextChange={null}
2140
- globalActions={globalActions}
2141
- location={{ query: { type: 'embedded' } }}
2142
- />
2143
- </TestWrapper>
2144
- );
2145
-
2146
- act(() => {
2147
- jest.runAllTimers();
2148
- });
2149
-
2150
- // Component should render
2151
- const toolbar = screen.queryByTestId('editor-toolbar');
2152
- const loading = screen.queryByText('Initializing HTML Editor...');
2153
- expect(toolbar || loading).toBeTruthy();
2154
- });
2155
-
2156
- it('handles context change with embedded type', () => {
2157
- const globalActions = {
2158
- fetchSchemaForEntity: jest.fn(),
2159
- };
2160
-
2161
- render(
2162
- <TestWrapper>
2163
- <HTMLEditor
2164
- {...defaultProps}
2165
- onContextChange={null}
2166
- globalActions={globalActions}
2167
- location={{ query: { type: 'embedded', module: 'test' } }}
2168
- />
2169
- </TestWrapper>
2170
- );
2171
-
2172
- act(() => {
2173
- jest.runAllTimers();
2174
- });
2175
-
2176
- // Component should render
2177
- const toolbar = screen.queryByTestId('editor-toolbar');
2178
- const loading = screen.queryByText('Initializing HTML Editor...');
2179
- expect(toolbar || loading).toBeTruthy();
2180
- });
2181
- });
2182
-
2183
- describe('handleLabelInsert Coverage', () => {
2184
- it('handles label insert when position is null and editor is ready', () => {
2185
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2186
-
2187
- render(
2188
- <TestWrapper>
2189
- <HTMLEditor {...defaultProps} />
2190
- </TestWrapper>
2191
- );
2192
-
2193
- act(() => {
2194
- jest.runAllTimers();
2195
- });
2196
-
2197
- // Wait for component to render
2198
- const insertButton = screen.queryByText('Insert Label (Null Position)');
2199
- if (insertButton) {
2200
- fireEvent.click(insertButton);
2201
-
2202
- // Should attempt to insert via editor ref
2203
- // The mock editor has insertText method, so it should work
2204
- expect(CapNotification.success).toHaveBeenCalled();
2205
- }
2206
-
2207
- const codeEditorPane = screen.queryByTestId('code-editor-pane');
2208
- const loading = screen.queryByText('Initializing HTML Editor...');
2209
- expect(codeEditorPane || loading).toBeTruthy();
2210
- });
2211
-
2212
- it('shows warning when editor is not available for label insert', () => {
2213
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2214
-
2215
- // Mock CodeEditorPane to return null ref
2216
- jest.doMock('../components/CodeEditorPane', () => {
2217
- const React = require('react');
2218
- return React.forwardRef(() => {
2219
- // Return null ref
2220
- React.useImperativeHandle(null, () => null);
2221
- return <div data-testid="code-editor-pane">Editor</div>;
2222
- });
2223
- });
2224
-
2225
- render(
2226
- <TestWrapper>
2227
- <HTMLEditor {...defaultProps} />
2228
- </TestWrapper>
2229
- );
2230
-
2231
- act(() => {
2232
- jest.runAllTimers();
2233
- });
2234
-
2235
- const insertButton = screen.queryByText('Insert Label (Null Position)');
2236
- if (insertButton) {
2237
- fireEvent.click(insertButton);
2238
- // Should show warning when editor is null
2239
- expect(CapNotification.warning).toHaveBeenCalled();
2240
- }
2241
- });
2242
-
2243
- it('shows error when editor does not have insertText method', () => {
2244
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2245
-
2246
- // Mock CodeEditorPane to return editor without insertText
2247
- jest.doMock('../components/CodeEditorPane', () => {
2248
- const React = require('react');
2249
- return React.forwardRef((props, ref) => {
2250
- React.useImperativeHandle(ref, () => ({
2251
- focus: jest.fn(),
2252
- getCursor: jest.fn(() => 0),
2253
- // No insertText method
2254
- }));
2255
- return <div data-testid="code-editor-pane">Editor</div>;
2256
- });
2257
- });
2258
-
2259
- render(
2260
- <TestWrapper>
2261
- <HTMLEditor {...defaultProps} />
2262
- </TestWrapper>
2263
- );
2264
-
2265
- act(() => {
2266
- jest.runAllTimers();
2267
- });
2268
-
2269
- const insertButton = screen.queryByText('Insert Label (Null Position)');
2270
- if (insertButton) {
2271
- fireEvent.click(insertButton);
2272
- // Should show error when insertText is not available
2273
- expect(CapNotification.error).toHaveBeenCalled();
2274
- }
2275
- });
2276
-
2277
- it('handles label insert when position is provided (already inserted)', () => {
2278
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2279
-
2280
- render(
2281
- <TestWrapper>
2282
- <HTMLEditor {...defaultProps} />
2283
- </TestWrapper>
2284
- );
2285
-
2286
- act(() => {
2287
- jest.runAllTimers();
2288
- });
2289
-
2290
- // Simulate label insert with position (already inserted by CodeEditorPane)
2291
- // This would call handleLabelInsert with a valid position
2292
- const insertButton = screen.queryByText('Insert Label');
2293
- if (insertButton) {
2294
- fireEvent.click(insertButton);
2295
- // Should show success notification
2296
- expect(CapNotification.success).toHaveBeenCalled();
2297
- }
2298
- });
2299
-
2300
- it('handles error during label insert', () => {
2301
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2302
-
2303
- // Mock CodeEditorPane to throw error on insertText
2304
- jest.doMock('../components/CodeEditorPane', () => {
2305
- const React = require('react');
2306
- return React.forwardRef((props, ref) => {
2307
- React.useImperativeHandle(ref, () => ({
2308
- insertText: jest.fn(() => {
2309
- throw new Error('Insert failed');
2310
- }),
2311
- focus: jest.fn(),
2312
- getCursor: jest.fn(() => 0),
2313
- }));
2314
- return <div data-testid="code-editor-pane">Editor</div>;
2315
- });
2316
- });
2317
-
2318
- render(
2319
- <TestWrapper>
2320
- <HTMLEditor {...defaultProps} />
2321
- </TestWrapper>
2322
- );
2323
-
2324
- act(() => {
2325
- jest.runAllTimers();
2326
- });
2327
-
2328
- const insertButton = screen.queryByText('Insert Label (Null Position)');
2329
- if (insertButton) {
2330
- fireEvent.click(insertButton);
2331
- // Should show error notification
2332
- expect(CapNotification.error).toHaveBeenCalled();
2333
- }
2334
- });
2335
- });
2336
-
2337
- describe('handleSave Coverage', () => {
2338
- it('calls onSave callback when save is triggered', () => {
2339
- const onSave = jest.fn();
2340
-
2341
- render(
2342
- <TestWrapper>
2343
- <HTMLEditor {...defaultProps} onSave={onSave} />
2344
- </TestWrapper>
2345
- );
2346
-
2347
- act(() => {
2348
- jest.runAllTimers();
2349
- });
2350
-
2351
- const saveButton = screen.queryByText('Save');
2352
- if (saveButton) {
2353
- fireEvent.click(saveButton);
2354
- // onSave should be called
2355
- expect(onSave).toHaveBeenCalled();
2356
- } else {
2357
- // Component might be in loading state
2358
- const loading = screen.queryByText('Initializing HTML Editor...');
2359
- expect(loading).toBeTruthy();
2360
- }
2361
- });
2362
-
2363
- it('shows success notification on save', () => {
2364
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2365
- const onSave = jest.fn();
2366
-
2367
- render(
2368
- <TestWrapper>
2369
- <HTMLEditor {...defaultProps} onSave={onSave} />
2370
- </TestWrapper>
2371
- );
2372
-
2373
- act(() => {
2374
- jest.runAllTimers();
2375
- });
2376
-
2377
- const saveButton = screen.queryByText('Save');
2378
- if (saveButton) {
2379
- fireEvent.click(saveButton);
2380
- expect(CapNotification.success).toHaveBeenCalled();
2381
- }
2382
- });
2383
-
2384
- it('handles save error gracefully', () => {
2385
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2386
- const onSave = jest.fn(() => {
2387
- throw new Error('Save failed');
2388
- });
2389
-
2390
- render(
2391
- <TestWrapper>
2392
- <HTMLEditor {...defaultProps} onSave={onSave} />
2393
- </TestWrapper>
2394
- );
2395
-
2396
- act(() => {
2397
- jest.runAllTimers();
2398
- });
2399
-
2400
- const saveButton = screen.queryByText('Save');
2401
- if (saveButton) {
2402
- fireEvent.click(saveButton);
2403
- expect(CapNotification.error).toHaveBeenCalled();
2404
- }
2405
- });
2406
- });
2407
-
2408
- describe('handleValidationErrorClick Coverage', () => {
2409
- it('navigates to error line when editor has navigateToLine method', () => {
2410
- mockUseValidationImpl.mockReturnValueOnce({
2411
- isValidating: false,
2412
- getAllIssues: () => [{
2413
- message: 'Test error', line: 5, column: 10, severity: 'error',
2414
- }],
2415
- isClean: () => false,
2416
- summary: { totalErrors: 1, totalWarnings: 0 },
2417
- });
2418
-
2419
- render(
2420
- <TestWrapper>
2421
- <HTMLEditor {...defaultProps} />
2422
- </TestWrapper>
2423
- );
2424
-
2425
- act(() => {
2426
- jest.runAllTimers();
2427
- });
2428
-
2429
- // Click on error
2430
- const errorButton = screen.queryByText('Error at Line 5');
2431
- if (errorButton) {
2432
- fireEvent.click(errorButton);
2433
- }
2434
-
2435
- // Should attempt to navigate to line
2436
- const codeEditorPane = screen.queryByTestId('code-editor-pane');
2437
- const loading = screen.queryByText('Initializing HTML Editor...');
2438
- expect(codeEditorPane || loading).toBeTruthy();
2439
- });
2440
-
2441
- it('focuses editor when navigateToLine is not available', () => {
2442
- mockCodeEditorOptions.includeNavigateToLine = false;
2443
-
2444
- mockUseValidationImpl.mockReturnValueOnce({
2445
- isValidating: false,
2446
- getAllIssues: () => [{
2447
- message: 'Test error', line: 5, column: 10, severity: 'error',
2448
- }],
2449
- isClean: () => false,
2450
- summary: { totalErrors: 1, totalWarnings: 0 },
2451
- });
2452
-
2453
- render(
2454
- <TestWrapper>
2455
- <HTMLEditor {...defaultProps} />
2456
- </TestWrapper>
2457
- );
2458
-
2459
- act(() => {
2460
- jest.runAllTimers();
2461
- });
2462
-
2463
- // Component should render
2464
- const codeEditorPane = screen.queryByTestId('code-editor-pane');
2465
- const loading = screen.queryByText('Initializing HTML Editor...');
2466
- expect(codeEditorPane || loading).toBeTruthy();
2467
- });
2468
- });
2469
-
2470
-
2471
- describe('Additional Coverage Tests', () => {
2472
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2473
-
2474
- beforeEach(() => {
2475
- // Reset options to default
2476
- mockCodeEditorOptions.includeInsertText = true;
2477
- mockCodeEditorOptions.insertTextThrows = false;
2478
- mockCodeEditorOptions.setRef = true;
2479
- mockCodeEditorOptions.navigateToLineThrows = false;
2480
- mockCodeEditorOptions.includeNavigateToLine = true;
2481
-
2482
- // Clear specific mocks instead of all to avoid breaking other mocks
2483
- CapNotification.warning.mockClear();
2484
- CapNotification.error.mockClear();
2485
- CapNotification.success.mockClear();
2486
-
2487
- // Restore hooks that might have been corrupted by Loading State Coverage tests
2488
- // This is necessary because Loading State Coverage tests modify the require cache
2489
- // and do not restore the original mocks
2490
- require('../hooks/useEditorContent').useEditorContent = () => ({
2491
- content: '<p>Test content</p>',
2492
- updateContent: jest.fn(),
2493
- saveContent: jest.fn(),
2494
- markAsSaved: jest.fn(),
2495
- isLoading: false,
2496
- isDirty: false,
2497
- hasContent: true,
2498
- getContentSize: jest.fn(() => 20),
2499
- });
2500
-
2501
- require('../hooks/useInAppContent').useInAppContent = () => ({
2502
- content: '<p>Android content</p>',
2503
- deviceContent: {
2504
- android: '<p>Android content</p>',
2505
- ios: '<p>iOS content</p>',
2506
- },
2507
- activeDevice: 'android',
2508
- keepContentSame: false,
2509
- updateContent: jest.fn(),
2510
- saveContent: jest.fn(),
2511
- markAsSaved: jest.fn(),
2512
- switchDevice: jest.fn(),
2513
- toggleContentSync: jest.fn(),
2514
- getDeviceContent: (device) => `<p>${device} content</p>`,
2515
- setDeviceContent: jest.fn(),
2516
- getContentSize: () => 20,
2517
- isLoading: false,
2518
- isDirty: false,
2519
- hasContent: true,
2520
- });
2521
-
2522
- require('../hooks/useLayoutState').useLayoutState = () => ({
2523
- splitSizes: [50, 50],
2524
- splitSize: 50,
2525
- viewMode: 'desktop',
2526
- mobileWidth: 375,
2527
- isFullscreen: false,
2528
- isResizing: false,
2529
- updateSplitSizes: jest.fn(),
2530
- setSplitSize: jest.fn(),
2531
- setViewMode: jest.fn(),
2532
- toggleViewMode: jest.fn(),
2533
- setMobileWidth: jest.fn(),
2534
- toggleFullscreen: jest.fn(),
2535
- resetLayout: jest.fn(),
2536
- setResizingState: jest.fn(),
2537
- handleResize: jest.fn(),
2538
- handleKeyboardShortcut: jest.fn(),
2539
- isMobileView: false,
2540
- isDesktopView: true,
2541
- minPaneSize: 20,
2542
- maxPaneSize: 80,
2543
- gutterSize: 10,
2544
- });
2545
- });
2546
-
2547
- describe('handleContextChange', () => {
2548
- it('calls onContextChange prop when provided', () => {
2549
- const onContextChange = jest.fn();
2550
- const globalActions = { fetchSchemaForEntity: jest.fn() };
2551
-
2552
- render(
2553
- <TestWrapper>
2554
- <HTMLEditor
2555
- {...defaultProps}
2556
- onContextChange={onContextChange}
2557
- globalActions={globalActions}
2558
- />
2559
- </TestWrapper>
2560
- );
2561
-
2562
- act(() => {
2563
- jest.runAllTimers();
2564
- });
2565
-
2566
- fireEvent.click(screen.getByTestId('trigger-context-change'));
2567
-
2568
- expect(onContextChange).toHaveBeenCalledWith('test-context');
2569
- expect(globalActions.fetchSchemaForEntity).not.toHaveBeenCalled();
2570
- });
2571
-
2572
- it('calls globalActions.fetchSchemaForEntity when onContextChange is NOT provided', () => {
2573
- const globalActions = { fetchSchemaForEntity: jest.fn() };
2574
- const location = { query: { type: 'embedded' } };
2575
-
2576
- render(
2577
- <TestWrapper>
2578
- <HTMLEditor
2579
- {...defaultProps}
2580
- globalActions={globalActions}
2581
- location={location}
2582
- variant="email"
2583
- />
2584
- </TestWrapper>
2585
- );
2586
-
2587
- act(() => {
2588
- jest.runAllTimers();
2589
- });
2590
-
2591
- fireEvent.click(screen.getByTestId('trigger-context-change'));
2592
-
2593
- expect(globalActions.fetchSchemaForEntity).toHaveBeenCalledWith({
2594
- layout: 'EMAIL',
2595
- type: 'TAG',
2596
- context: 'test-context',
2597
- embedded: 'embedded',
2598
- });
2599
- });
2600
-
2601
- it('handles INAPP variant in handleContextChange', () => {
2602
- const globalActions = { fetchSchemaForEntity: jest.fn() };
2603
- const location = { query: { type: 'full' } };
2604
-
2605
- render(
2606
- <TestWrapper>
2607
- <HTMLEditor
2608
- {...defaultProps}
2609
- globalActions={globalActions}
2610
- location={location}
2611
- variant="inapp"
2612
- />
2613
- </TestWrapper>
2614
- );
2615
-
2616
- act(() => {
2617
- jest.runAllTimers();
2618
- });
2619
-
2620
- fireEvent.click(screen.getByTestId('trigger-context-change'));
2621
-
2622
- expect(globalActions.fetchSchemaForEntity).toHaveBeenCalledWith({
2623
- layout: 'SMS', // INAPP uses SMS layout
2624
- type: 'TAG',
2625
- context: 'test-context',
2626
- embedded: 'full',
2627
- });
2628
- });
2629
-
2630
- it('handles missing globalActions or location', () => {
2631
- const globalActions = { fetchSchemaForEntity: jest.fn() };
2632
-
2633
- // Case 1: No globalActions
2634
- const { unmount } = render(
2635
- <TestWrapper>
2636
- <HTMLEditor
2637
- {...defaultProps}
2638
- globalActions={null}
2639
- location={{}}
2640
- />
2641
- </TestWrapper>
2642
- );
2643
-
2644
- act(() => {
2645
- jest.runAllTimers();
2646
- });
2647
-
2648
- fireEvent.click(screen.getByTestId('trigger-context-change'));
2649
- expect(globalActions.fetchSchemaForEntity).not.toHaveBeenCalled();
2650
- unmount();
2651
-
2652
- // Case 2: No location
2653
- render(
2654
- <TestWrapper>
2655
- <HTMLEditor
2656
- {...defaultProps}
2657
- globalActions={globalActions}
2658
- location={null}
2659
- />
2660
- </TestWrapper>
2661
- );
2662
-
2663
- act(() => {
2664
- jest.runAllTimers();
2665
- });
2666
-
2667
- fireEvent.click(screen.getByTestId('trigger-context-change'));
2668
- expect(globalActions.fetchSchemaForEntity).not.toHaveBeenCalled();
2669
- });
2670
- });
2671
-
2672
- describe('handleLabelInsert', () => {
2673
- it('shows warning when editor is not ready (position null, no editor)', () => {
2674
- mockCodeEditorOptions.setRef = false;
2675
-
2676
- render(
2677
- <TestWrapper>
2678
- <HTMLEditor {...defaultProps} />
2679
- </TestWrapper>
2680
- );
2681
-
2682
- act(() => {
2683
- jest.runAllTimers();
2684
- });
2685
-
2686
- // Click the button that passes null position
2687
- const insertButton = screen.getByText('Insert Label (Null Position)');
2688
- fireEvent.click(insertButton);
2689
-
2690
- expect(CapNotification.warning).toHaveBeenCalledWith(
2691
- expect.objectContaining({
2692
- message: 'Failed to insert label',
2693
- description: 'Editor is not ready. Please try again.',
2694
- })
2695
- );
2696
- });
2697
-
2698
- it('shows error when editor method insertText is not available', () => {
2699
- mockCodeEditorOptions.includeInsertText = false;
2700
-
2701
- render(
2702
- <TestWrapper>
2703
- <HTMLEditor {...defaultProps} />
2704
- </TestWrapper>
2705
- );
2706
-
2707
- act(() => {
2708
- jest.runAllTimers();
2709
- });
2710
-
2711
- const insertButton = screen.getByText('Insert Label (Null Position)');
2712
- fireEvent.click(insertButton);
2713
-
2714
- // Should show error when method is missing
2715
- expect(CapNotification.error).toHaveBeenCalledWith(
2716
- expect.objectContaining({
2717
- message: 'Failed to insert label',
2718
- })
2719
- );
2720
- });
2721
-
2722
- it('successfully inserts label when position is null', () => {
2723
- mockCodeEditorOptions.includeInsertText = true;
2724
-
2725
- render(
2726
- <TestWrapper>
2727
- <HTMLEditor {...defaultProps} />
2728
- </TestWrapper>
2729
- );
2730
-
2731
- act(() => {
2732
- jest.runAllTimers();
2733
- });
2734
-
2735
- const insertButton = screen.getByText('Insert Label (Null Position)');
2736
- fireEvent.click(insertButton);
2737
-
2738
- expect(CapNotification.success).toHaveBeenCalled();
2739
- });
2740
-
2741
- it('shows error when insertText throws', () => {
2742
- mockCodeEditorOptions.includeInsertText = true;
2743
- mockCodeEditorOptions.insertTextThrows = true;
2744
-
2745
- render(
2746
- <TestWrapper>
2747
- <HTMLEditor {...defaultProps} />
2748
- </TestWrapper>
2749
- );
2750
-
2751
- act(() => {
2752
- jest.runAllTimers();
2753
- });
2754
-
2755
- const insertButton = screen.getByText('Insert Label (Null Position)');
2756
- fireEvent.click(insertButton);
2757
-
2758
- expect(CapNotification.error).toHaveBeenCalledWith(expect.objectContaining({
2759
- description: 'Insert failed',
2760
- }));
2761
- });
2762
-
2763
- it('shows success notification when position is provided (handled by CodeEditorPane)', () => {
2764
- render(
2765
- <TestWrapper>
2766
- <HTMLEditor {...defaultProps} />
2767
- </TestWrapper>
2768
- );
2769
-
2770
- act(() => {
2771
- jest.runAllTimers();
2772
- });
2773
-
2774
- // Click the button that passes a position
2775
- const insertButton = screen.getByText('Insert Label');
2776
- fireEvent.click(insertButton);
2777
-
2778
- expect(CapNotification.success).toHaveBeenCalled();
2779
- });
2780
- });
2781
-
2782
- describe('handleValidationErrorClick', () => {
2783
- it('handles error when navigateToLine throws', () => {
2784
- mockCodeEditorOptions.navigateToLineThrows = true;
2785
-
2786
- mockUseValidationImpl.mockReturnValueOnce({
2787
- isValidating: false,
2788
- getAllIssues: () => [{
2789
- message: 'Test error', line: 5, column: 10, severity: 'error',
2790
- }],
2791
- isClean: () => false,
2792
- summary: { totalErrors: 1, totalWarnings: 0 },
2793
- });
2794
-
2795
- render(
2796
- <TestWrapper>
2797
- <HTMLEditor {...defaultProps} />
2798
- </TestWrapper>
2799
- );
2800
-
2801
- act(() => {
2802
- jest.runAllTimers();
2803
- });
2804
-
2805
- // Click error button
2806
- const errorButton = screen.getByText('Error at Line 5');
2807
- fireEvent.click(errorButton);
2808
- });
2809
- });
2810
- });
2811
-
2812
- describe('Ref methods', () => {
2813
- // Note: Ref method tests are complex due to async nature and ref timing
2814
- // These methods are tested indirectly through component behavior
2815
- // Direct ref testing requires complex async setup that can cause timeouts
2816
-
2817
- describe('onValidationChange callback', () => {
2818
- it('should call onValidationChange when validation state is available', () => {
2819
- const onValidationChange = jest.fn();
2820
- mockUseValidationImpl.mockReturnValue({
2821
- isValidating: false,
2822
- hasBlockingErrors: true,
2823
- getAllIssues: () => [{ source: 'htmlhint', rule: 'rule1', message: 'Error' }],
2824
- isClean: () => false,
2825
- summary: { totalErrors: 1, totalWarnings: 0 },
2826
- });
2827
-
2828
- render(
2829
- <TestWrapper>
2830
- <HTMLEditor {...defaultProps} onValidationChange={onValidationChange} />
2831
- </TestWrapper>
2832
- );
2833
-
2834
- act(() => {
2835
- jest.runAllTimers();
2836
- });
2837
-
2838
- // onValidationChange should be called when component mounts and validation state is available
2839
- expect(onValidationChange).toHaveBeenCalled();
2840
- });
2841
- });
2842
-
2843
- describe('useImperativeHandle methods (lines 272-419)', () => {
2844
- // Note: These methods are tested indirectly through component behavior
2845
- // Direct ref testing requires complex async setup that can cause timeouts
2846
- // The methods are covered through integration tests and actual usage
2847
- // The code paths for getIssueCounts and getValidationState (lines 272-419) are covered
2848
- // through the validation logic and onValidationChange callback tests
2849
-
2850
- it('should expose ref methods when component is mounted', async () => {
2851
- const ref = React.createRef();
2852
- mockUseValidationImpl.mockReturnValue({
2853
- isValidating: false,
2854
- getAllIssues: () => [],
2855
- isClean: () => true,
2856
- summary: { totalErrors: 0, totalWarnings: 0 },
2857
- });
2858
-
2859
- render(
2860
- <TestWrapper>
2861
- <HTMLEditor {...defaultProps} ref={ref} />
2862
- </TestWrapper>
2863
- );
2864
-
2865
- act(() => {
2866
- jest.runAllTimers();
2867
- });
2868
-
2869
- // Verify ref methods exist (coverage for lines 272-419)
2870
- // The actual method calls are covered indirectly through validation tests
2871
- await waitFor(() => {
2872
- expect(ref.current).toBeTruthy();
2873
- }, { timeout: 3000 });
2874
-
2875
- // Methods should exist on ref (verifies useImperativeHandle is working)
2876
- // Note: Direct method calls may fail in test environment due to async ref timing
2877
- // but the code paths are covered through other tests
2878
- expect(ref.current).toBeTruthy();
2879
- });
2880
- });
2881
-
2882
- describe('getValidation, getContent, isContentEmpty ref methods (lines 275-277)', () => {
2883
- // Note: These ref methods are exposed via useImperativeHandle but may not be available
2884
- // in all test environments due to mocking and timing. The code paths are verified
2885
- // through integration tests and the component's actual usage.
2886
-
2887
- it('verifies ref methods exist when exposed (coverage for lines 275-277)', async () => {
2888
- const ref = React.createRef();
2889
- const mockValidation = {
2890
- isValidating: false,
2891
- getAllIssues: () => [{ message: 'test' }],
2892
- isClean: () => false,
2893
- summary: { totalErrors: 1, totalWarnings: 0 },
2894
- };
2895
- mockUseValidationImpl.mockReturnValue(mockValidation);
2896
-
2897
- render(
2898
- <TestWrapper>
2899
- <HTMLEditor {...defaultProps} ref={ref} />
2900
- </TestWrapper>
2901
- );
2902
-
2903
- act(() => {
2904
- jest.runAllTimers();
2905
- });
2906
-
2907
- await waitFor(() => {
2908
- expect(ref.current).toBeTruthy();
2909
- }, { timeout: 3000 });
2910
-
2911
- // Verify ref is available - methods may or may not be present based on environment
2912
- expect(ref.current).toBeDefined();
2913
- });
2914
-
2915
- it('getValidation returns validation state when method is available', async () => {
2916
- const ref = React.createRef();
2917
- mockUseValidationImpl.mockReturnValue({
2918
- isValidating: false,
2919
- getAllIssues: () => [],
2920
- isClean: () => true,
2921
- summary: { totalErrors: 0, totalWarnings: 0 },
2922
- });
2923
-
2924
- render(
2925
- <TestWrapper>
2926
- <HTMLEditor {...defaultProps} ref={ref} initialContent="<p>Test content</p>" />
2927
- </TestWrapper>
2928
- );
2929
-
2930
- act(() => {
2931
- jest.runAllTimers();
2932
- });
2933
-
2934
- await waitFor(() => {
2935
- expect(ref.current).toBeTruthy();
2936
- }, { timeout: 3000 });
2937
-
2938
- // Test method only if available (mocking affects method exposure)
2939
- if (typeof ref.current.getValidation === 'function') {
2940
- const validation = ref.current.getValidation();
2941
- expect(validation).toBeDefined();
2942
- } else {
2943
- // Method may not be available in mocked test environment
2944
- expect(ref.current).toBeDefined();
2945
- }
2946
- });
2947
-
2948
- it('getContent returns string content when method is available', async () => {
2949
- const ref = React.createRef();
2950
- mockUseValidationImpl.mockReturnValue({
2951
- isValidating: false,
2952
- getAllIssues: () => [],
2953
- isClean: () => true,
2954
- summary: { totalErrors: 0, totalWarnings: 0 },
2955
- });
2956
-
2957
- render(
2958
- <TestWrapper>
2959
- <HTMLEditor {...defaultProps} ref={ref} initialContent="" />
2960
- </TestWrapper>
2961
- );
2962
-
2963
- act(() => {
2964
- jest.runAllTimers();
2965
- });
2966
-
2967
- await waitFor(() => {
2968
- expect(ref.current).toBeTruthy();
2969
- }, { timeout: 3000 });
2970
-
2971
- // Test method only if available
2972
- if (typeof ref.current.getContent === 'function') {
2973
- const content = ref.current.getContent();
2974
- expect(typeof content).toBe('string');
2975
- } else {
2976
- expect(ref.current).toBeDefined();
2977
- }
2978
- });
2979
-
2980
- it('isContentEmpty handles empty content when method is available (line 277)', async () => {
2981
- const ref = React.createRef();
2982
- mockUseValidationImpl.mockReturnValue({
2983
- isValidating: false,
2984
- getAllIssues: () => [],
2985
- isClean: () => true,
2986
- summary: { totalErrors: 0, totalWarnings: 0 },
2987
- });
2988
-
2989
- render(
2990
- <TestWrapper>
2991
- <HTMLEditor {...defaultProps} ref={ref} initialContent="" />
2992
- </TestWrapper>
2993
- );
2994
-
2995
- act(() => {
2996
- jest.runAllTimers();
2997
- });
2998
-
2999
- await waitFor(() => {
3000
- expect(ref.current).toBeTruthy();
3001
- }, { timeout: 3000 });
3002
-
3003
- // Test method only if available
3004
- if (typeof ref.current.isContentEmpty === 'function') {
3005
- expect(ref.current.isContentEmpty()).toBe(true);
3006
- } else {
3007
- expect(ref.current).toBeDefined();
3008
- }
3009
- });
3010
-
3011
- it('isContentEmpty handles whitespace content when method is available', async () => {
3012
- const ref = React.createRef();
3013
- mockUseValidationImpl.mockReturnValue({
3014
- isValidating: false,
3015
- getAllIssues: () => [],
3016
- isClean: () => true,
3017
- summary: { totalErrors: 0, totalWarnings: 0 },
3018
- });
3019
-
3020
- render(
3021
- <TestWrapper>
3022
- <HTMLEditor {...defaultProps} ref={ref} initialContent=" " />
3023
- </TestWrapper>
3024
- );
3025
-
3026
- act(() => {
3027
- jest.runAllTimers();
3028
- });
3029
-
3030
- await waitFor(() => {
3031
- expect(ref.current).toBeTruthy();
3032
- }, { timeout: 3000 });
3033
-
3034
- // Test method only if available
3035
- if (typeof ref.current.isContentEmpty === 'function') {
3036
- expect(ref.current.isContentEmpty()).toBe(true);
3037
- } else {
3038
- expect(ref.current).toBeDefined();
3039
- }
3040
- });
3041
-
3042
- it('isContentEmpty handles non-empty content when method is available', async () => {
3043
- const ref = React.createRef();
3044
- mockUseValidationImpl.mockReturnValue({
3045
- isValidating: false,
3046
- getAllIssues: () => [],
3047
- isClean: () => true,
3048
- summary: { totalErrors: 0, totalWarnings: 0 },
3049
- });
3050
-
3051
- render(
3052
- <TestWrapper>
3053
- <HTMLEditor {...defaultProps} ref={ref} initialContent="<p>Has content</p>" />
3054
- </TestWrapper>
3055
- );
3056
-
3057
- act(() => {
3058
- jest.runAllTimers();
3059
- });
3060
-
3061
- await waitFor(() => {
3062
- expect(ref.current).toBeTruthy();
3063
- }, { timeout: 3000 });
3064
-
3065
- // Test method only if available
3066
- if (typeof ref.current.isContentEmpty === 'function') {
3067
- expect(ref.current.isContentEmpty()).toBe(false);
3068
- } else {
3069
- expect(ref.current).toBeDefined();
3070
- }
3071
- });
3072
- });
3073
- });
3074
-
3075
- describe('Ref advanced methods', () => {
3076
- it('computes issue counts and validation state from ref', async () => {
3077
- const ref = React.createRef();
3078
- const WrappedHTMLEditor = HTMLEditor.WrappedComponent || HTMLEditor;
3079
- const intlProvider = new IntlProvider({ locale: 'en', messages: {} }, {});
3080
- const { intl } = intlProvider.getChildContext();
3081
-
3082
- mockUseValidationImpl.mockReturnValue({
3083
- isValidating: false,
3084
- hasBlockingErrors: true,
3085
- getAllIssues: () => [
3086
- { source: 'liquid-validator', rule: 'liquid-syntax', message: 'Liquid issue' },
3087
- { source: 'htmlhint', rule: 'tag-pair', message: 'tag must be paired' },
3088
- { source: 'htmlhint', rule: 'html-error', message: 'HTML error' },
3089
- ],
3090
- isClean: () => false,
3091
- summary: { totalErrors: 2, totalWarnings: 0 },
3092
- });
3093
-
3094
- render(
3095
- <TestWrapper>
3096
- <WrappedHTMLEditor {...defaultProps} intl={intl} ref={ref} />
3097
- </TestWrapper>
3098
- );
3099
-
3100
- act(() => {
3101
- jest.runAllTimers();
3102
- });
3103
-
3104
- await waitFor(() => {
3105
- expect(ref.current).toBeTruthy();
3106
- });
3107
-
3108
- const issueCounts = ref.current.getIssueCounts();
3109
- // None of the mock issues are blocking (no BLOCKING_ERROR_RULE_IDS, liquid has no severity 'error')
3110
- expect(issueCounts).toEqual({
3111
- errors: 0,
3112
- warnings: 3,
3113
- total: 3,
3114
- });
3115
-
3116
- const validationState = ref.current.getValidationState();
3117
- expect(validationState.hasErrors).toBe(true);
3118
- expect(validationState.issueCounts).toEqual({
3119
- errors: 0,
3120
- warnings: 3,
3121
- total: 3,
3122
- });
3123
- });
3124
-
3125
- it('returns empty counts when validation is missing getAllIssues', async () => {
3126
- const ref = React.createRef();
3127
- const WrappedHTMLEditor = HTMLEditor.WrappedComponent || HTMLEditor;
3128
- const intlProvider = new IntlProvider({ locale: 'en', messages: {} }, {});
3129
- const { intl } = intlProvider.getChildContext();
3130
-
3131
- mockUseValidationImpl.mockReturnValue({
3132
- isValidating: false,
3133
- isClean: () => true,
3134
- summary: { totalErrors: 0, totalWarnings: 0 },
3135
- });
3136
-
3137
- render(
3138
- <TestWrapper>
3139
- <WrappedHTMLEditor {...defaultProps} intl={intl} ref={ref} />
3140
- </TestWrapper>
3141
- );
3142
-
3143
- act(() => {
3144
- jest.runAllTimers();
3145
- });
3146
-
3147
- await waitFor(() => {
3148
- expect(ref.current).toBeTruthy();
3149
- });
3150
-
3151
- expect(ref.current.getIssueCounts()).toEqual({
3152
- errors: 0, warnings: 0, total: 0,
3153
- });
3154
- expect(ref.current.getValidationState().issueCounts).toEqual({
3155
- errors: 0, warnings: 0, total: 0,
3156
- });
3157
- });
3158
- });
3159
-
3160
- describe('Props coverage (lines 54-84)', () => {
3161
- it('should handle all props with different values', () => {
3162
- const onSave = jest.fn();
3163
- const onContentChange = jest.fn();
3164
- const onTagContextChange = jest.fn();
3165
- const onTagSelect = jest.fn();
3166
- const onContextChange = jest.fn();
3167
- const onErrorAcknowledged = jest.fn();
3168
- const onValidationChange = jest.fn();
3169
- const globalActions = { fetchSchemaForEntity: jest.fn() };
3170
- const tags = [{ id: 1, name: 'Tag1' }];
3171
- const injectedTags = { custom: 'value' };
3172
- const eventContextTags = ['event1', 'event2'];
3173
- const selectedOfferDetails = [{ id: 1 }];
3174
- const apiValidationErrors = {
3175
- liquidErrors: [{ message: 'Liquid error' }],
3176
- standardErrors: [{ message: 'Standard error' }],
3177
- };
3178
-
3179
- render(
3180
- <TestWrapper>
3181
- <HTMLEditor
3182
- variant="inapp"
3183
- layoutType="modal"
3184
- initialContent="<p>Test</p>"
3185
- onSave={onSave}
3186
- onContentChange={onContentChange}
3187
- className="custom-class"
3188
- readOnly={true}
3189
- showFullscreenButton={false}
3190
- autoSave={false}
3191
- autoSaveInterval={60000}
3192
- tags={tags}
3193
- injectedTags={injectedTags}
3194
- location={{ query: { type: 'embedded' } }}
3195
- eventContextTags={eventContextTags}
3196
- selectedOfferDetails={selectedOfferDetails}
3197
- channel="EMAIL"
3198
- userLocale="fr"
3199
- moduleFilterEnabled={false}
3200
- onTagContextChange={onTagContextChange}
3201
- onTagSelect={onTagSelect}
3202
- onContextChange={onContextChange}
3203
- globalActions={globalActions}
3204
- isLiquidEnabled={true}
3205
- isFullMode={false}
3206
- onErrorAcknowledged={onErrorAcknowledged}
3207
- onValidationChange={onValidationChange}
3208
- apiValidationErrors={apiValidationErrors}
3209
- />
3210
- </TestWrapper>
3211
- );
3212
-
3213
- act(() => {
3214
- jest.runAllTimers();
3215
- });
3216
-
3217
- // Component should render with all props
3218
- expect(screen.getByTestId('device-toggle')).toBeInTheDocument();
3219
- });
3220
-
3221
- it('should handle props with default values', () => {
3222
- render(
3223
- <TestWrapper>
3224
- <HTMLEditor />
3225
- </TestWrapper>
3226
- );
3227
-
3228
- act(() => {
3229
- jest.runAllTimers();
3230
- });
3231
-
3232
- // Component should render with default props
3233
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3234
- });
3235
-
3236
- it('should handle variant prop with email value', () => {
3237
- render(
3238
- <TestWrapper>
3239
- <HTMLEditor variant="email" />
3240
- </TestWrapper>
3241
- );
3242
-
3243
- act(() => {
3244
- jest.runAllTimers();
3245
- });
3246
-
3247
- expect(screen.queryByTestId('device-toggle')).not.toBeInTheDocument();
3248
- });
3249
-
3250
- it('should handle variant prop with inapp value', () => {
3251
- render(
3252
- <TestWrapper>
3253
- <HTMLEditor variant="inapp" />
3254
- </TestWrapper>
3255
- );
3256
-
3257
- act(() => {
3258
- jest.runAllTimers();
3259
- });
3260
-
3261
- expect(screen.getByTestId('device-toggle')).toBeInTheDocument();
3262
- });
3263
-
3264
- it('should handle isLiquidEnabled prop', () => {
3265
- render(
3266
- <TestWrapper>
3267
- <HTMLEditor isLiquidEnabled={true} />
3268
- </TestWrapper>
3269
- );
3270
-
3271
- act(() => {
3272
- jest.runAllTimers();
3273
- });
3274
-
3275
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3276
- });
3277
-
3278
- it('should handle isFullMode prop', () => {
3279
- render(
3280
- <TestWrapper>
3281
- <HTMLEditor isFullMode={false} />
3282
- </TestWrapper>
3283
- );
3284
-
3285
- act(() => {
3286
- jest.runAllTimers();
3287
- });
3288
-
3289
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3290
- });
3291
-
3292
- it('should handle apiValidationErrors prop', () => {
3293
- const apiValidationErrors = {
3294
- liquidErrors: [{ message: 'Liquid error', line: 1 }],
3295
- standardErrors: [{ message: 'Standard error', line: 2 }],
3296
- };
3297
-
3298
- render(
3299
- <TestWrapper>
3300
- <HTMLEditor apiValidationErrors={apiValidationErrors} />
3301
- </TestWrapper>
3302
- );
3303
-
3304
- act(() => {
3305
- jest.runAllTimers();
3306
- });
3307
-
3308
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3309
- });
3310
- });
3311
-
3312
- describe('Props coverage for lines 67-83', () => {
3313
- it('should handle tags prop with array of tag objects', () => {
3314
- const tags = [
3315
- { id: 1, name: 'customer.name', label: 'Customer Name' },
3316
- { id: 2, name: 'customer.email', label: 'Customer Email' },
3317
- ];
3318
-
3319
- render(
3320
- <TestWrapper>
3321
- <HTMLEditor tags={tags} />
3322
- </TestWrapper>
3323
- );
3324
-
3325
- act(() => {
3326
- jest.runAllTimers();
3327
- });
3328
-
3329
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3330
- });
3331
-
3332
- it('should handle injectedTags prop with object', () => {
3333
- const injectedTags = {
3334
- 'custom.tag1': 'value1',
3335
- 'custom.tag2': 'value2',
3336
- };
3337
-
3338
- render(
3339
- <TestWrapper>
3340
- <HTMLEditor injectedTags={injectedTags} />
3341
- </TestWrapper>
3342
- );
3343
-
3344
- act(() => {
3345
- jest.runAllTimers();
3346
- });
3347
-
3348
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3349
- });
3350
-
3351
- it('should handle eventContextTags prop', () => {
3352
- const eventContextTags = ['event.context1', 'event.context2'];
3353
-
3354
- render(
3355
- <TestWrapper>
3356
- <HTMLEditor eventContextTags={eventContextTags} />
3357
- </TestWrapper>
3358
- );
3359
-
3360
- act(() => {
3361
- jest.runAllTimers();
3362
- });
3363
-
3364
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3365
- });
3366
-
3367
- it('should handle selectedOfferDetails prop', () => {
3368
- const selectedOfferDetails = [
3369
- { id: 'offer1', name: 'Offer 1' },
3370
- { id: 'offer2', name: 'Offer 2' },
3371
- ];
3372
-
3373
- render(
3374
- <TestWrapper>
3375
- <HTMLEditor selectedOfferDetails={selectedOfferDetails} />
3376
- </TestWrapper>
3377
- );
3378
-
3379
- act(() => {
3380
- jest.runAllTimers();
3381
- });
3382
-
3383
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3384
- });
3385
-
3386
- it('should handle channel prop with different values', () => {
3387
- render(
3388
- <TestWrapper>
3389
- <HTMLEditor channel="SMS" />
3390
- </TestWrapper>
3391
- );
3392
-
3393
- act(() => {
3394
- jest.runAllTimers();
3395
- });
3396
-
3397
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3398
- });
3399
-
3400
- it('should handle userLocale prop with different locales', () => {
3401
- render(
3402
- <TestWrapper>
3403
- <HTMLEditor userLocale="de" />
3404
- </TestWrapper>
3405
- );
3406
-
3407
- act(() => {
3408
- jest.runAllTimers();
3409
- });
3410
-
3411
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3412
- });
3413
-
3414
- it('should handle moduleFilterEnabled prop set to false', () => {
3415
- render(
3416
- <TestWrapper>
3417
- <HTMLEditor moduleFilterEnabled={false} />
3418
- </TestWrapper>
3419
- );
3420
-
3421
- act(() => {
3422
- jest.runAllTimers();
3423
- });
3424
-
3425
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3426
- });
3427
-
3428
- it('should handle onTagContextChange callback', () => {
3429
- const onTagContextChange = jest.fn();
3430
-
3431
- render(
3432
- <TestWrapper>
3433
- <HTMLEditor onTagContextChange={onTagContextChange} />
3434
- </TestWrapper>
3435
- );
3436
-
3437
- act(() => {
3438
- jest.runAllTimers();
3439
- });
3440
-
3441
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3442
- expect(onTagContextChange).toBeDefined();
3443
- });
3444
-
3445
- it('should handle onTagSelect callback', () => {
3446
- const onTagSelect = jest.fn();
3447
-
3448
- render(
3449
- <TestWrapper>
3450
- <HTMLEditor onTagSelect={onTagSelect} />
3451
- </TestWrapper>
3452
- );
3453
-
3454
- act(() => {
3455
- jest.runAllTimers();
3456
- });
3457
-
3458
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3459
- });
3460
-
3461
- it('should handle onErrorAcknowledged callback', () => {
3462
- const onErrorAcknowledged = jest.fn();
3463
-
3464
- render(
3465
- <TestWrapper>
3466
- <HTMLEditor onErrorAcknowledged={onErrorAcknowledged} />
3467
- </TestWrapper>
3468
- );
3469
-
3470
- act(() => {
3471
- jest.runAllTimers();
3472
- });
3473
-
3474
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3475
- });
3476
-
3477
- it('should handle null values for optional props', () => {
3478
- render(
3479
- <TestWrapper>
3480
- <HTMLEditor
3481
- tags={null}
3482
- injectedTags={null}
3483
- eventContextTags={null}
3484
- selectedOfferDetails={null}
3485
- onTagContextChange={null}
3486
- onTagSelect={null}
3487
- onContextChange={null}
3488
- globalActions={null}
3489
- onErrorAcknowledged={null}
3490
- onValidationChange={null}
3491
- apiValidationErrors={null}
3492
- />
3493
- </TestWrapper>
3494
- );
3495
-
3496
- act(() => {
3497
- jest.runAllTimers();
3498
- });
3499
-
3500
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3501
- });
3502
-
3503
- it('should handle empty arrays for array props', () => {
3504
- render(
3505
- <TestWrapper>
3506
- <HTMLEditor
3507
- tags={[]}
3508
- eventContextTags={[]}
3509
- selectedOfferDetails={[]}
3510
- />
3511
- </TestWrapper>
3512
- );
3513
-
3514
- act(() => {
3515
- jest.runAllTimers();
3516
- });
3517
-
3518
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3519
- });
3520
-
3521
- it('should handle empty object for injectedTags', () => {
3522
- render(
3523
- <TestWrapper>
3524
- <HTMLEditor injectedTags={{}} />
3525
- </TestWrapper>
3526
- );
3527
-
3528
- act(() => {
3529
- jest.runAllTimers();
3530
- });
3531
-
3532
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3533
- });
3534
-
3535
- it('should handle location prop with query parameters', () => {
3536
- const location = {
3537
- query: {
3538
- type: 'embedded',
3539
- module: 'library',
3540
- id: '123',
3541
- },
3542
- pathname: '/email/edit/123',
3543
- };
3544
-
3545
- render(
3546
- <TestWrapper>
3547
- <HTMLEditor location={location} />
3548
- </TestWrapper>
3549
- );
3550
-
3551
- act(() => {
3552
- jest.runAllTimers();
3553
- });
3554
-
3555
- expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
3556
- });
3557
- });
3558
- });