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

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.
@@ -410,7 +410,7 @@ const CommonTestAndPreview = (props) => {
410
410
  /**
411
411
  * Common handler for saving test customers (both new and existing)
412
412
  */
413
- const handleSaveTestCustomer = async (validationErrors = {},setIsLoading = false) => {
413
+ const handleSaveTestCustomer = async (validationErrors = {},setIsLoading = () => {}) => {
414
414
  // Check for validation errors before saving (for new customers)
415
415
  if (customerModal[1] === CUSTOMER_MODAL_NEW && (validationErrors.email || validationErrors.mobile)) {
416
416
  return;
@@ -2676,7 +2676,10 @@ const CommonTestAndPreview = (props) => {
2676
2676
 
2677
2677
  if (!success) {
2678
2678
  const errorMessage = response?.message || response?.status?.message || formatMessage(messages.memberLookupError);
2679
- CapNotification.error({ title: formatMessage(messages.errorTitle), message: errorMessage });
2679
+ CapNotification.error({
2680
+ message: formatMessage(messages.memberLookupError),
2681
+ description: errorMessage,
2682
+ });
2680
2683
  return;
2681
2684
  }
2682
2685
 
@@ -2710,6 +2713,7 @@ const CommonTestAndPreview = (props) => {
2710
2713
  } catch {
2711
2714
  CapNotification.error({
2712
2715
  message: formatMessage(messages.memberLookupError),
2716
+ description: formatMessage(messages.memberLookupError),
2713
2717
  });
2714
2718
  } finally {
2715
2719
  setIsCustomerDataLoading(false);
@@ -361,6 +361,7 @@ describe('CommonTestAndPreview – Add Test Customer flow', () => {
361
361
  await waitFor(() => expect(mockGetMembersLookup).toHaveBeenCalled());
362
362
  await waitFor(() => expect(CapNotification.error).toHaveBeenCalledWith(expect.objectContaining({
363
363
  message: 'Unable to look up customer. Please try again.',
364
+ description: 'Unable to look up customer. Please try again.',
364
365
  })));
365
366
  expect(screen.queryByText(/add new test customer/i)).toBeFalsy();
366
367
  });
@@ -573,7 +574,8 @@ describe('CommonTestAndPreview – Add Test Customer flow', () => {
573
574
  await clickAddTestCustomer();
574
575
  await waitFor(() => expect(mockGetMembersLookup).toHaveBeenCalled());
575
576
  await waitFor(() => expect(CapNotification.error).toHaveBeenCalledWith(expect.objectContaining({
576
- message: 'Merged customer found',
577
+ message: 'Unable to look up customer. Please try again.',
578
+ description: 'Merged customer found',
577
579
  })));
578
580
  expect(screen.queryByText(/add new test customer/i)).toBeFalsy();
579
581
  expect(screen.queryByText(/this user profile already exists/i)).toBeFalsy();
@@ -595,7 +597,8 @@ describe('CommonTestAndPreview – Add Test Customer flow', () => {
595
597
  await clickAddTestCustomer();
596
598
  await waitFor(() => expect(mockGetMembersLookup).toHaveBeenCalled());
597
599
  await waitFor(() => expect(CapNotification.error).toHaveBeenCalledWith(expect.objectContaining({
598
- message: 'Server error',
600
+ message: 'Unable to look up customer. Please try again.',
601
+ description: 'Server error',
599
602
  })));
600
603
  expect(screen.queryByText(/add new test customer/i)).toBeFalsy();
601
604
  expect(screen.queryByText(/this user profile already exists/i)).toBeFalsy();
@@ -617,6 +620,7 @@ describe('CommonTestAndPreview – Add Test Customer flow', () => {
617
620
  await clickAddTestCustomer();
618
621
  await waitFor(() => expect(CapNotification.error).toHaveBeenCalledWith(expect.objectContaining({
619
622
  message: 'Unable to look up customer. Please try again.',
623
+ description: 'Unable to look up customer. Please try again.',
620
624
  })));
621
625
  });
622
626
  });
@@ -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', () => {