@capillarytech/creatives-library 8.0.354 → 8.0.356

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 (28) hide show
  1. package/index.html +0 -1
  2. package/package.json +1 -1
  3. package/utils/cdnTransformation.js +3 -63
  4. package/utils/tests/cdnTransformation.test.js +0 -111
  5. package/v2Components/CommonTestAndPreview/UnifiedPreview/PreviewHeader.js +16 -0
  6. package/v2Components/CommonTestAndPreview/UnifiedPreview/WebPushPreviewContent.js +169 -0
  7. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +54 -0
  8. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +52 -6
  9. package/v2Components/CommonTestAndPreview/constants.js +2 -0
  10. package/v2Components/CommonTestAndPreview/index.js +51 -2
  11. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/PreviewHeader.test.js +163 -0
  12. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/WebPushPreviewContent.test.js +522 -0
  13. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +255 -0
  14. package/v2Components/CommonTestAndPreview/tests/constants.test.js +2 -1
  15. package/v2Components/CommonTestAndPreview/tests/index.test.js +194 -0
  16. package/v2Components/FormBuilder/index.js +162 -52
  17. package/v2Components/TestAndPreviewSlidebox/index.js +2 -2
  18. package/v2Containers/App/constants.js +3 -0
  19. package/v2Containers/App/tests/constants.test.js +61 -0
  20. package/v2Containers/CreativesContainer/index.js +60 -24
  21. package/v2Containers/Templates/index.js +72 -2
  22. package/v2Containers/Templates/sagas.js +1 -6
  23. package/v2Containers/Templates/tests/sagas.test.js +6 -23
  24. package/v2Containers/Templates/tests/webpush.test.js +375 -0
  25. package/v2Containers/WebPush/Create/index.js +91 -8
  26. package/v2Containers/WebPush/Create/index.scss +7 -0
  27. package/v2Containers/WebPush/Create/tests/getTemplateContent.test.js +348 -0
  28. package/v2Containers/WebPush/Create/tests/testAndPreviewIntegration.test.js +325 -0
@@ -0,0 +1,522 @@
1
+ /**
2
+ * Tests for WebPushPreviewContent Component
3
+ *
4
+ * Covers: OS/browser state, fullscreen modal, isUpdating/error early-returns,
5
+ * handleOSChange, handleBrowserChange, handleCloseFullscreen, browser coercion effect.
6
+ */
7
+
8
+ import React from 'react';
9
+ import { render, screen, fireEvent, act } from '@testing-library/react';
10
+ import '@testing-library/jest-dom';
11
+ import { IntlProvider } from 'react-intl';
12
+ import WebPushPreviewContent from '../../UnifiedPreview/WebPushPreviewContent';
13
+ import messages from '../../messages';
14
+
15
+ const messagesForIntl = Object.keys(messages).reduce((acc, key) => {
16
+ const msg = messages[key];
17
+ acc[msg.id] = msg.defaultMessage;
18
+ return acc;
19
+ }, {});
20
+
21
+ const TestWrapper = ({ children }) => (
22
+ <IntlProvider locale="en" messages={messagesForIntl}>
23
+ {children}
24
+ </IntlProvider>
25
+ );
26
+
27
+ // Mock heavy child components
28
+ jest.mock('../../../../v2Containers/WebPush/Create/preview/PreviewControls', () => ({
29
+ __esModule: true,
30
+ default: (props) => (
31
+ <div data-testid="preview-controls">
32
+ <button
33
+ data-testid="os-change-btn"
34
+ onClick={() => props.onOSChange('Windows')}
35
+ >
36
+ Change OS
37
+ </button>
38
+ <button
39
+ data-testid="browser-change-btn"
40
+ onClick={() => props.onBrowserChange('Firefox')}
41
+ >
42
+ Change Browser
43
+ </button>
44
+ <div data-testid="selected-os">{props.selectedOS}</div>
45
+ <div data-testid="selected-browser">{props.selectedBrowser}</div>
46
+ <div data-testid="browser-options">{JSON.stringify(props.browserOptions)}</div>
47
+ </div>
48
+ ),
49
+ }));
50
+
51
+ jest.mock('../../../../v2Containers/WebPush/Create/preview/PreviewContent', () => ({
52
+ __esModule: true,
53
+ default: (props) => (
54
+ <div data-testid="preview-content">
55
+ <div data-testid="content-title">{props.notificationTitle}</div>
56
+ <div data-testid="content-body">{props.notificationBody}</div>
57
+ <div data-testid="content-url">{props.url}</div>
58
+ <div data-testid="content-os">{props.selectedOS}</div>
59
+ <div data-testid="content-browser">{props.selectedBrowser}</div>
60
+ <div data-testid="content-image">{props.imageSrc}</div>
61
+ <div data-testid="content-icon">{props.brandIconSrc}</div>
62
+ <div data-testid="content-buttons">{JSON.stringify(props.buttons)}</div>
63
+ </div>
64
+ ),
65
+ }));
66
+
67
+ jest.mock('../../../../v2Containers/WebPush/Create/preview/DevicePreviewContent', () => ({
68
+ __esModule: true,
69
+ default: (props) => (
70
+ <div data-testid="device-preview-content">
71
+ <div data-testid="device-title">{props.notificationTitle}</div>
72
+ <div data-testid="device-body">{props.notificationBody}</div>
73
+ <div data-testid="device-image">{props.imageSrc}</div>
74
+ <div data-testid="device-icon">{props.brandIconSrc}</div>
75
+ </div>
76
+ ),
77
+ }));
78
+
79
+ // Mock CapModal to render children when visible
80
+ jest.mock('@capillarytech/cap-ui-library/CapModal', () => ({
81
+ __esModule: true,
82
+ default: ({ visible, children, onCancel }) => (
83
+ <div data-testid="cap-modal" data-visible={visible ? 'true' : 'false'}>
84
+ {visible && <div data-testid="modal-content">{children}</div>}
85
+ <button data-testid="modal-cancel" onClick={onCancel}>Cancel</button>
86
+ </div>
87
+ ),
88
+ }));
89
+
90
+ jest.mock('@capillarytech/cap-ui-library/CapRow', () => ({
91
+ __esModule: true,
92
+ default: ({ children, className }) => <div className={className}>{children}</div>,
93
+ }));
94
+
95
+ jest.mock('@capillarytech/cap-ui-library/CapLabel', () => ({
96
+ __esModule: true,
97
+ default: ({ children }) => <span>{children}</span>,
98
+ }));
99
+
100
+ jest.mock('@capillarytech/cap-ui-library/CapIcon', () => ({
101
+ __esModule: true,
102
+ default: ({ type, onClick, className }) => (
103
+ <span
104
+ data-testid={`cap-icon-${type}`}
105
+ className={className}
106
+ onClick={onClick}
107
+ />
108
+ ),
109
+ }));
110
+
111
+ jest.mock('@capillarytech/cap-ui-library/CapDivider', () => ({
112
+ __esModule: true,
113
+ default: () => <hr data-testid="cap-divider" />,
114
+ }));
115
+
116
+ const defaultProps = {
117
+ notificationTitle: 'Test Title',
118
+ notificationBody: 'Test body text',
119
+ imageSrc: 'https://example.com/image.jpg',
120
+ brandIconSrc: 'https://example.com/icon.png',
121
+ buttons: [{ text: 'Click', url: 'https://example.com', type: 'EXTERNAL_URL' }],
122
+ url: 'https://example.com',
123
+ isUpdating: false,
124
+ error: null,
125
+ isFullscreenOpen: false,
126
+ setIsFullscreenOpen: jest.fn(),
127
+ selectedCustomer: null,
128
+ };
129
+
130
+ describe('WebPushPreviewContent', () => {
131
+ beforeEach(() => {
132
+ jest.clearAllMocks();
133
+ });
134
+
135
+ describe('Early-return guards', () => {
136
+ it('should return null when isUpdating is true', () => {
137
+ const { container } = render(
138
+ <TestWrapper>
139
+ <WebPushPreviewContent {...defaultProps} isUpdating={true} />
140
+ </TestWrapper>
141
+ );
142
+ expect(container.firstChild).toBeNull();
143
+ });
144
+
145
+ it('should return null when error is truthy', () => {
146
+ const { container } = render(
147
+ <TestWrapper>
148
+ <WebPushPreviewContent {...defaultProps} error="Something went wrong" />
149
+ </TestWrapper>
150
+ );
151
+ expect(container.firstChild).toBeNull();
152
+ });
153
+
154
+ it('should render normally when isUpdating is false and error is null', () => {
155
+ render(
156
+ <TestWrapper>
157
+ <WebPushPreviewContent {...defaultProps} />
158
+ </TestWrapper>
159
+ );
160
+ expect(screen.getByTestId('preview-controls')).toBeTruthy();
161
+ expect(screen.getByTestId('preview-content')).toBeTruthy();
162
+ });
163
+ });
164
+
165
+ describe('Initial OS and browser state', () => {
166
+ it('should render with default OS (macOS)', () => {
167
+ render(
168
+ <TestWrapper>
169
+ <WebPushPreviewContent {...defaultProps} />
170
+ </TestWrapper>
171
+ );
172
+ expect(screen.getByTestId('selected-os')).toHaveTextContent('macOS');
173
+ });
174
+
175
+ it('should render with default browser (Chrome)', () => {
176
+ render(
177
+ <TestWrapper>
178
+ <WebPushPreviewContent {...defaultProps} />
179
+ </TestWrapper>
180
+ );
181
+ expect(screen.getByTestId('selected-browser')).toHaveTextContent('Chrome');
182
+ });
183
+
184
+ it('should pass default OS to PreviewContent', () => {
185
+ render(
186
+ <TestWrapper>
187
+ <WebPushPreviewContent {...defaultProps} />
188
+ </TestWrapper>
189
+ );
190
+ expect(screen.getByTestId('content-os')).toHaveTextContent('macOS');
191
+ });
192
+
193
+ it('should pass default browser to PreviewContent', () => {
194
+ render(
195
+ <TestWrapper>
196
+ <WebPushPreviewContent {...defaultProps} />
197
+ </TestWrapper>
198
+ );
199
+ expect(screen.getByTestId('content-browser')).toHaveTextContent('Chrome');
200
+ });
201
+ });
202
+
203
+ describe('Props forwarding to PreviewContent', () => {
204
+ it('should pass notificationTitle to PreviewContent', () => {
205
+ render(
206
+ <TestWrapper>
207
+ <WebPushPreviewContent {...defaultProps} />
208
+ </TestWrapper>
209
+ );
210
+ expect(screen.getByTestId('content-title')).toHaveTextContent('Test Title');
211
+ });
212
+
213
+ it('should pass notificationBody to PreviewContent', () => {
214
+ render(
215
+ <TestWrapper>
216
+ <WebPushPreviewContent {...defaultProps} />
217
+ </TestWrapper>
218
+ );
219
+ expect(screen.getByTestId('content-body')).toHaveTextContent('Test body text');
220
+ });
221
+
222
+ it('should pass url to PreviewContent', () => {
223
+ render(
224
+ <TestWrapper>
225
+ <WebPushPreviewContent {...defaultProps} />
226
+ </TestWrapper>
227
+ );
228
+ expect(screen.getByTestId('content-url')).toHaveTextContent('https://example.com');
229
+ });
230
+
231
+ it('should pass imageSrc to PreviewContent', () => {
232
+ render(
233
+ <TestWrapper>
234
+ <WebPushPreviewContent {...defaultProps} />
235
+ </TestWrapper>
236
+ );
237
+ expect(screen.getByTestId('content-image')).toHaveTextContent('https://example.com/image.jpg');
238
+ });
239
+
240
+ it('should pass brandIconSrc to PreviewContent', () => {
241
+ render(
242
+ <TestWrapper>
243
+ <WebPushPreviewContent {...defaultProps} />
244
+ </TestWrapper>
245
+ );
246
+ expect(screen.getByTestId('content-icon')).toHaveTextContent('https://example.com/icon.png');
247
+ });
248
+
249
+ it('should pass buttons to PreviewContent', () => {
250
+ render(
251
+ <TestWrapper>
252
+ <WebPushPreviewContent {...defaultProps} />
253
+ </TestWrapper>
254
+ );
255
+ const buttons = JSON.parse(screen.getByTestId('content-buttons').textContent);
256
+ expect(buttons).toHaveLength(1);
257
+ expect(buttons[0]).toEqual({ text: 'Click', url: 'https://example.com', type: 'EXTERNAL_URL' });
258
+ });
259
+ });
260
+
261
+ describe('OS and browser change handlers', () => {
262
+ it('should update selected OS when handleOSChange is called', () => {
263
+ render(
264
+ <TestWrapper>
265
+ <WebPushPreviewContent {...defaultProps} />
266
+ </TestWrapper>
267
+ );
268
+
269
+ fireEvent.click(screen.getByTestId('os-change-btn'));
270
+ expect(screen.getByTestId('selected-os')).toHaveTextContent('Windows');
271
+ });
272
+
273
+ it('should update selected browser when handleBrowserChange is called', () => {
274
+ render(
275
+ <TestWrapper>
276
+ <WebPushPreviewContent {...defaultProps} />
277
+ </TestWrapper>
278
+ );
279
+
280
+ fireEvent.click(screen.getByTestId('browser-change-btn'));
281
+ expect(screen.getByTestId('selected-browser')).toHaveTextContent('Firefox');
282
+ });
283
+
284
+ it('should pass updated OS to PreviewContent after OS change', () => {
285
+ render(
286
+ <TestWrapper>
287
+ <WebPushPreviewContent {...defaultProps} />
288
+ </TestWrapper>
289
+ );
290
+
291
+ fireEvent.click(screen.getByTestId('os-change-btn'));
292
+ expect(screen.getByTestId('content-os')).toHaveTextContent('Windows');
293
+ });
294
+ });
295
+
296
+ describe('Browser coercion on OS change', () => {
297
+ it('should keep Chrome when switching from macOS to Windows (Chrome is valid on both)', () => {
298
+ // Chrome is supported on all OS platforms, so switching macOS → Windows should NOT coerce.
299
+ render(
300
+ <TestWrapper>
301
+ <WebPushPreviewContent {...defaultProps} />
302
+ </TestWrapper>
303
+ );
304
+
305
+ expect(screen.getByTestId('selected-browser')).toHaveTextContent('Chrome');
306
+ fireEvent.click(screen.getByTestId('os-change-btn')); // sets OS to Windows
307
+ // Chrome is still valid on Windows, so browser stays Chrome (no coercion)
308
+ expect(screen.getByTestId('selected-browser')).toHaveTextContent('Chrome');
309
+ });
310
+
311
+ it('should update OS state when OS is changed', () => {
312
+ render(
313
+ <TestWrapper>
314
+ <WebPushPreviewContent {...defaultProps} />
315
+ </TestWrapper>
316
+ );
317
+
318
+ fireEvent.click(screen.getByTestId('os-change-btn')); // sets OS to Windows
319
+ expect(screen.getByTestId('selected-os')).toHaveTextContent('Windows');
320
+ });
321
+
322
+ it('should allow explicit browser change independent of OS', () => {
323
+ render(
324
+ <TestWrapper>
325
+ <WebPushPreviewContent {...defaultProps} />
326
+ </TestWrapper>
327
+ );
328
+
329
+ fireEvent.click(screen.getByTestId('browser-change-btn')); // sets browser to Firefox
330
+ expect(screen.getByTestId('selected-browser')).toHaveTextContent('Firefox');
331
+ });
332
+ });
333
+
334
+ describe('Fullscreen modal', () => {
335
+ it('should NOT show modal when isFullscreenOpen is false', () => {
336
+ render(
337
+ <TestWrapper>
338
+ <WebPushPreviewContent {...defaultProps} isFullscreenOpen={false} />
339
+ </TestWrapper>
340
+ );
341
+
342
+ const modal = screen.getByTestId('cap-modal');
343
+ expect(modal.getAttribute('data-visible')).toBe('false');
344
+ expect(screen.queryByTestId('modal-content')).toBeNull();
345
+ });
346
+
347
+ it('should show modal when isFullscreenOpen is true', () => {
348
+ render(
349
+ <TestWrapper>
350
+ <WebPushPreviewContent {...defaultProps} isFullscreenOpen={true} />
351
+ </TestWrapper>
352
+ );
353
+
354
+ const modal = screen.getByTestId('cap-modal');
355
+ expect(modal.getAttribute('data-visible')).toBe('true');
356
+ expect(screen.getByTestId('modal-content')).toBeTruthy();
357
+ });
358
+
359
+ it('should render DevicePreviewContent inside modal', () => {
360
+ render(
361
+ <TestWrapper>
362
+ <WebPushPreviewContent {...defaultProps} isFullscreenOpen={true} />
363
+ </TestWrapper>
364
+ );
365
+
366
+ expect(screen.getByTestId('device-preview-content')).toBeTruthy();
367
+ });
368
+
369
+ it('should pass notificationTitle to DevicePreviewContent', () => {
370
+ render(
371
+ <TestWrapper>
372
+ <WebPushPreviewContent {...defaultProps} isFullscreenOpen={true} />
373
+ </TestWrapper>
374
+ );
375
+
376
+ expect(screen.getByTestId('device-title')).toHaveTextContent('Test Title');
377
+ });
378
+
379
+ it('should pass notificationBody to DevicePreviewContent', () => {
380
+ render(
381
+ <TestWrapper>
382
+ <WebPushPreviewContent {...defaultProps} isFullscreenOpen={true} />
383
+ </TestWrapper>
384
+ );
385
+
386
+ expect(screen.getByTestId('device-body')).toHaveTextContent('Test body text');
387
+ });
388
+
389
+ it('should call setIsFullscreenOpen(false) when close icon is clicked', () => {
390
+ const setIsFullscreenOpen = jest.fn();
391
+ render(
392
+ <TestWrapper>
393
+ <WebPushPreviewContent
394
+ {...defaultProps}
395
+ isFullscreenOpen={true}
396
+ setIsFullscreenOpen={setIsFullscreenOpen}
397
+ />
398
+ </TestWrapper>
399
+ );
400
+
401
+ fireEvent.click(screen.getByTestId('cap-icon-collapse2'));
402
+ expect(setIsFullscreenOpen).toHaveBeenCalledWith(false);
403
+ });
404
+
405
+ it('should call setIsFullscreenOpen(false) when modal onCancel fires', () => {
406
+ const setIsFullscreenOpen = jest.fn();
407
+ render(
408
+ <TestWrapper>
409
+ <WebPushPreviewContent
410
+ {...defaultProps}
411
+ isFullscreenOpen={true}
412
+ setIsFullscreenOpen={setIsFullscreenOpen}
413
+ />
414
+ </TestWrapper>
415
+ );
416
+
417
+ fireEvent.click(screen.getByTestId('modal-cancel'));
418
+ expect(setIsFullscreenOpen).toHaveBeenCalledWith(false);
419
+ });
420
+
421
+ it('should display selectedCustomer name in modal header when customer is provided', () => {
422
+ render(
423
+ <TestWrapper>
424
+ <WebPushPreviewContent
425
+ {...defaultProps}
426
+ isFullscreenOpen={true}
427
+ selectedCustomer={{ name: 'Alice' }}
428
+ />
429
+ </TestWrapper>
430
+ );
431
+
432
+ expect(screen.getByText('Alice')).toBeTruthy();
433
+ });
434
+
435
+ it('should display default preview text in modal header when no customer', () => {
436
+ render(
437
+ <TestWrapper>
438
+ <WebPushPreviewContent
439
+ {...defaultProps}
440
+ isFullscreenOpen={true}
441
+ selectedCustomer={null}
442
+ />
443
+ </TestWrapper>
444
+ );
445
+
446
+ // FormattedMessage renders defaultMessage for defaultPreview
447
+ expect(screen.queryByText('Alice')).toBeNull();
448
+ });
449
+ });
450
+
451
+ describe('Default props', () => {
452
+ it('should render with minimal props (title and body only)', () => {
453
+ render(
454
+ <TestWrapper>
455
+ <WebPushPreviewContent
456
+ notificationTitle="Min title"
457
+ notificationBody="Min body"
458
+ />
459
+ </TestWrapper>
460
+ );
461
+
462
+ expect(screen.getByTestId('content-title')).toHaveTextContent('Min title');
463
+ expect(screen.getByTestId('content-body')).toHaveTextContent('Min body');
464
+ });
465
+
466
+ it('should render with no props at all (all defaults)', () => {
467
+ render(
468
+ <TestWrapper>
469
+ <WebPushPreviewContent />
470
+ </TestWrapper>
471
+ );
472
+
473
+ expect(screen.getByTestId('preview-controls')).toBeTruthy();
474
+ });
475
+
476
+ it('should render empty buttons array by default', () => {
477
+ render(
478
+ <TestWrapper>
479
+ <WebPushPreviewContent notificationTitle="T" notificationBody="B" />
480
+ </TestWrapper>
481
+ );
482
+
483
+ const buttons = JSON.parse(screen.getByTestId('content-buttons').textContent);
484
+ expect(buttons).toEqual([]);
485
+ });
486
+ });
487
+
488
+ describe('PreviewControls props', () => {
489
+ it('should pass OS_OPTIONS to PreviewControls', () => {
490
+ render(
491
+ <TestWrapper>
492
+ <WebPushPreviewContent {...defaultProps} />
493
+ </TestWrapper>
494
+ );
495
+ // PreviewControls is rendered
496
+ expect(screen.getByTestId('preview-controls')).toBeTruthy();
497
+ });
498
+
499
+ it('should pass browserOptions derived from selectedOS to PreviewControls', () => {
500
+ render(
501
+ <TestWrapper>
502
+ <WebPushPreviewContent {...defaultProps} />
503
+ </TestWrapper>
504
+ );
505
+
506
+ // macOS supports all browsers including Safari
507
+ const browserOptions = JSON.parse(screen.getByTestId('browser-options').textContent);
508
+ expect(Array.isArray(browserOptions)).toBe(true);
509
+ expect(browserOptions.length).toBeGreaterThan(0);
510
+ });
511
+
512
+ it('should pass layoutMode="newRow" to PreviewControls', () => {
513
+ // PreviewControls mock just renders - verifying no crash when layoutMode is set
514
+ render(
515
+ <TestWrapper>
516
+ <WebPushPreviewContent {...defaultProps} />
517
+ </TestWrapper>
518
+ );
519
+ expect(screen.getByTestId('preview-controls')).toBeTruthy();
520
+ });
521
+ });
522
+ });