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