@capillarytech/creatives-library 8.0.207 → 8.0.209

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 (77) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/package.json +16 -2
  4. package/v2Components/HtmlEditor/HTMLEditor.js +508 -0
  5. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1809 -0
  6. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +532 -0
  7. package/v2Components/HtmlEditor/_htmlEditor.scss +304 -0
  8. package/v2Components/HtmlEditor/_index.lazy.scss +26 -0
  9. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +376 -0
  10. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +331 -0
  11. package/v2Components/HtmlEditor/components/DeviceToggle/__tests__/index.test.js +314 -0
  12. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +244 -0
  13. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +111 -0
  14. package/v2Components/HtmlEditor/components/EditorToolbar/PreviewModeGroup.js +72 -0
  15. package/v2Components/HtmlEditor/components/EditorToolbar/__tests__/PreviewModeGroup.test.js +1594 -0
  16. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +113 -0
  17. package/v2Components/HtmlEditor/components/EditorToolbar/_previewModeGroup.scss +82 -0
  18. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +115 -0
  19. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +57 -0
  20. package/v2Components/HtmlEditor/components/InAppPreviewPane/ContentOverlay.js +90 -0
  21. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +60 -0
  22. package/v2Components/HtmlEditor/components/InAppPreviewPane/LayoutSelector.js +58 -0
  23. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/ContentOverlay.test.js +389 -0
  24. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +424 -0
  25. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/LayoutSelector.test.js +248 -0
  26. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +253 -0
  27. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +104 -0
  28. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +179 -0
  29. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +220 -0
  30. package/v2Components/HtmlEditor/components/PreviewPane/index.js +229 -0
  31. package/v2Components/HtmlEditor/components/SplitContainer/SplitContainer.js +276 -0
  32. package/v2Components/HtmlEditor/components/SplitContainer/__tests__/SplitContainer.test.js +295 -0
  33. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +257 -0
  34. package/v2Components/HtmlEditor/components/SplitContainer/index.js +7 -0
  35. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +152 -0
  36. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +31 -0
  37. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +70 -0
  38. package/v2Components/HtmlEditor/components/ValidationPanel/__tests__/index.test.js +98 -0
  39. package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +311 -0
  40. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +297 -0
  41. package/v2Components/HtmlEditor/components/ValidationPanel/messages.js +57 -0
  42. package/v2Components/HtmlEditor/components/common/EditorContext.js +84 -0
  43. package/v2Components/HtmlEditor/components/common/__tests__/EditorContext.test.js +660 -0
  44. package/v2Components/HtmlEditor/constants.js +241 -0
  45. package/v2Components/HtmlEditor/hooks/__tests__/useEditorContent.test.js +450 -0
  46. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +785 -0
  47. package/v2Components/HtmlEditor/hooks/__tests__/useLayoutState.test.js +580 -0
  48. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.enhanced.test.js +768 -0
  49. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +590 -0
  50. package/v2Components/HtmlEditor/hooks/useEditorContent.js +274 -0
  51. package/v2Components/HtmlEditor/hooks/useInAppContent.js +407 -0
  52. package/v2Components/HtmlEditor/hooks/useLayoutState.js +247 -0
  53. package/v2Components/HtmlEditor/hooks/useValidation.js +325 -0
  54. package/v2Components/HtmlEditor/index.js +29 -0
  55. package/v2Components/HtmlEditor/index.lazy.js +114 -0
  56. package/v2Components/HtmlEditor/messages.js +389 -0
  57. package/v2Components/HtmlEditor/utils/__tests__/contentSanitizer.test.js +741 -0
  58. package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +1042 -0
  59. package/v2Components/HtmlEditor/utils/__tests__/liquidTemplateSupport.test.js +515 -0
  60. package/v2Components/HtmlEditor/utils/__tests__/properSyntaxHighlighting.test.js +473 -0
  61. package/v2Components/HtmlEditor/utils/__tests__/simplePerformance.test.js +1109 -0
  62. package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +240 -0
  63. package/v2Components/HtmlEditor/utils/contentSanitizer.js +433 -0
  64. package/v2Components/HtmlEditor/utils/htmlValidator.js +508 -0
  65. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +524 -0
  66. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +163 -0
  67. package/v2Components/HtmlEditor/utils/simplePerformance.js +145 -0
  68. package/v2Components/HtmlEditor/utils/validationAdapter.js +130 -0
  69. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +200 -0
  70. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +545 -0
  71. package/v2Containers/EmailWrapper/index.js +8 -1
  72. package/v2Containers/Templates/constants.js +8 -0
  73. package/v2Containers/Templates/index.js +56 -28
  74. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +5 -14
  75. package/v2Containers/Whatsapp/constants.js +26 -2
  76. package/v2Containers/Whatsapp/index.js +4 -1
  77. package/v2Containers/Whatsapp/tests/index.test.js +460 -18
@@ -0,0 +1,532 @@
1
+ /**
2
+ * index.lazy.js Tests
3
+ *
4
+ * Tests for lazy loading wrapper, fallback component, and preload utilities
5
+ */
6
+
7
+ import React from 'react';
8
+ import { render, screen, waitFor } from '@testing-library/react';
9
+ import '@testing-library/jest-dom';
10
+
11
+ // Mock CapSpin before importing the component
12
+ jest.mock('@capillarytech/cap-ui-library/CapSpin', () => {
13
+ return function MockCapSpin({ size }) {
14
+ return <div data-testid="cap-spin" data-size={size}>Loading...</div>;
15
+ };
16
+ });
17
+
18
+ // Mock the HTMLEditor component
19
+ jest.mock('../HTMLEditor', () => {
20
+ return {
21
+ __esModule: true,
22
+ default: function MockHTMLEditor(props) {
23
+ return <div data-testid="html-editor">Mock HTML Editor - {props.variant}</div>;
24
+ }
25
+ };
26
+ });
27
+
28
+ describe('index.lazy.js', () => {
29
+ // Reset modules before each test to ensure clean state
30
+ beforeEach(() => {
31
+ jest.clearAllMocks();
32
+ // Clear the window flag
33
+ if (typeof window !== 'undefined') {
34
+ delete window.__htmlEditorLoaded;
35
+ }
36
+ });
37
+
38
+ describe('HTMLEditorFallback', () => {
39
+ it('renders loading fallback with default message', async () => {
40
+ // Import after mocks are set up
41
+ const { HTMLEditorFallback } = await import('../index.lazy');
42
+
43
+ render(<HTMLEditorFallback />);
44
+
45
+ expect(screen.getByTestId('cap-spin')).toBeInTheDocument();
46
+ expect(screen.getByText('Loading HTML Editor...')).toBeInTheDocument();
47
+ });
48
+
49
+ it('renders loading fallback with custom message', async () => {
50
+ const { HTMLEditorFallback } = await import('../index.lazy');
51
+
52
+ render(<HTMLEditorFallback message="Initializing editor..." />);
53
+
54
+ expect(screen.getByTestId('cap-spin')).toBeInTheDocument();
55
+ expect(screen.getByText('Initializing editor...')).toBeInTheDocument();
56
+ });
57
+
58
+ it('renders CapSpin with large size', async () => {
59
+ const { HTMLEditorFallback } = await import('../index.lazy');
60
+
61
+ render(<HTMLEditorFallback />);
62
+
63
+ const spinner = screen.getByTestId('cap-spin');
64
+ expect(spinner).toHaveAttribute('data-size', 'large');
65
+ });
66
+
67
+ it('applies correct container CSS class', async () => {
68
+ const { HTMLEditorFallback } = await import('../index.lazy');
69
+
70
+ const { container } = render(<HTMLEditorFallback />);
71
+ const fallbackDiv = container.firstChild;
72
+
73
+ expect(fallbackDiv).toHaveClass('html-editor-lazy-fallback');
74
+ });
75
+
76
+ it('applies correct container CSS class for styling', async () => {
77
+ const { HTMLEditorFallback } = await import('../index.lazy');
78
+
79
+ const { container } = render(<HTMLEditorFallback />);
80
+ const fallbackDiv = container.firstChild;
81
+
82
+ expect(fallbackDiv).toHaveClass('html-editor-lazy-fallback');
83
+ });
84
+
85
+ it('applies correct layout CSS class', async () => {
86
+ const { HTMLEditorFallback } = await import('../index.lazy');
87
+
88
+ const { container } = render(<HTMLEditorFallback />);
89
+ const fallbackDiv = container.firstChild;
90
+
91
+ expect(fallbackDiv).toHaveClass('html-editor-lazy-fallback');
92
+ });
93
+
94
+ it('renders message with correct CSS class and content', async () => {
95
+ const { HTMLEditorFallback } = await import('../index.lazy');
96
+
97
+ const { container } = render(<HTMLEditorFallback message="Test message" />);
98
+ const messageDiv = container.querySelector('.html-editor-lazy-fallback__message');
99
+
100
+ expect(messageDiv).toHaveClass('html-editor-lazy-fallback__message');
101
+ expect(messageDiv).toHaveTextContent('Test message');
102
+ });
103
+ });
104
+
105
+ describe('HTMLEditorLazy', () => {
106
+ it('renders the lazy loaded component', async () => {
107
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
108
+
109
+ render(<HTMLEditorLazy variant="email" />);
110
+
111
+ // Wait for lazy component to load
112
+ await waitFor(() => {
113
+ expect(screen.getByTestId('html-editor')).toBeInTheDocument();
114
+ });
115
+
116
+ expect(screen.getByText(/Mock HTML Editor - email/)).toBeInTheDocument();
117
+ });
118
+
119
+ it('shows fallback while loading', async () => {
120
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
121
+
122
+ const { container } = render(<HTMLEditorLazy variant="email" />);
123
+
124
+ // Fallback should be visible initially (though it may load very quickly in tests)
125
+ // The Suspense boundary is in place
126
+ expect(container.firstChild).toBeInTheDocument();
127
+ });
128
+
129
+ it('forwards all props to the underlying component', async () => {
130
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
131
+ const mockOnSave = jest.fn();
132
+
133
+ render(
134
+ <HTMLEditorLazy
135
+ variant="inapp"
136
+ onSave={mockOnSave}
137
+ readOnly={true}
138
+ className="custom-class"
139
+ />
140
+ );
141
+
142
+ await waitFor(() => {
143
+ expect(screen.getByTestId('html-editor')).toBeInTheDocument();
144
+ });
145
+
146
+ expect(screen.getByText(/Mock HTML Editor - inapp/)).toBeInTheDocument();
147
+ });
148
+
149
+ it('has correct display name', async () => {
150
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
151
+
152
+ expect(HTMLEditorLazy.displayName).toBe('HTMLEditorLazy');
153
+ });
154
+
155
+ it('accepts variant prop', async () => {
156
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
157
+
158
+ render(<HTMLEditorLazy variant="email" />);
159
+
160
+ await waitFor(() => {
161
+ expect(screen.getByTestId('html-editor')).toBeInTheDocument();
162
+ });
163
+ });
164
+
165
+ it('accepts layoutType prop', async () => {
166
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
167
+
168
+ render(<HTMLEditorLazy layoutType="modal" />);
169
+
170
+ await waitFor(() => {
171
+ expect(screen.getByTestId('html-editor')).toBeInTheDocument();
172
+ });
173
+ });
174
+
175
+ it('accepts initialContent as string', async () => {
176
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
177
+
178
+ render(<HTMLEditorLazy initialContent="<p>Test</p>" />);
179
+
180
+ await waitFor(() => {
181
+ expect(screen.getByTestId('html-editor')).toBeInTheDocument();
182
+ });
183
+ });
184
+
185
+ it('accepts initialContent as object', async () => {
186
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
187
+
188
+ render(<HTMLEditorLazy initialContent={{ android: '<p>Android</p>', ios: '<p>iOS</p>' }} />);
189
+
190
+ await waitFor(() => {
191
+ expect(screen.getByTestId('html-editor')).toBeInTheDocument();
192
+ });
193
+ });
194
+
195
+ it('accepts event handler props', async () => {
196
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
197
+ const mockOnSave = jest.fn();
198
+ const mockOnContentChange = jest.fn();
199
+
200
+ render(
201
+ <HTMLEditorLazy
202
+ onSave={mockOnSave}
203
+ onContentChange={mockOnContentChange}
204
+ />
205
+ );
206
+
207
+ await waitFor(() => {
208
+ expect(screen.getByTestId('html-editor')).toBeInTheDocument();
209
+ });
210
+ });
211
+
212
+ it('accepts configuration props', async () => {
213
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
214
+
215
+ render(
216
+ <HTMLEditorLazy
217
+ className="custom-editor"
218
+ readOnly={true}
219
+ showFullscreenButton={false}
220
+ autoSave={false}
221
+ autoSaveInterval={60000}
222
+ />
223
+ );
224
+
225
+ await waitFor(() => {
226
+ expect(screen.getByTestId('html-editor')).toBeInTheDocument();
227
+ });
228
+ });
229
+ });
230
+
231
+ describe('Default Props', () => {
232
+ it('has correct default variant', async () => {
233
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
234
+
235
+ expect(HTMLEditorLazy.defaultProps.variant).toBe('email');
236
+ });
237
+
238
+ it('has correct default initialContent', async () => {
239
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
240
+
241
+ expect(HTMLEditorLazy.defaultProps.initialContent).toBe(null);
242
+ });
243
+
244
+ it('has correct default event handlers', async () => {
245
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
246
+
247
+ expect(HTMLEditorLazy.defaultProps.onSave).toBe(null);
248
+ expect(HTMLEditorLazy.defaultProps.onContentChange).toBe(null);
249
+ });
250
+
251
+ it('has correct default configuration', async () => {
252
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
253
+
254
+ expect(HTMLEditorLazy.defaultProps.className).toBe('');
255
+ expect(HTMLEditorLazy.defaultProps.readOnly).toBe(false);
256
+ expect(HTMLEditorLazy.defaultProps.showFullscreenButton).toBe(true);
257
+ expect(HTMLEditorLazy.defaultProps.autoSave).toBe(true);
258
+ expect(HTMLEditorLazy.defaultProps.autoSaveInterval).toBe(30000);
259
+ });
260
+ });
261
+
262
+ describe('PropTypes', () => {
263
+ it('defines variant propType', async () => {
264
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
265
+
266
+ expect(HTMLEditorLazy.propTypes.variant).toBeDefined();
267
+ });
268
+
269
+ it('defines layoutType propType', async () => {
270
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
271
+
272
+ expect(HTMLEditorLazy.propTypes.layoutType).toBeDefined();
273
+ });
274
+
275
+ it('defines initialContent propType', async () => {
276
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
277
+
278
+ expect(HTMLEditorLazy.propTypes.initialContent).toBeDefined();
279
+ });
280
+
281
+ it('defines event handler propTypes', async () => {
282
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
283
+
284
+ expect(HTMLEditorLazy.propTypes.onSave).toBeDefined();
285
+ expect(HTMLEditorLazy.propTypes.onContentChange).toBeDefined();
286
+ });
287
+
288
+ it('defines configuration propTypes', async () => {
289
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
290
+
291
+ expect(HTMLEditorLazy.propTypes.className).toBeDefined();
292
+ expect(HTMLEditorLazy.propTypes.readOnly).toBeDefined();
293
+ expect(HTMLEditorLazy.propTypes.showFullscreenButton).toBeDefined();
294
+ expect(HTMLEditorLazy.propTypes.autoSave).toBeDefined();
295
+ expect(HTMLEditorLazy.propTypes.autoSaveInterval).toBeDefined();
296
+ });
297
+ });
298
+
299
+ describe('preloadHTMLEditor', () => {
300
+ it('is exported as a function', async () => {
301
+ const { preloadHTMLEditor } = await import('../index.lazy');
302
+
303
+ expect(typeof preloadHTMLEditor).toBe('function');
304
+ });
305
+
306
+ it('can be called without errors', async () => {
307
+ const { preloadHTMLEditor } = await import('../index.lazy');
308
+
309
+ expect(() => {
310
+ preloadHTMLEditor();
311
+ }).not.toThrow();
312
+ });
313
+
314
+ it('handles import failures gracefully', async () => {
315
+ const { preloadHTMLEditor } = await import('../index.lazy');
316
+
317
+ // Should not throw even if import fails (preloadHTMLEditor catches errors internally)
318
+ expect(() => {
319
+ preloadHTMLEditor();
320
+ }).not.toThrow();
321
+ });
322
+
323
+ it('returns undefined', async () => {
324
+ const { preloadHTMLEditor } = await import('../index.lazy');
325
+
326
+ const result = preloadHTMLEditor();
327
+ expect(result).toBeUndefined();
328
+ });
329
+ });
330
+
331
+ describe('isHTMLEditorLoaded', () => {
332
+ it('is exported as a function', async () => {
333
+ const { isHTMLEditorLoaded } = await import('../index.lazy');
334
+
335
+ expect(typeof isHTMLEditorLoaded).toBe('function');
336
+ });
337
+
338
+ it('returns false when window.__htmlEditorLoaded is not set', async () => {
339
+ const { isHTMLEditorLoaded } = await import('../index.lazy');
340
+
341
+ // The function returns undefined when window.__htmlEditorLoaded is not set
342
+ expect(isHTMLEditorLoaded()).toBeFalsy();
343
+ });
344
+
345
+ it('returns true when window.__htmlEditorLoaded is set', async () => {
346
+ const { isHTMLEditorLoaded } = await import('../index.lazy');
347
+
348
+ if (typeof window !== 'undefined') {
349
+ window.__htmlEditorLoaded = true;
350
+ }
351
+
352
+ expect(isHTMLEditorLoaded()).toBe(true);
353
+ });
354
+
355
+ it('handles non-browser environment', async () => {
356
+ const { isHTMLEditorLoaded } = await import('../index.lazy');
357
+
358
+ // In Node.js test environment with jsdom, window is defined
359
+ // The function should return a value (true or falsy)
360
+ const result = isHTMLEditorLoaded();
361
+ expect([true, false, undefined]).toContain(result);
362
+ });
363
+
364
+ it('handles different window states', async () => {
365
+ const { isHTMLEditorLoaded } = await import('../index.lazy');
366
+
367
+ // Should not throw regardless of window state
368
+ expect(() => {
369
+ isHTMLEditorLoaded();
370
+ }).not.toThrow();
371
+ });
372
+ });
373
+
374
+ describe('Module Exports', () => {
375
+ it('exports HTMLEditorLazy as default', async () => {
376
+ const module = await import('../index.lazy');
377
+
378
+ expect(module.default).toBeDefined();
379
+ expect(typeof module.default).toBe('function');
380
+ });
381
+
382
+ it('exports HTMLEditorFallback as named export', async () => {
383
+ const { HTMLEditorFallback } = await import('../index.lazy');
384
+
385
+ expect(HTMLEditorFallback).toBeDefined();
386
+ expect(typeof HTMLEditorFallback).toBe('function');
387
+ });
388
+
389
+ it('exports preloadHTMLEditor as named export', async () => {
390
+ const { preloadHTMLEditor } = await import('../index.lazy');
391
+
392
+ expect(preloadHTMLEditor).toBeDefined();
393
+ expect(typeof preloadHTMLEditor).toBe('function');
394
+ });
395
+
396
+ it('exports isHTMLEditorLoaded as named export', async () => {
397
+ const { isHTMLEditorLoaded } = await import('../index.lazy');
398
+
399
+ expect(isHTMLEditorLoaded).toBeDefined();
400
+ expect(typeof isHTMLEditorLoaded).toBe('function');
401
+ });
402
+ });
403
+
404
+ describe('Integration Tests', () => {
405
+ it('renders complete lazy loading flow', async () => {
406
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
407
+
408
+ const { container } = render(<HTMLEditorLazy variant="email" />);
409
+
410
+ // Container should exist
411
+ expect(container.firstChild).toBeInTheDocument();
412
+
413
+ // Wait for actual component to load
414
+ await waitFor(() => {
415
+ expect(screen.getByTestId('html-editor')).toBeInTheDocument();
416
+ });
417
+ });
418
+
419
+ it('can preload and then render', async () => {
420
+ const { default: HTMLEditorLazy, preloadHTMLEditor } = await import('../index.lazy');
421
+
422
+ // Preload
423
+ preloadHTMLEditor();
424
+
425
+ // Then render
426
+ render(<HTMLEditorLazy variant="email" />);
427
+
428
+ await waitFor(() => {
429
+ expect(screen.getByTestId('html-editor')).toBeInTheDocument();
430
+ });
431
+ });
432
+
433
+ it('handles multiple instances', async () => {
434
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
435
+
436
+ render(
437
+ <>
438
+ <HTMLEditorLazy variant="email" />
439
+ <HTMLEditorLazy variant="inapp" />
440
+ </>
441
+ );
442
+
443
+ await waitFor(() => {
444
+ const editors = screen.getAllByTestId('html-editor');
445
+ expect(editors).toHaveLength(2);
446
+ });
447
+ });
448
+
449
+ it('fallback and main component work together', async () => {
450
+ const { default: HTMLEditorLazy, HTMLEditorFallback } = await import('../index.lazy');
451
+
452
+ // Render fallback independently
453
+ const { unmount } = render(<HTMLEditorFallback message="Custom loading" />);
454
+ expect(screen.getByText('Custom loading')).toBeInTheDocument();
455
+ unmount();
456
+
457
+ // Render lazy component
458
+ render(<HTMLEditorLazy variant="email" />);
459
+
460
+ await waitFor(() => {
461
+ expect(screen.getByTestId('html-editor')).toBeInTheDocument();
462
+ });
463
+ });
464
+ });
465
+
466
+ describe('Edge Cases', () => {
467
+ it('handles undefined props gracefully', async () => {
468
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
469
+
470
+ expect(() => {
471
+ render(<HTMLEditorLazy variant={undefined} />);
472
+ }).not.toThrow();
473
+ });
474
+
475
+ it('handles null props gracefully', async () => {
476
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
477
+
478
+ expect(() => {
479
+ render(<HTMLEditorLazy onSave={null} />);
480
+ }).not.toThrow();
481
+ });
482
+
483
+ it('fallback handles empty message', async () => {
484
+ const { HTMLEditorFallback } = await import('../index.lazy');
485
+
486
+ render(<HTMLEditorFallback message="" />);
487
+
488
+ expect(screen.getByTestId('cap-spin')).toBeInTheDocument();
489
+ });
490
+
491
+ it('fallback handles null message', async () => {
492
+ const { HTMLEditorFallback } = await import('../index.lazy');
493
+
494
+ render(<HTMLEditorFallback message={null} />);
495
+
496
+ // Should render with default message
497
+ expect(screen.getByTestId('cap-spin')).toBeInTheDocument();
498
+ });
499
+
500
+ it('handles rapid mount/unmount', async () => {
501
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
502
+
503
+ const { unmount } = render(<HTMLEditorLazy variant="email" />);
504
+
505
+ // Unmount immediately
506
+ unmount();
507
+
508
+ // Should not throw
509
+ expect(true).toBe(true);
510
+ });
511
+ });
512
+
513
+ describe('Performance and Code Splitting', () => {
514
+ it('lazy component uses React.lazy', async () => {
515
+ const HTMLEditorLazy = (await import('../index.lazy')).default;
516
+
517
+ // The component should be wrapped in Suspense
518
+ const { container } = render(<HTMLEditorLazy variant="email" />);
519
+
520
+ expect(container.firstChild).toBeInTheDocument();
521
+ });
522
+
523
+ it('does not immediately load the actual editor', async () => {
524
+ // This test verifies that import() is used for lazy loading
525
+ const module = await import('../index.lazy');
526
+
527
+ // The default export should be a wrapper component, not the actual editor
528
+ expect(module.default.displayName).toBe('HTMLEditorLazy');
529
+ });
530
+ });
531
+ });
532
+