@capillarytech/creatives-library 8.0.236-alpha.7 → 8.0.236

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 (86) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/constants/unified.js +1 -1
  4. package/initialReducer.js +0 -2
  5. package/package.json +1 -1
  6. package/services/api.js +0 -5
  7. package/services/tests/api.test.js +0 -18
  8. package/utils/common.js +2 -1
  9. package/utils/commonUtils.js +1 -14
  10. package/utils/tests/commonUtil.test.js +0 -224
  11. package/utils/transformTemplateConfig.js +10 -0
  12. package/v2Components/CapDeviceContent/index.js +56 -61
  13. package/v2Components/CapTagList/index.js +0 -4
  14. package/v2Components/CapWhatsappCTA/tests/index.test.js +0 -5
  15. package/v2Components/HtmlEditor/HTMLEditor.js +83 -235
  16. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +19 -932
  17. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +12 -17
  18. package/v2Components/HtmlEditor/_htmlEditor.scss +4 -2
  19. package/v2Components/HtmlEditor/_index.lazy.scss +1 -0
  20. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +101 -2
  21. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +131 -105
  22. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +1 -2
  23. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  24. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +0 -1
  25. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +7 -4
  26. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +45 -35
  27. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +3 -1
  28. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  29. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +6 -7
  30. package/v2Components/HtmlEditor/constants.js +20 -29
  31. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +16 -373
  32. package/v2Components/HtmlEditor/hooks/useInAppContent.js +148 -130
  33. package/v2Components/HtmlEditor/index.js +1 -1
  34. package/v2Components/HtmlEditor/messages.js +85 -85
  35. package/v2Components/MobilePushPreviewV2/index.js +7 -32
  36. package/v2Components/TemplatePreview/_templatePreview.scss +24 -44
  37. package/v2Components/TemplatePreview/index.js +32 -47
  38. package/v2Components/TemplatePreview/messages.js +0 -4
  39. package/v2Containers/BeeEditor/index.js +80 -82
  40. package/v2Containers/CreativesContainer/SlideBoxContent.js +34 -69
  41. package/v2Containers/CreativesContainer/SlideBoxHeader.js +1 -2
  42. package/v2Containers/CreativesContainer/constants.js +0 -1
  43. package/v2Containers/CreativesContainer/index.js +32 -92
  44. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +12 -4
  45. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +0 -15
  46. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +74 -40
  47. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +67 -2
  48. package/v2Containers/InApp/actions.js +0 -7
  49. package/v2Containers/InApp/constants.js +4 -20
  50. package/v2Containers/InApp/index.js +386 -984
  51. package/v2Containers/InApp/index.scss +3 -4
  52. package/v2Containers/InApp/messages.js +3 -7
  53. package/v2Containers/InApp/reducer.js +3 -21
  54. package/v2Containers/InApp/sagas.js +9 -29
  55. package/v2Containers/InApp/selectors.js +5 -25
  56. package/v2Containers/InApp/tests/index.test.js +50 -154
  57. package/v2Containers/InApp/tests/reducer.test.js +0 -34
  58. package/v2Containers/InApp/tests/sagas.test.js +9 -61
  59. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +0 -3
  60. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +0 -2
  61. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +0 -2
  62. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +0 -9
  63. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +0 -12
  64. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +0 -4
  65. package/v2Containers/TagList/index.js +1 -65
  66. package/v2Containers/Templates/_templates.scss +1 -60
  67. package/v2Containers/Templates/index.js +5 -99
  68. package/v2Containers/Templates/messages.js +0 -4
  69. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +0 -35
  70. package/v2Containers/BeePopupEditor/constants.js +0 -10
  71. package/v2Containers/BeePopupEditor/index.js +0 -193
  72. package/v2Containers/BeePopupEditor/tests/index.test.js +0 -627
  73. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +0 -376
  74. package/v2Containers/InApp/__tests__/sagas.test.js +0 -363
  75. package/v2Containers/InApp/tests/selectors.test.js +0 -612
  76. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +0 -162
  77. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +0 -267
  78. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +0 -9
  79. package/v2Containers/InAppWrapper/constants.js +0 -16
  80. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +0 -473
  81. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +0 -198
  82. package/v2Containers/InAppWrapper/index.js +0 -148
  83. package/v2Containers/InAppWrapper/messages.js +0 -49
  84. package/v2Containers/InappAdvance/index.js +0 -1115
  85. package/v2Containers/InappAdvance/index.scss +0 -10
  86. package/v2Containers/InappAdvance/tests/index.test.js +0 -448
@@ -10,32 +10,22 @@ import '@testing-library/jest-dom';
10
10
  import { IntlProvider } from 'react-intl';
11
11
  import HTMLEditor from '../HTMLEditor';
12
12
 
13
- // Options to control CodeEditorPane mock behavior
14
- const mockCodeEditorOptions = {
15
- includeInsertText: true,
16
- insertTextThrows: false,
17
- setRef: true,
18
- navigateToLineThrows: false,
19
- includeNavigateToLine: true,
20
- };
21
-
22
-
23
13
  // Mock useLayoutEffect to behave like useEffect in tests
24
14
  React.useLayoutEffect = React.useEffect;
25
15
 
26
16
  // Mock browser APIs
27
17
  global.IntersectionObserver = class IntersectionObserver {
28
- constructor() { }
29
- disconnect() { }
30
- observe() { }
31
- unobserve() { }
18
+ constructor() {}
19
+ disconnect() {}
20
+ observe() {}
21
+ unobserve() {}
32
22
  };
33
23
 
34
24
  global.ResizeObserver = class ResizeObserver {
35
- constructor() { }
36
- disconnect() { }
37
- observe() { }
38
- unobserve() { }
25
+ constructor() {}
26
+ disconnect() {}
27
+ observe() {}
28
+ unobserve() {}
39
29
  };
40
30
 
41
31
  // Setup window.matchMedia mock
@@ -110,9 +100,6 @@ jest.mock('../components/EditorToolbar', () => {
110
100
  <button onClick={() => props.onLabelInsert && props.onLabelInsert('test-label', 10)}>
111
101
  Insert Label
112
102
  </button>
113
- <button onClick={() => props.onLabelInsert && props.onLabelInsert('test-label', null)}>
114
- Insert Label (Null Position)
115
- </button>
116
103
  <button onClick={() => props.onSave && props.onSave()}>
117
104
  Save
118
105
  </button>
@@ -142,42 +129,19 @@ jest.mock('../components/SplitContainer', () => {
142
129
  };
143
130
  });
144
131
 
145
-
146
132
  jest.mock('../components/CodeEditorPane', () => {
147
133
  const React = require('react');
148
134
  return React.forwardRef(function MockCodeEditorPane(props, ref) {
149
135
  const [value, setValue] = React.useState('');
150
136
 
151
- React.useImperativeHandle(ref, () => {
152
- if (!mockCodeEditorOptions.setRef) {
153
- return null;
154
- }
155
-
156
- const methods = {
157
- focus: jest.fn(),
158
- getCursor: jest.fn(() => 0),
159
- getValue: jest.fn(() => value),
160
- setValue: jest.fn((newValue) => setValue(newValue)),
161
- };
162
-
163
- if (mockCodeEditorOptions.includeNavigateToLine) {
164
- methods.navigateToLine = jest.fn(() => {
165
- if (mockCodeEditorOptions.navigateToLineThrows) {
166
- throw new Error('Navigation failed');
167
- }
168
- });
169
- }
170
-
171
- if (mockCodeEditorOptions.includeInsertText) {
172
- methods.insertText = jest.fn(() => {
173
- if (mockCodeEditorOptions.insertTextThrows) {
174
- throw new Error('Insert failed');
175
- }
176
- });
177
- }
178
-
179
- return methods;
180
- });
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
+ }));
181
145
 
182
146
  return (
183
147
  <div data-testid="code-editor-pane">
@@ -191,12 +155,6 @@ jest.mock('../components/CodeEditorPane', () => {
191
155
  }}
192
156
  data-testid="editor-textarea"
193
157
  />
194
- <button
195
- onClick={() => props.onContextChange && props.onContextChange('test-context')}
196
- data-testid="trigger-context-change"
197
- >
198
- Trigger Context Change
199
- </button>
200
158
  </div>
201
159
  );
202
160
  });
@@ -515,40 +473,6 @@ describe('HTMLEditor', () => {
515
473
  expect(screen.getByTestId('device-toggle')).toBeInTheDocument();
516
474
  });
517
475
 
518
- it('converts string initialContent to device-specific format for inapp variant', () => {
519
- // This test covers lines 104-109 in HTMLEditor.js
520
- const mockUseInAppContent = require('../hooks/useInAppContent').useInAppContent;
521
- const originalUseInAppContent = jest.requireActual('../hooks/useInAppContent').useInAppContent;
522
-
523
- let capturedInitialContent = null;
524
- jest.spyOn(require('../hooks/useInAppContent'), 'useInAppContent').mockImplementation((initialContent) => {
525
- capturedInitialContent = initialContent;
526
- return originalUseInAppContent(initialContent, {});
527
- });
528
-
529
- render(
530
- <TestWrapper>
531
- <HTMLEditor
532
- {...defaultProps}
533
- variant="inapp"
534
- initialContent="<p>String content</p>"
535
- />
536
- </TestWrapper>
537
- );
538
-
539
- act(() => {
540
- jest.runAllTimers();
541
- });
542
-
543
- // Verify that string content was converted to device-specific format
544
- expect(capturedInitialContent).toEqual({
545
- android: '<p>String content</p>',
546
- ios: '<p>String content</p>'
547
- });
548
-
549
- jest.restoreAllMocks();
550
- });
551
-
552
476
  it('handles object initialContent for inapp variant', () => {
553
477
  const deviceContent = {
554
478
  android: '<p>Android content</p>',
@@ -572,42 +496,6 @@ describe('HTMLEditor', () => {
572
496
  expect(screen.getByTestId('device-toggle')).toBeInTheDocument();
573
497
  });
574
498
 
575
- it('uses provided device-specific content for inapp variant', () => {
576
- // This test covers lines 110-113 in HTMLEditor.js
577
- const deviceContent = {
578
- android: '<p>Android content</p>',
579
- ios: '<p>iOS content</p>'
580
- };
581
-
582
- const mockUseInAppContent = require('../hooks/useInAppContent').useInAppContent;
583
- const originalUseInAppContent = jest.requireActual('../hooks/useInAppContent').useInAppContent;
584
-
585
- let capturedInitialContent = null;
586
- jest.spyOn(require('../hooks/useInAppContent'), 'useInAppContent').mockImplementation((initialContent) => {
587
- capturedInitialContent = initialContent;
588
- return originalUseInAppContent(initialContent, {});
589
- });
590
-
591
- render(
592
- <TestWrapper>
593
- <HTMLEditor
594
- {...defaultProps}
595
- variant="inapp"
596
- initialContent={deviceContent}
597
- />
598
- </TestWrapper>
599
- );
600
-
601
- act(() => {
602
- jest.runAllTimers();
603
- });
604
-
605
- // Verify that object content was passed as-is
606
- expect(capturedInitialContent).toEqual(deviceContent);
607
-
608
- jest.restoreAllMocks();
609
- });
610
-
611
499
  it('handles inapp variant with layoutType', () => {
612
500
  render(
613
501
  <TestWrapper>
@@ -969,7 +857,7 @@ describe('HTMLEditor', () => {
969
857
  describe('Error Handling', () => {
970
858
  it('handles missing editorRef gracefully', () => {
971
859
  // Mock console.warn to avoid noise in tests
972
- const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
860
+ const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
973
861
 
974
862
  render(
975
863
  <TestWrapper>
@@ -1438,8 +1326,8 @@ describe('HTMLEditor', () => {
1438
1326
  <div className="html-editor html-editor--email">
1439
1327
  <CustomToolbar
1440
1328
  onLabelInsert={handleLabelInsert}
1441
- onToggleFullscreen={() => { }}
1442
- onSave={() => { }}
1329
+ onToggleFullscreen={() => {}}
1330
+ onSave={() => {}}
1443
1331
  />
1444
1332
  <div data-testid="split-container">
1445
1333
  <div data-testid="code-editor-pane" ref={editorRef}>
@@ -1917,806 +1805,5 @@ describe('HTMLEditor', () => {
1917
1805
  unmount();
1918
1806
  });
1919
1807
  });
1920
-
1921
- describe('handleContextChange Coverage', () => {
1922
- it('calls onContextChange when provided instead of making API call', () => {
1923
- const onContextChange = jest.fn();
1924
- const globalActions = {
1925
- fetchSchemaForEntity: jest.fn(),
1926
- };
1927
-
1928
- render(
1929
- <TestWrapper>
1930
- <HTMLEditor
1931
- {...defaultProps}
1932
- onContextChange={onContextChange}
1933
- globalActions={globalActions}
1934
- location={{ query: { type: 'embedded' } }}
1935
- />
1936
- </TestWrapper>
1937
- );
1938
-
1939
- act(() => {
1940
- jest.runAllTimers();
1941
- });
1942
-
1943
- // Wait for component to render
1944
- const codeEditorPane = screen.queryByTestId('code-editor-pane');
1945
- if (!codeEditorPane) {
1946
- // Component might be in loading state, wait a bit more
1947
- act(() => {
1948
- jest.runAllTimers();
1949
- });
1950
- }
1951
-
1952
- // The CodeEditorPane would call onContextChange
1953
- // We verify that onContextChange is passed and would be called
1954
- expect(onContextChange).toBeDefined();
1955
-
1956
- // If onContextChange is provided, globalActions.fetchSchemaForEntity should not be called
1957
- // This is tested by ensuring onContextChange is used instead
1958
- });
1959
-
1960
- it('makes API call when onContextChange is not provided but globalActions is available', () => {
1961
- const globalActions = {
1962
- fetchSchemaForEntity: jest.fn(),
1963
- };
1964
-
1965
- render(
1966
- <TestWrapper>
1967
- <HTMLEditor
1968
- {...defaultProps}
1969
- onContextChange={null}
1970
- globalActions={globalActions}
1971
- location={{ query: { type: 'embedded' } }}
1972
- />
1973
- </TestWrapper>
1974
- );
1975
-
1976
- act(() => {
1977
- jest.runAllTimers();
1978
- });
1979
-
1980
- // Wait for component to render
1981
- const toolbar = screen.queryByTestId('editor-toolbar');
1982
- if (!toolbar) {
1983
- act(() => {
1984
- jest.runAllTimers();
1985
- });
1986
- }
1987
-
1988
- // The handleContextChange would be called by CodeEditorPane
1989
- // We verify globalActions is available
1990
- expect(globalActions.fetchSchemaForEntity).toBeDefined();
1991
- });
1992
-
1993
- it('does not make API call when globalActions is not available', () => {
1994
- render(
1995
- <TestWrapper>
1996
- <HTMLEditor
1997
- {...defaultProps}
1998
- onContextChange={null}
1999
- globalActions={null}
2000
- location={{ query: { type: 'embedded' } }}
2001
- />
2002
- </TestWrapper>
2003
- );
2004
-
2005
- act(() => {
2006
- jest.runAllTimers();
2007
- });
2008
-
2009
- // Wait for component to render - might be in loading state
2010
- const toolbar = screen.queryByTestId('editor-toolbar');
2011
- const loading = screen.queryByText('Initializing HTML Editor...');
2012
-
2013
- // Component should render (either loaded or loading)
2014
- expect(toolbar || loading).toBeTruthy();
2015
- });
2016
-
2017
- it('uses SMS layout for INAPP variant in handleContextChange', () => {
2018
- const globalActions = {
2019
- fetchSchemaForEntity: jest.fn(),
2020
- };
2021
-
2022
- render(
2023
- <TestWrapper>
2024
- <HTMLEditor
2025
- {...defaultProps}
2026
- variant="inapp"
2027
- onContextChange={null}
2028
- globalActions={globalActions}
2029
- location={{ query: { type: 'embedded' } }}
2030
- />
2031
- </TestWrapper>
2032
- );
2033
-
2034
- act(() => {
2035
- jest.runAllTimers();
2036
- });
2037
-
2038
- // Wait for component to render
2039
- const deviceToggle = screen.queryByTestId('device-toggle');
2040
- const loading = screen.queryByText('Initializing HTML Editor...');
2041
-
2042
- // Component should render (either loaded or loading)
2043
- expect(deviceToggle || loading).toBeTruthy();
2044
- });
2045
-
2046
- it('handles context change with ALL context type', () => {
2047
- const globalActions = {
2048
- fetchSchemaForEntity: jest.fn(),
2049
- };
2050
-
2051
- render(
2052
- <TestWrapper>
2053
- <HTMLEditor
2054
- {...defaultProps}
2055
- onContextChange={null}
2056
- globalActions={globalActions}
2057
- location={{ query: { type: 'embedded' } }}
2058
- />
2059
- </TestWrapper>
2060
- );
2061
-
2062
- act(() => {
2063
- jest.runAllTimers();
2064
- });
2065
-
2066
- // Component should render
2067
- const toolbar = screen.queryByTestId('editor-toolbar');
2068
- const loading = screen.queryByText('Initializing HTML Editor...');
2069
- expect(toolbar || loading).toBeTruthy();
2070
- });
2071
-
2072
- it('handles context change with embedded type', () => {
2073
- const globalActions = {
2074
- fetchSchemaForEntity: jest.fn(),
2075
- };
2076
-
2077
- render(
2078
- <TestWrapper>
2079
- <HTMLEditor
2080
- {...defaultProps}
2081
- onContextChange={null}
2082
- globalActions={globalActions}
2083
- location={{ query: { type: 'embedded', module: 'test' } }}
2084
- />
2085
- </TestWrapper>
2086
- );
2087
-
2088
- act(() => {
2089
- jest.runAllTimers();
2090
- });
2091
-
2092
- // Component should render
2093
- const toolbar = screen.queryByTestId('editor-toolbar');
2094
- const loading = screen.queryByText('Initializing HTML Editor...');
2095
- expect(toolbar || loading).toBeTruthy();
2096
- });
2097
- });
2098
-
2099
- describe('handleLabelInsert Coverage', () => {
2100
- it('handles label insert when position is null and editor is ready', () => {
2101
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2102
-
2103
- render(
2104
- <TestWrapper>
2105
- <HTMLEditor {...defaultProps} />
2106
- </TestWrapper>
2107
- );
2108
-
2109
- act(() => {
2110
- jest.runAllTimers();
2111
- });
2112
-
2113
- // Wait for component to render
2114
- const insertButton = screen.queryByText('Insert Label (Null Position)');
2115
- if (insertButton) {
2116
- fireEvent.click(insertButton);
2117
-
2118
- // Should attempt to insert via editor ref
2119
- // The mock editor has insertText method, so it should work
2120
- expect(CapNotification.success).toHaveBeenCalled();
2121
- }
2122
-
2123
- const codeEditorPane = screen.queryByTestId('code-editor-pane');
2124
- const loading = screen.queryByText('Initializing HTML Editor...');
2125
- expect(codeEditorPane || loading).toBeTruthy();
2126
- });
2127
-
2128
- it('shows warning when editor is not available for label insert', () => {
2129
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2130
-
2131
- // Mock CodeEditorPane to return null ref
2132
- jest.doMock('../components/CodeEditorPane', () => {
2133
- const React = require('react');
2134
- return React.forwardRef(function MockCodeEditorPane() {
2135
- // Return null ref
2136
- React.useImperativeHandle(null, () => null);
2137
- return <div data-testid="code-editor-pane">Editor</div>;
2138
- });
2139
- });
2140
-
2141
- render(
2142
- <TestWrapper>
2143
- <HTMLEditor {...defaultProps} />
2144
- </TestWrapper>
2145
- );
2146
-
2147
- act(() => {
2148
- jest.runAllTimers();
2149
- });
2150
-
2151
- const insertButton = screen.queryByText('Insert Label (Null Position)');
2152
- if (insertButton) {
2153
- fireEvent.click(insertButton);
2154
- // Should show warning when editor is null
2155
- expect(CapNotification.warning).toHaveBeenCalled();
2156
- }
2157
- });
2158
-
2159
- it('shows error when editor does not have insertText method', () => {
2160
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2161
-
2162
- // Mock CodeEditorPane to return editor without insertText
2163
- jest.doMock('../components/CodeEditorPane', () => {
2164
- const React = require('react');
2165
- return React.forwardRef(function MockCodeEditorPane(props, ref) {
2166
- React.useImperativeHandle(ref, () => ({
2167
- focus: jest.fn(),
2168
- getCursor: jest.fn(() => 0),
2169
- // No insertText method
2170
- }));
2171
- return <div data-testid="code-editor-pane">Editor</div>;
2172
- });
2173
- });
2174
-
2175
- render(
2176
- <TestWrapper>
2177
- <HTMLEditor {...defaultProps} />
2178
- </TestWrapper>
2179
- );
2180
-
2181
- act(() => {
2182
- jest.runAllTimers();
2183
- });
2184
-
2185
- const insertButton = screen.queryByText('Insert Label (Null Position)');
2186
- if (insertButton) {
2187
- fireEvent.click(insertButton);
2188
- // Should show error when insertText is not available
2189
- expect(CapNotification.error).toHaveBeenCalled();
2190
- }
2191
- });
2192
-
2193
- it('handles label insert when position is provided (already inserted)', () => {
2194
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2195
-
2196
- render(
2197
- <TestWrapper>
2198
- <HTMLEditor {...defaultProps} />
2199
- </TestWrapper>
2200
- );
2201
-
2202
- act(() => {
2203
- jest.runAllTimers();
2204
- });
2205
-
2206
- // Simulate label insert with position (already inserted by CodeEditorPane)
2207
- // This would call handleLabelInsert with a valid position
2208
- const insertButton = screen.queryByText('Insert Label');
2209
- if (insertButton) {
2210
- fireEvent.click(insertButton);
2211
- // Should show success notification
2212
- expect(CapNotification.success).toHaveBeenCalled();
2213
- }
2214
- });
2215
-
2216
- it('handles error during label insert', () => {
2217
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2218
-
2219
- // Mock CodeEditorPane to throw error on insertText
2220
- jest.doMock('../components/CodeEditorPane', () => {
2221
- const React = require('react');
2222
- return React.forwardRef(function MockCodeEditorPane(props, ref) {
2223
- React.useImperativeHandle(ref, () => ({
2224
- insertText: jest.fn(() => {
2225
- throw new Error('Insert failed');
2226
- }),
2227
- focus: jest.fn(),
2228
- getCursor: jest.fn(() => 0),
2229
- }));
2230
- return <div data-testid="code-editor-pane">Editor</div>;
2231
- });
2232
- });
2233
-
2234
- render(
2235
- <TestWrapper>
2236
- <HTMLEditor {...defaultProps} />
2237
- </TestWrapper>
2238
- );
2239
-
2240
- act(() => {
2241
- jest.runAllTimers();
2242
- });
2243
-
2244
- const insertButton = screen.queryByText('Insert Label (Null Position)');
2245
- if (insertButton) {
2246
- fireEvent.click(insertButton);
2247
- // Should show error notification
2248
- expect(CapNotification.error).toHaveBeenCalled();
2249
- }
2250
- });
2251
- });
2252
-
2253
- describe('handleSave Coverage', () => {
2254
- it('calls onSave callback when save is triggered', () => {
2255
- const onSave = jest.fn();
2256
-
2257
- render(
2258
- <TestWrapper>
2259
- <HTMLEditor {...defaultProps} onSave={onSave} />
2260
- </TestWrapper>
2261
- );
2262
-
2263
- act(() => {
2264
- jest.runAllTimers();
2265
- });
2266
-
2267
- const saveButton = screen.queryByText('Save');
2268
- if (saveButton) {
2269
- fireEvent.click(saveButton);
2270
- // onSave should be called
2271
- expect(onSave).toHaveBeenCalled();
2272
- } else {
2273
- // Component might be in loading state
2274
- const loading = screen.queryByText('Initializing HTML Editor...');
2275
- expect(loading).toBeTruthy();
2276
- }
2277
- });
2278
-
2279
- it('shows success notification on save', () => {
2280
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2281
- const onSave = jest.fn();
2282
-
2283
- render(
2284
- <TestWrapper>
2285
- <HTMLEditor {...defaultProps} onSave={onSave} />
2286
- </TestWrapper>
2287
- );
2288
-
2289
- act(() => {
2290
- jest.runAllTimers();
2291
- });
2292
-
2293
- const saveButton = screen.queryByText('Save');
2294
- if (saveButton) {
2295
- fireEvent.click(saveButton);
2296
- expect(CapNotification.success).toHaveBeenCalled();
2297
- }
2298
- });
2299
-
2300
- it('handles save error gracefully', () => {
2301
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2302
- const onSave = jest.fn(() => {
2303
- throw new Error('Save failed');
2304
- });
2305
-
2306
- render(
2307
- <TestWrapper>
2308
- <HTMLEditor {...defaultProps} onSave={onSave} />
2309
- </TestWrapper>
2310
- );
2311
-
2312
- act(() => {
2313
- jest.runAllTimers();
2314
- });
2315
-
2316
- const saveButton = screen.queryByText('Save');
2317
- if (saveButton) {
2318
- fireEvent.click(saveButton);
2319
- expect(CapNotification.error).toHaveBeenCalled();
2320
- }
2321
- });
2322
- });
2323
-
2324
- describe('handleValidationErrorClick Coverage', () => {
2325
- it('navigates to error line when editor has navigateToLine method', () => {
2326
- mockUseValidationImpl.mockReturnValueOnce({
2327
- isValidating: false,
2328
- getAllIssues: () => [{ message: 'Test error', line: 5, column: 10, severity: 'error' }],
2329
- isClean: () => false,
2330
- summary: { totalErrors: 1, totalWarnings: 0 }
2331
- });
2332
-
2333
- render(
2334
- <TestWrapper>
2335
- <HTMLEditor {...defaultProps} />
2336
- </TestWrapper>
2337
- );
2338
-
2339
- act(() => {
2340
- jest.runAllTimers();
2341
- });
2342
-
2343
- // Click on error
2344
- const errorButton = screen.queryByText('Error at Line 5');
2345
- if (errorButton) {
2346
- fireEvent.click(errorButton);
2347
- }
2348
-
2349
- // Should attempt to navigate to line
2350
- const codeEditorPane = screen.queryByTestId('code-editor-pane');
2351
- const loading = screen.queryByText('Initializing HTML Editor...');
2352
- expect(codeEditorPane || loading).toBeTruthy();
2353
- });
2354
-
2355
- it('focuses editor when navigateToLine is not available', () => {
2356
- mockCodeEditorOptions.includeNavigateToLine = false;
2357
-
2358
- mockUseValidationImpl.mockReturnValueOnce({
2359
- isValidating: false,
2360
- getAllIssues: () => [{ message: 'Test error', line: 5, column: 10, severity: 'error' }],
2361
- isClean: () => false,
2362
- summary: { totalErrors: 1, totalWarnings: 0 }
2363
- });
2364
-
2365
- render(
2366
- <TestWrapper>
2367
- <HTMLEditor {...defaultProps} />
2368
- </TestWrapper>
2369
- );
2370
-
2371
- act(() => {
2372
- jest.runAllTimers();
2373
- });
2374
-
2375
- // Component should render
2376
- const codeEditorPane = screen.queryByTestId('code-editor-pane');
2377
- const loading = screen.queryByText('Initializing HTML Editor...');
2378
- expect(codeEditorPane || loading).toBeTruthy();
2379
- });
2380
- });
2381
-
2382
-
2383
- describe('Additional Coverage Tests', () => {
2384
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
2385
-
2386
- beforeEach(() => {
2387
- // Reset options to default
2388
- mockCodeEditorOptions.includeInsertText = true;
2389
- mockCodeEditorOptions.insertTextThrows = false;
2390
- mockCodeEditorOptions.setRef = true;
2391
- mockCodeEditorOptions.navigateToLineThrows = false;
2392
- mockCodeEditorOptions.includeNavigateToLine = true;
2393
-
2394
- // Clear specific mocks instead of all to avoid breaking other mocks
2395
- CapNotification.warning.mockClear();
2396
- CapNotification.error.mockClear();
2397
- CapNotification.success.mockClear();
2398
-
2399
- // Restore hooks that might have been corrupted by Loading State Coverage tests
2400
- // This is necessary because Loading State Coverage tests modify the require cache
2401
- // and do not restore the original mocks
2402
- require('../hooks/useEditorContent').useEditorContent = () => ({
2403
- content: '<p>Test content</p>',
2404
- updateContent: jest.fn(),
2405
- saveContent: jest.fn(),
2406
- markAsSaved: jest.fn(),
2407
- isLoading: false,
2408
- isDirty: false,
2409
- hasContent: true,
2410
- getContentSize: jest.fn(() => 20)
2411
- });
2412
-
2413
- require('../hooks/useInAppContent').useInAppContent = () => ({
2414
- content: '<p>Android content</p>',
2415
- deviceContent: {
2416
- android: '<p>Android content</p>',
2417
- ios: '<p>iOS content</p>'
2418
- },
2419
- activeDevice: 'android',
2420
- keepContentSame: false,
2421
- updateContent: jest.fn(),
2422
- saveContent: jest.fn(),
2423
- markAsSaved: jest.fn(),
2424
- switchDevice: jest.fn(),
2425
- toggleContentSync: jest.fn(),
2426
- getDeviceContent: (device) => `<p>${device} content</p>`,
2427
- setDeviceContent: jest.fn(),
2428
- getContentSize: () => 20,
2429
- isLoading: false,
2430
- isDirty: false,
2431
- hasContent: true
2432
- });
2433
-
2434
- require('../hooks/useLayoutState').useLayoutState = () => ({
2435
- splitSizes: [50, 50],
2436
- splitSize: 50,
2437
- viewMode: 'desktop',
2438
- mobileWidth: 375,
2439
- isFullscreen: false,
2440
- isResizing: false,
2441
- updateSplitSizes: jest.fn(),
2442
- setSplitSize: jest.fn(),
2443
- setViewMode: jest.fn(),
2444
- toggleViewMode: jest.fn(),
2445
- setMobileWidth: jest.fn(),
2446
- toggleFullscreen: jest.fn(),
2447
- resetLayout: jest.fn(),
2448
- setResizingState: jest.fn(),
2449
- handleResize: jest.fn(),
2450
- handleKeyboardShortcut: jest.fn(),
2451
- isMobileView: false,
2452
- isDesktopView: true,
2453
- minPaneSize: 20,
2454
- maxPaneSize: 80,
2455
- gutterSize: 10
2456
- });
2457
- });
2458
-
2459
- describe('handleContextChange', () => {
2460
- it('calls onContextChange prop when provided', () => {
2461
- const onContextChange = jest.fn();
2462
- const globalActions = { fetchSchemaForEntity: jest.fn() };
2463
-
2464
- render(
2465
- <TestWrapper>
2466
- <HTMLEditor
2467
- {...defaultProps}
2468
- onContextChange={onContextChange}
2469
- globalActions={globalActions}
2470
- />
2471
- </TestWrapper>
2472
- );
2473
-
2474
- act(() => {
2475
- jest.runAllTimers();
2476
- });
2477
-
2478
- fireEvent.click(screen.getByTestId('trigger-context-change'));
2479
-
2480
- expect(onContextChange).toHaveBeenCalledWith('test-context');
2481
- expect(globalActions.fetchSchemaForEntity).not.toHaveBeenCalled();
2482
- });
2483
-
2484
- it('calls globalActions.fetchSchemaForEntity when onContextChange is NOT provided', () => {
2485
- const globalActions = { fetchSchemaForEntity: jest.fn() };
2486
- const location = { query: { type: 'embedded' } };
2487
-
2488
- render(
2489
- <TestWrapper>
2490
- <HTMLEditor
2491
- {...defaultProps}
2492
- globalActions={globalActions}
2493
- location={location}
2494
- variant="email"
2495
- />
2496
- </TestWrapper>
2497
- );
2498
-
2499
- act(() => {
2500
- jest.runAllTimers();
2501
- });
2502
-
2503
- fireEvent.click(screen.getByTestId('trigger-context-change'));
2504
-
2505
- expect(globalActions.fetchSchemaForEntity).toHaveBeenCalledWith({
2506
- layout: 'EMAIL',
2507
- type: 'TAG',
2508
- context: 'test-context',
2509
- embedded: 'embedded',
2510
- });
2511
- });
2512
-
2513
- it('handles INAPP variant in handleContextChange', () => {
2514
- const globalActions = { fetchSchemaForEntity: jest.fn() };
2515
- const location = { query: { type: 'full' } };
2516
-
2517
- render(
2518
- <TestWrapper>
2519
- <HTMLEditor
2520
- {...defaultProps}
2521
- globalActions={globalActions}
2522
- location={location}
2523
- variant="inapp"
2524
- />
2525
- </TestWrapper>
2526
- );
2527
-
2528
- act(() => {
2529
- jest.runAllTimers();
2530
- });
2531
-
2532
- fireEvent.click(screen.getByTestId('trigger-context-change'));
2533
-
2534
- expect(globalActions.fetchSchemaForEntity).toHaveBeenCalledWith({
2535
- layout: 'SMS', // INAPP uses SMS layout
2536
- type: 'TAG',
2537
- context: 'test-context',
2538
- embedded: 'full',
2539
- });
2540
- });
2541
-
2542
- it('handles missing globalActions or location', () => {
2543
- const globalActions = { fetchSchemaForEntity: jest.fn() };
2544
-
2545
- // Case 1: No globalActions
2546
- const { unmount } = render(
2547
- <TestWrapper>
2548
- <HTMLEditor
2549
- {...defaultProps}
2550
- globalActions={null}
2551
- location={{}}
2552
- />
2553
- </TestWrapper>
2554
- );
2555
-
2556
- act(() => {
2557
- jest.runAllTimers();
2558
- });
2559
-
2560
- fireEvent.click(screen.getByTestId('trigger-context-change'));
2561
- expect(globalActions.fetchSchemaForEntity).not.toHaveBeenCalled();
2562
- unmount();
2563
-
2564
- // Case 2: No location
2565
- render(
2566
- <TestWrapper>
2567
- <HTMLEditor
2568
- {...defaultProps}
2569
- globalActions={globalActions}
2570
- location={null}
2571
- />
2572
- </TestWrapper>
2573
- );
2574
-
2575
- act(() => {
2576
- jest.runAllTimers();
2577
- });
2578
-
2579
- fireEvent.click(screen.getByTestId('trigger-context-change'));
2580
- expect(globalActions.fetchSchemaForEntity).not.toHaveBeenCalled();
2581
- });
2582
- });
2583
-
2584
- describe('handleLabelInsert', () => {
2585
- it('shows warning when editor is not ready (position null, no editor)', () => {
2586
- mockCodeEditorOptions.setRef = false;
2587
-
2588
- render(
2589
- <TestWrapper>
2590
- <HTMLEditor {...defaultProps} />
2591
- </TestWrapper>
2592
- );
2593
-
2594
- act(() => {
2595
- jest.runAllTimers();
2596
- });
2597
-
2598
- // Click the button that passes null position
2599
- const insertButton = screen.getByText('Insert Label (Null Position)');
2600
- fireEvent.click(insertButton);
2601
-
2602
- expect(CapNotification.warning).toHaveBeenCalledWith(
2603
- expect.objectContaining({
2604
- message: 'Failed to insert label',
2605
- description: 'Editor is not ready. Please try again.',
2606
- })
2607
- );
2608
- });
2609
-
2610
- it('shows error when editor method insertText is not available', () => {
2611
- mockCodeEditorOptions.includeInsertText = false;
2612
-
2613
- render(
2614
- <TestWrapper>
2615
- <HTMLEditor {...defaultProps} />
2616
- </TestWrapper>
2617
- );
2618
-
2619
- act(() => {
2620
- jest.runAllTimers();
2621
- });
2622
-
2623
- const insertButton = screen.getByText('Insert Label (Null Position)');
2624
- fireEvent.click(insertButton);
2625
-
2626
- // Should show error when method is missing
2627
- expect(CapNotification.error).toHaveBeenCalledWith(
2628
- expect.objectContaining({
2629
- message: 'Failed to insert label',
2630
- })
2631
- );
2632
- });
2633
-
2634
- it('successfully inserts label when position is null', () => {
2635
- mockCodeEditorOptions.includeInsertText = true;
2636
-
2637
- render(
2638
- <TestWrapper>
2639
- <HTMLEditor {...defaultProps} />
2640
- </TestWrapper>
2641
- );
2642
-
2643
- act(() => {
2644
- jest.runAllTimers();
2645
- });
2646
-
2647
- const insertButton = screen.getByText('Insert Label (Null Position)');
2648
- fireEvent.click(insertButton);
2649
-
2650
- expect(CapNotification.success).toHaveBeenCalled();
2651
- });
2652
-
2653
- it('shows error when insertText throws', () => {
2654
- mockCodeEditorOptions.includeInsertText = true;
2655
- mockCodeEditorOptions.insertTextThrows = true;
2656
-
2657
- render(
2658
- <TestWrapper>
2659
- <HTMLEditor {...defaultProps} />
2660
- </TestWrapper>
2661
- );
2662
-
2663
- act(() => {
2664
- jest.runAllTimers();
2665
- });
2666
-
2667
- const insertButton = screen.getByText('Insert Label (Null Position)');
2668
- fireEvent.click(insertButton);
2669
-
2670
- expect(CapNotification.error).toHaveBeenCalledWith(expect.objectContaining({
2671
- description: 'Insert failed'
2672
- }));
2673
- });
2674
-
2675
- it('shows success notification when position is provided (handled by CodeEditorPane)', () => {
2676
- render(
2677
- <TestWrapper>
2678
- <HTMLEditor {...defaultProps} />
2679
- </TestWrapper>
2680
- );
2681
-
2682
- act(() => {
2683
- jest.runAllTimers();
2684
- });
2685
-
2686
- // Click the button that passes a position
2687
- const insertButton = screen.getByText('Insert Label');
2688
- fireEvent.click(insertButton);
2689
-
2690
- expect(CapNotification.success).toHaveBeenCalled();
2691
- });
2692
- });
2693
-
2694
- describe('handleValidationErrorClick', () => {
2695
- it('handles error when navigateToLine throws', () => {
2696
- mockCodeEditorOptions.navigateToLineThrows = true;
2697
-
2698
- mockUseValidationImpl.mockReturnValueOnce({
2699
- isValidating: false,
2700
- getAllIssues: () => [{ message: 'Test error', line: 5, column: 10, severity: 'error' }],
2701
- isClean: () => false,
2702
- summary: { totalErrors: 1, totalWarnings: 0 }
2703
- });
2704
-
2705
- render(
2706
- <TestWrapper>
2707
- <HTMLEditor {...defaultProps} />
2708
- </TestWrapper>
2709
- );
2710
-
2711
- act(() => {
2712
- jest.runAllTimers();
2713
- });
2714
-
2715
- // Click error button
2716
- const errorButton = screen.getByText('Error at Line 5');
2717
- fireEvent.click(errorButton);
2718
- });
2719
- });
2720
- });
2721
1808
  });
2722
1809