@capillarytech/creatives-library 8.0.299-alpha.10 → 8.0.299-alpha.11

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.299-alpha.10",
4
+ "version": "8.0.299-alpha.11",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Tests for CustomValuesEditor Component
3
+ */
4
+
5
+ import '@testing-library/jest-dom';
6
+ import React from 'react';
7
+ import { render, screen, fireEvent } from '@testing-library/react';
8
+ import { IntlProvider } from 'react-intl';
9
+ import PropTypes from 'prop-types';
10
+ import CustomValuesEditor from '../CustomValuesEditor';
11
+
12
+ const scope = 'app.v2Components.TestAndPreviewSlidebox';
13
+ const mockMessages = {
14
+ [`${scope}.extractingTags`]: 'Extracting tags...',
15
+ [`${scope}.valuesMissing`]: 'Some values are missing',
16
+ [`${scope}.showJSON`]: 'Show JSON',
17
+ [`${scope}.personalizationTags`]: 'Personalization Tags',
18
+ [`${scope}.customValues`]: 'Custom Values',
19
+ [`${scope}.enterValue`]: 'Enter value',
20
+ [`${scope}.discardCustomValues`]: 'Discard custom values',
21
+ [`${scope}.updatePreview`]: 'Update Preview',
22
+ };
23
+
24
+ const TestWrapper = ({ children }) => (
25
+ <IntlProvider locale="en" messages={mockMessages}>
26
+ {children}
27
+ </IntlProvider>
28
+ );
29
+
30
+ TestWrapper.propTypes = {
31
+ children: PropTypes.node.isRequired,
32
+ };
33
+
34
+ describe('CustomValuesEditor', () => {
35
+ const defaultProps = {
36
+ isExtractingTags: false,
37
+ isUpdatePreviewDisabled: false,
38
+ showJSON: false,
39
+ setShowJSON: jest.fn(),
40
+ customValues: { 'user.firstName': 'John', 'user.lastName': 'Doe' },
41
+ handleJSONTextChange: jest.fn(),
42
+ extractedTags: [
43
+ { name: 'firstName', fullPath: 'user.firstName' },
44
+ { name: 'lastName', fullPath: 'user.lastName' },
45
+ ],
46
+ requiredTags: [{ name: 'firstName', fullPath: 'user.firstName' }],
47
+ optionalTags: [{ name: 'lastName', fullPath: 'user.lastName' }],
48
+ handleCustomValueChange: jest.fn(),
49
+ handleDiscardCustomValues: jest.fn(),
50
+ handleUpdatePreview: jest.fn(),
51
+ isUpdatingPreview: false,
52
+ formatMessage: jest.fn((msg) => msg.defaultMessage || msg.id),
53
+ };
54
+
55
+ beforeEach(() => {
56
+ jest.clearAllMocks();
57
+ });
58
+
59
+ it('renders loading state when isExtractingTags is true', () => {
60
+ render(
61
+ <TestWrapper>
62
+ <CustomValuesEditor {...defaultProps} isExtractingTags />
63
+ </TestWrapper>
64
+ );
65
+ expect(screen.getByText('Extracting tags...')).toBeInTheDocument();
66
+ });
67
+
68
+ it('renders values missing message when isUpdatePreviewDisabled is true', () => {
69
+ render(
70
+ <TestWrapper>
71
+ <CustomValuesEditor {...defaultProps} isUpdatePreviewDisabled />
72
+ </TestWrapper>
73
+ );
74
+ expect(screen.getByText(/Some values are missing/i)).toBeInTheDocument();
75
+ });
76
+
77
+ it('renders Show JSON toggle and calls setShowJSON when toggled', () => {
78
+ render(
79
+ <TestWrapper>
80
+ <CustomValuesEditor {...defaultProps} />
81
+ </TestWrapper>
82
+ );
83
+ expect(screen.getByText('Show JSON')).toBeInTheDocument();
84
+ const switchInput = document.querySelector('input[type="checkbox"]');
85
+ if (switchInput) {
86
+ fireEvent.click(switchInput);
87
+ expect(defaultProps.setShowJSON).toHaveBeenCalled();
88
+ }
89
+ });
90
+
91
+ it('renders JSON editor when showJSON is true', () => {
92
+ render(
93
+ <TestWrapper>
94
+ <CustomValuesEditor {...defaultProps} showJSON />
95
+ </TestWrapper>
96
+ );
97
+ const textarea = document.querySelector('textarea.json-textarea');
98
+ expect(textarea).toBeInTheDocument();
99
+ expect(textarea.value).toContain('user.firstName');
100
+ });
101
+
102
+ it('calls handleJSONTextChange when JSON textarea is changed', () => {
103
+ render(
104
+ <TestWrapper>
105
+ <CustomValuesEditor {...defaultProps} showJSON />
106
+ </TestWrapper>
107
+ );
108
+ const textarea = document.querySelector('textarea.json-textarea');
109
+ fireEvent.change(textarea, { target: { value: '{"foo":"bar"}' } });
110
+ expect(defaultProps.handleJSONTextChange).toHaveBeenCalledWith('{"foo":"bar"}');
111
+ });
112
+
113
+ it('renders required and optional tag inputs when showJSON is false', () => {
114
+ render(
115
+ <TestWrapper>
116
+ <CustomValuesEditor {...defaultProps} />
117
+ </TestWrapper>
118
+ );
119
+ expect(screen.getByText('Personalization Tags')).toBeInTheDocument();
120
+ expect(screen.getByText('Custom Values')).toBeInTheDocument();
121
+ expect(screen.getByDisplayValue('John')).toBeInTheDocument();
122
+ expect(screen.getByDisplayValue('Doe')).toBeInTheDocument();
123
+ });
124
+
125
+ it('calls handleCustomValueChange when tag input is changed', () => {
126
+ render(
127
+ <TestWrapper>
128
+ <CustomValuesEditor {...defaultProps} />
129
+ </TestWrapper>
130
+ );
131
+ const inputs = document.querySelectorAll('input[type="text"]');
132
+ fireEvent.change(inputs[0], { target: { value: 'Jane' } });
133
+ expect(defaultProps.handleCustomValueChange).toHaveBeenCalledWith('user.firstName', 'Jane');
134
+ });
135
+
136
+ it('calls handleDiscardCustomValues when Discard button is clicked', () => {
137
+ render(
138
+ <TestWrapper>
139
+ <CustomValuesEditor {...defaultProps} />
140
+ </TestWrapper>
141
+ );
142
+ const discardBtn = screen.getByText(/Discard custom values/i);
143
+ fireEvent.click(discardBtn);
144
+ expect(defaultProps.handleDiscardCustomValues).toHaveBeenCalled();
145
+ });
146
+
147
+ it('calls handleUpdatePreview when Update Preview button is clicked', () => {
148
+ render(
149
+ <TestWrapper>
150
+ <CustomValuesEditor {...defaultProps} />
151
+ </TestWrapper>
152
+ );
153
+ const updateBtn = screen.getByText('Update Preview');
154
+ fireEvent.click(updateBtn);
155
+ expect(defaultProps.handleUpdatePreview).toHaveBeenCalled();
156
+ });
157
+
158
+ it('renders nothing for tags table when extractedTags is empty', () => {
159
+ render(
160
+ <TestWrapper>
161
+ <CustomValuesEditor
162
+ {...defaultProps}
163
+ extractedTags={[]}
164
+ requiredTags={[]}
165
+ optionalTags={[]}
166
+ />
167
+ </TestWrapper>
168
+ );
169
+ expect(screen.getByText(/Show JSON/i)).toBeInTheDocument();
170
+ expect(screen.queryByText(/Personalization Tags/i)).not.toBeInTheDocument();
171
+ });
172
+ });
@@ -313,4 +313,154 @@ describe('CustomerCreationModal', () => {
313
313
  jest.advanceTimersByTime(1000);
314
314
  jest.useRealTimers();
315
315
  });
316
+
317
+ it('disables Save when required email is missing (EMAIL channel)', () => {
318
+ render(
319
+ <TestWrapper>
320
+ <CustomerCreationModal
321
+ {...defaultProps}
322
+ channel={CHANNELS.EMAIL}
323
+ customerData={{ name: '', email: '', mobile: '' }}
324
+ />
325
+ </TestWrapper>
326
+ );
327
+ const saveBtn = screen.getByRole('button', { name: /save/i });
328
+ expect(saveBtn.disabled).toBe(true);
329
+ });
330
+
331
+ it('disables Save when required mobile is missing (SMS channel)', () => {
332
+ render(
333
+ <TestWrapper>
334
+ <CustomerCreationModal
335
+ {...defaultProps}
336
+ channel={CHANNELS.SMS}
337
+ customerData={{ name: '', email: '', mobile: '' }}
338
+ />
339
+ </TestWrapper>
340
+ );
341
+ const saveBtn = screen.getByRole('button', { name: /save/i });
342
+ expect(saveBtn.disabled).toBe(true);
343
+ });
344
+
345
+ it('disables Save and Cancel during lookup', async () => {
346
+ let resolveLookup;
347
+ mockGetMembersLookup.mockImplementation(() => new Promise((resolve) => { resolveLookup = resolve; }));
348
+ render(
349
+ <TestWrapper>
350
+ <CustomerCreationModal {...defaultProps} customerData={{ name: '', email: '', mobile: '' }} />
351
+ </TestWrapper>
352
+ );
353
+ const emailInput = screen.getByPlaceholderText(/enter the email/i);
354
+ fireEvent.change(emailInput, { target: { value: 'user@example.com' } });
355
+ await waitFor(() => expect(mockGetMembersLookup).toHaveBeenCalled());
356
+ await waitFor(() => {
357
+ expect(screen.getByRole('button', { name: /save/i }).disabled).toBe(true);
358
+ expect(screen.getByRole('button', { name: /cancel/i }).disabled).toBe(true);
359
+ });
360
+ resolveLookup({ success: true, status: {}, response: { exists: false, customerDetails: [] } });
361
+ });
362
+
363
+ it('shows email already exists when lookup returns "merged customer found"', async () => {
364
+ jest.useFakeTimers();
365
+ mockGetMembersLookup.mockResolvedValue({
366
+ success: false,
367
+ message: 'Merged customer found',
368
+ status: {},
369
+ response: {},
370
+ });
371
+ render(
372
+ <TestWrapper>
373
+ <CustomerCreationModal {...defaultProps} customerData={{ name: '', email: '', mobile: '' }} />
374
+ </TestWrapper>
375
+ );
376
+ const emailInput = screen.getByPlaceholderText(/enter the email/i);
377
+ fireEvent.change(emailInput, { target: { value: 'merged@example.com' } });
378
+ jest.advanceTimersByTime(600);
379
+ await waitFor(() => expect(mockGetMembersLookup).toHaveBeenCalledWith('email', 'merged@example.com'));
380
+ await waitFor(() => {
381
+ expect(screen.queryByText(/this email already exists|already existing user profile/i)).toBeTruthy();
382
+ }, { timeout: 1000 });
383
+ jest.useRealTimers();
384
+ });
385
+
386
+ it('does not show already exists when lookup returns success: false with other message', async () => {
387
+ jest.useFakeTimers();
388
+ mockGetMembersLookup.mockResolvedValue({
389
+ success: false,
390
+ message: 'Server error',
391
+ status: {},
392
+ response: {},
393
+ });
394
+ render(
395
+ <TestWrapper>
396
+ <CustomerCreationModal {...defaultProps} customerData={{ name: '', email: '', mobile: '' }} />
397
+ </TestWrapper>
398
+ );
399
+ const emailInput = screen.getByPlaceholderText(/enter the email/i);
400
+ fireEvent.change(emailInput, { target: { value: 'user@example.com' } });
401
+ jest.advanceTimersByTime(600);
402
+ await waitFor(() => expect(mockGetMembersLookup).toHaveBeenCalled());
403
+ await waitFor(() => {
404
+ expect(screen.queryByText(/this email already exists/i)).toBeFalsy();
405
+ });
406
+ jest.useRealTimers();
407
+ });
408
+
409
+ it('does not show already exists when response has status.isError', async () => {
410
+ jest.useFakeTimers();
411
+ mockGetMembersLookup.mockResolvedValue({
412
+ success: true,
413
+ status: { isError: true },
414
+ response: { exists: false, customerDetails: [] },
415
+ });
416
+ render(
417
+ <TestWrapper>
418
+ <CustomerCreationModal {...defaultProps} customerData={{ name: '', email: '', mobile: '' }} />
419
+ </TestWrapper>
420
+ );
421
+ const emailInput = screen.getByPlaceholderText(/enter the email/i);
422
+ fireEvent.change(emailInput, { target: { value: 'user@example.com' } });
423
+ jest.advanceTimersByTime(600);
424
+ await waitFor(() => expect(mockGetMembersLookup).toHaveBeenCalled());
425
+ await waitFor(() => {
426
+ expect(screen.queryByText(/this email already exists/i)).toBeFalsy();
427
+ });
428
+ jest.useRealTimers();
429
+ });
430
+
431
+ it('shows (Optional) for email when channel is SMS', () => {
432
+ render(
433
+ <TestWrapper>
434
+ <CustomerCreationModal
435
+ {...defaultProps}
436
+ channel={CHANNELS.SMS}
437
+ customerData={{ name: '', email: '', mobile: '' }}
438
+ />
439
+ </TestWrapper>
440
+ );
441
+ const optionals = screen.getAllByText(/\(optional\)/i);
442
+ expect(optionals.length).toBeGreaterThanOrEqual(2);
443
+ });
444
+
445
+ it('shows (Optional) for mobile when channel is EMAIL', () => {
446
+ render(
447
+ <TestWrapper>
448
+ <CustomerCreationModal
449
+ {...defaultProps}
450
+ channel={CHANNELS.EMAIL}
451
+ customerData={{ name: '', email: '', mobile: '' }}
452
+ />
453
+ </TestWrapper>
454
+ );
455
+ expect(screen.getAllByText(/\(optional\)/i).length).toBeGreaterThanOrEqual(2);
456
+ });
457
+
458
+ it('renders modal description', () => {
459
+ render(
460
+ <TestWrapper>
461
+ <CustomerCreationModal {...defaultProps} />
462
+ </TestWrapper>
463
+ );
464
+ expect(screen.getByText(/this customer profile will be available for testing/i)).toBeTruthy();
465
+ });
316
466
  });
@@ -1585,6 +1585,35 @@ describe('CommonTestAndPreview', () => {
1585
1585
 
1586
1586
  expect(screen.getByTestId('send-test-message')).toBeTruthy();
1587
1587
  });
1588
+
1589
+ it('should show error notification when sendTestMessageRequested callback receives false', async () => {
1590
+ const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
1591
+ mockActions.createMessageMetaRequested.mockImplementation((payload, metaId, cb) => {
1592
+ if (cb) cb({ entity: 'meta-123' });
1593
+ });
1594
+ mockActions.sendTestMessageRequested.mockImplementation((payload, cb) => {
1595
+ if (cb) cb(false);
1596
+ });
1597
+ const props = {
1598
+ ...defaultProps,
1599
+ selectedTestEntities: ['user-1'],
1600
+ testGroups: [],
1601
+ };
1602
+
1603
+ render(
1604
+ <TestWrapper>
1605
+ <CommonTestAndPreview {...props} />
1606
+ </TestWrapper>
1607
+ );
1608
+
1609
+ expect(lastSendTestMessageProps).toBeTruthy();
1610
+ expect(lastSendTestMessageProps.handleSendTestMessage).toBeDefined();
1611
+ lastSendTestMessageProps.handleSendTestMessage();
1612
+
1613
+ await waitFor(() => {
1614
+ expect(CapNotification.error).toHaveBeenCalled();
1615
+ });
1616
+ });
1588
1617
  });
1589
1618
 
1590
1619
  describe('Content Extraction', () => {