@capillarytech/creatives-library 8.0.330-alpha.0 → 8.0.331-alpha.0

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 (111) hide show
  1. package/constants/unified.js +0 -18
  2. package/package.json +1 -1
  3. package/services/tests/api.test.js +0 -13
  4. package/utils/commonUtils.js +1 -19
  5. package/v2Components/CapTagList/index.js +0 -10
  6. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -70
  7. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  8. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -207
  9. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  10. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
  11. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
  13. package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +1 -0
  14. package/v2Components/CommonTestAndPreview/SendTestMessage.js +5 -10
  15. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +1 -20
  16. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
  17. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -11
  18. package/v2Components/CommonTestAndPreview/constants.js +2 -38
  19. package/v2Components/CommonTestAndPreview/index.js +176 -672
  20. package/v2Components/CommonTestAndPreview/messages.js +3 -41
  21. package/v2Components/CommonTestAndPreview/reducer.js +3 -1
  22. package/v2Components/CommonTestAndPreview/sagas.js +8 -16
  23. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +284 -308
  24. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
  25. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
  26. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
  27. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +13 -34
  28. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
  29. package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -132
  30. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  31. package/v2Components/FormBuilder/index.js +1 -7
  32. package/v2Components/TestAndPreviewSlidebox/index.js +1 -13
  33. package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
  34. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
  35. package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
  36. package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
  37. package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
  38. package/v2Containers/CreativesContainer/constants.js +0 -9
  39. package/v2Containers/CreativesContainer/index.js +93 -292
  40. package/v2Containers/CreativesContainer/index.scss +1 -51
  41. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
  42. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  43. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  44. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
  45. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +10 -20
  46. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  47. package/v2Containers/Rcs/constants.js +3 -40
  48. package/v2Containers/Rcs/index.js +895 -1145
  49. package/v2Containers/Rcs/index.scss +6 -85
  50. package/v2Containers/Rcs/messages.js +2 -12
  51. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1432 -40783
  52. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
  53. package/v2Containers/Rcs/tests/index.test.js +38 -41
  54. package/v2Containers/Rcs/tests/mockData.js +0 -38
  55. package/v2Containers/Rcs/tests/utils.test.js +1 -435
  56. package/v2Containers/Rcs/utils.js +10 -405
  57. package/v2Containers/Sms/Create/index.js +38 -100
  58. package/v2Containers/SmsTrai/Create/index.js +4 -9
  59. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  60. package/v2Containers/SmsTrai/Edit/index.js +128 -636
  61. package/v2Containers/SmsTrai/Edit/messages.js +4 -14
  62. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2296 -4249
  63. package/v2Containers/SmsWrapper/index.js +8 -37
  64. package/v2Containers/TagList/index.js +11 -21
  65. package/v2Containers/Templates/_templates.scss +2 -63
  66. package/v2Containers/Templates/actions.js +0 -11
  67. package/v2Containers/Templates/constants.js +0 -2
  68. package/v2Containers/Templates/index.js +40 -90
  69. package/v2Containers/Templates/sagas.js +12 -57
  70. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1079 -1043
  71. package/v2Containers/Templates/tests/sagas.test.js +123 -193
  72. package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
  73. package/v2Containers/TemplatesV2/index.js +23 -86
  74. package/v2Containers/WebPush/Create/index.js +1 -1
  75. package/v2Containers/Whatsapp/index.js +20 -3
  76. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -578
  77. package/utils/templateVarUtils.js +0 -201
  78. package/utils/tests/templateVarUtils.test.js +0 -204
  79. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  80. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  81. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -87
  82. package/v2Components/SmsFallback/constants.js +0 -73
  83. package/v2Components/SmsFallback/index.js +0 -955
  84. package/v2Components/SmsFallback/index.scss +0 -265
  85. package/v2Components/SmsFallback/messages.js +0 -78
  86. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -118
  87. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  88. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  89. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  90. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -197
  91. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -277
  92. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  93. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  94. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  95. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  96. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  97. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
  98. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -67
  99. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  100. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  101. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  102. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -225
  103. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
  104. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  105. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  106. package/v2Containers/SmsTrai/Edit/index.scss +0 -121
  107. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  108. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  109. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  110. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  111. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
@@ -1,95 +1,83 @@
1
1
  /**
2
+ * Tests for CustomValuesEditor component.
3
+ * Covers: loading state, values-missing warning, JSON view, tag table, discard/update buttons.
2
4
  * @jest-environment jsdom
3
5
  */
6
+
4
7
  import React from 'react';
5
8
  import { render, screen, fireEvent } from '@testing-library/react';
6
- import '@testing-library/jest-dom';
7
9
  import { IntlProvider } from 'react-intl';
8
10
  import PropTypes from 'prop-types';
9
- import CustomValuesEditor from '../CustomValuesEditor';
10
11
 
11
- jest.mock('@capillarytech/cap-ui-library/CapRow', () => {
12
- const React = require('react');
13
- return function CapRow(props) {
14
- return React.createElement('div', { className: props?.className, 'data-testid': 'cap-row' }, props?.children);
15
- };
16
- });
17
- jest.mock('@capillarytech/cap-ui-library/CapSpin', () => {
18
- const React = require('react');
19
- return function CapSpin() {
20
- return React.createElement('div', { 'data-testid': 'cap-spin' }, 'spin');
21
- };
22
- });
23
- jest.mock('@capillarytech/cap-ui-library/CapSwitch', () => {
24
- const React = require('react');
25
- return function CapSwitch(props) {
26
- return React.createElement('input', {
27
- type: 'checkbox',
28
- 'data-testid': 'json-switch',
29
- checked: props?.checked,
30
- onChange: (e) => props?.onChange?.(e.target.checked),
31
- });
32
- };
33
- });
34
- jest.mock('@capillarytech/cap-ui-library/CapButton', () => {
35
- const React = require('react');
36
- return function CapButton(props) {
37
- return React.createElement('button', {
38
- type: 'button',
39
- 'data-testid': 'cap-button',
40
- onClick: props?.onClick,
41
- disabled: props?.disabled,
42
- 'data-loading': props?.loading,
43
- }, props?.children);
44
- };
45
- });
46
- jest.mock('@capillarytech/cap-ui-library/CapInput', () => {
47
- const React = require('react');
48
- return function CapInput(props) {
49
- return React.createElement('input', {
50
- 'data-testid': 'tag-input',
51
- value: props?.value,
52
- placeholder: props?.placeholder,
53
- onChange: props?.onChange,
54
- });
55
- };
56
- });
57
- jest.mock('@capillarytech/cap-ui-library/CapLabel', () => {
58
- const React = require('react');
59
- return function CapLabel(props) {
60
- return React.createElement('div', { className: props?.className, 'data-testid': 'cap-label' }, props?.children);
61
- };
62
- });
12
+ jest.mock('@capillarytech/cap-ui-library/CapRow', () => ({ children, className }) => (
13
+ <div className={className}>{children}</div>
14
+ ));
15
+ jest.mock('@capillarytech/cap-ui-library/CapSpin', () => ({ size }) => (
16
+ <div data-testid="cap-spin" data-size={size}>Loading...</div>
17
+ ));
18
+ jest.mock('@capillarytech/cap-ui-library/CapSwitch', () => ({ checked, onChange }) => (
19
+ <button
20
+ type="button"
21
+ data-testid="cap-switch"
22
+ aria-checked={String(checked)}
23
+ onClick={() => onChange(!checked)}
24
+ >
25
+ Toggle
26
+ </button>
27
+ ));
28
+ jest.mock('@capillarytech/cap-ui-library/CapButton', () => ({ children, onClick, disabled, loading, type: btnType, icon }) => (
29
+ <button
30
+ type="button"
31
+ data-testid="cap-button"
32
+ data-btntype={btnType}
33
+ data-icon={icon}
34
+ onClick={onClick}
35
+ disabled={!!(disabled || loading)}
36
+ >
37
+ {children}
38
+ </button>
39
+ ));
40
+ jest.mock('@capillarytech/cap-ui-library/CapInput', () => ({ value, onChange, placeholder, isRequired, className }) => (
41
+ <input
42
+ data-testid="cap-input"
43
+ className={className}
44
+ value={value || ''}
45
+ onChange={onChange}
46
+ placeholder={placeholder}
47
+ required={!!isRequired}
48
+ />
49
+ ));
50
+ jest.mock('@capillarytech/cap-ui-library/CapLabel', () => ({ children, type, className }) => (
51
+ <span data-testid="cap-label" data-type={type} className={className}>{children}</span>
52
+ ));
63
53
 
64
- const formatMessage = (msg) => (typeof msg === 'object' && msg?.id ? msg.id : String(msg));
54
+ import CustomValuesEditor from '../CustomValuesEditor';
65
55
 
66
- const baseProps = {
56
+ const defaultProps = {
67
57
  isExtractingTags: false,
68
58
  isUpdatePreviewDisabled: false,
69
59
  showJSON: false,
70
60
  setShowJSON: jest.fn(),
71
- customValues: { 'tag.a': 'v1' },
61
+ customValues: { '{{customer.name}}': 'Alice', '{{customer.email}}': '' },
72
62
  handleJSONTextChange: jest.fn(),
73
- sections: [
74
- {
75
- key: 'sec1',
76
- title: 'Section A',
77
- requiredTags: [{ fullPath: 'tag.a', name: 'a' }],
78
- optionalTags: [{ fullPath: 'tag.b', name: 'b' }],
79
- },
63
+ extractedTags: [
64
+ { fullPath: '{{customer.name}}' },
65
+ { fullPath: '{{customer.email}}' },
80
66
  ],
67
+ requiredTags: [{ fullPath: '{{customer.name}}' }],
68
+ optionalTags: [{ fullPath: '{{customer.email}}' }],
81
69
  handleCustomValueChange: jest.fn(),
82
70
  handleDiscardCustomValues: jest.fn(),
83
71
  handleUpdatePreview: jest.fn(),
84
72
  isUpdatingPreview: false,
85
- formatMessage,
73
+ formatMessage: jest.fn((msg) => msg.defaultMessage || msg.id),
86
74
  };
87
75
 
88
76
  // IntlProvider wrapper for incoming tests
89
77
  const scope = 'app.v2Components.TestAndPreviewSlidebox';
90
78
  const mockMessages = {
91
79
  [`${scope}.extractingTags`]: 'Extracting tags...',
92
- [`${scope}.valuesMissing`]: 'Some values are missing',
80
+ [`${scope}.valuesMissing`]: 'Values missing for some of the personalization tags',
93
81
  [`${scope}.showJSON`]: 'Show JSON',
94
82
  [`${scope}.personalizationTags`]: 'Personalization Tags',
95
83
  [`${scope}.customValues`]: 'Custom Values',
@@ -109,268 +97,256 @@ TestWrapper.propTypes = {
109
97
  };
110
98
 
111
99
  describe('CustomValuesEditor', () => {
112
- beforeEach(() => {
113
- jest.clearAllMocks();
114
- });
100
+ beforeEach(() => jest.clearAllMocks());
115
101
 
116
- it('shows loading state when isExtractingTags', () => {
117
- render(
118
- <IntlProvider locale="en" messages={{}}>
119
- <CustomValuesEditor {...baseProps} isExtractingTags />
120
- </IntlProvider>,
121
- );
122
- expect(screen.getByTestId('cap-spin')).toBeInTheDocument();
123
- });
102
+ describe('loading state (isExtractingTags)', () => {
103
+ it('renders a spinner and extracting-tags message when isExtractingTags is true', () => {
104
+ render(
105
+ <TestWrapper>
106
+ <CustomValuesEditor {...defaultProps} isExtractingTags />
107
+ </TestWrapper>
108
+ );
109
+ expect(screen.getByTestId('cap-spin')).toBeTruthy();
110
+ expect(screen.getByText('Extracting tags...')).toBeTruthy();
111
+ });
124
112
 
125
- it('shows values missing label when isUpdatePreviewDisabled', () => {
126
- render(
127
- <IntlProvider locale="en" messages={{}}>
128
- <CustomValuesEditor {...baseProps} isUpdatePreviewDisabled />
129
- </IntlProvider>,
130
- );
131
- expect(document.querySelector('.values-missing-message')).toBeTruthy();
132
- });
113
+ it('does not render the editor toggle when extracting tags', () => {
114
+ render(
115
+ <TestWrapper>
116
+ <CustomValuesEditor {...defaultProps} isExtractingTags />
117
+ </TestWrapper>
118
+ );
119
+ expect(screen.queryByTestId('cap-switch')).toBeNull();
120
+ expect(screen.queryAllByTestId('cap-button')).toHaveLength(0);
121
+ });
133
122
 
134
- it('toggles JSON mode and calls setShowJSON', () => {
135
- const setShowJSON = jest.fn();
136
- render(
137
- <IntlProvider locale="en" messages={{}}>
138
- <CustomValuesEditor {...baseProps} setShowJSON={setShowJSON} />
139
- </IntlProvider>,
140
- );
141
- fireEvent.click(screen.getByTestId('json-switch'));
142
- expect(setShowJSON).toHaveBeenCalled();
123
+ it('renders the editor (not spinner) when isExtractingTags is false', () => {
124
+ render(
125
+ <TestWrapper>
126
+ <CustomValuesEditor {...defaultProps} isExtractingTags={false} />
127
+ </TestWrapper>
128
+ );
129
+ expect(screen.queryByTestId('cap-spin')).toBeNull();
130
+ expect(screen.getByTestId('cap-switch')).toBeTruthy();
131
+ });
143
132
  });
144
133
 
145
- it('renders JSON textarea and fires handleJSONTextChange', () => {
146
- render(
147
- <IntlProvider locale="en" messages={{}}>
148
- <CustomValuesEditor {...baseProps} showJSON />
149
- </IntlProvider>,
150
- );
151
- const ta = document.querySelector('.json-textarea');
152
- expect(ta).toBeTruthy();
153
- fireEvent.change(ta, { target: { value: '{}' } });
154
- expect(baseProps.handleJSONTextChange).toHaveBeenCalledWith('{}');
155
- });
134
+ describe('values missing warning', () => {
135
+ it('shows values-missing label when isUpdatePreviewDisabled is true', () => {
136
+ render(
137
+ <TestWrapper>
138
+ <CustomValuesEditor {...defaultProps} isUpdatePreviewDisabled />
139
+ </TestWrapper>
140
+ );
141
+ expect(screen.getByText('Values missing for some of the personalization tags')).toBeTruthy();
142
+ });
156
143
 
157
- it('renders required and optional tag inputs when not JSON', () => {
158
- render(
159
- <IntlProvider locale="en" messages={{}}>
160
- <CustomValuesEditor {...baseProps} />
161
- </IntlProvider>,
162
- );
163
- const inputs = screen.getAllByTestId('tag-input');
164
- expect(inputs.length).toBeGreaterThan(0);
165
- fireEvent.change(inputs[0], { target: { value: 'new' } });
166
- expect(baseProps.handleCustomValueChange).toHaveBeenCalledWith('tag.a', 'new');
144
+ it('does not show values-missing label when isUpdatePreviewDisabled is false', () => {
145
+ render(
146
+ <TestWrapper>
147
+ <CustomValuesEditor {...defaultProps} isUpdatePreviewDisabled={false} />
148
+ </TestWrapper>
149
+ );
150
+ expect(
151
+ screen.queryByText('Values missing for some of the personalization tags')
152
+ ).toBeNull();
153
+ });
167
154
  });
168
155
 
169
- it('calls discard and update handlers', () => {
170
- render(
171
- <IntlProvider locale="en" messages={{}}>
172
- <CustomValuesEditor {...baseProps} />
173
- </IntlProvider>,
174
- );
175
- const buttons = screen.getAllByTestId('cap-button');
176
- fireEvent.click(buttons[0]);
177
- fireEvent.click(buttons[buttons.length - 1]);
178
- expect(baseProps.handleDiscardCustomValues).toHaveBeenCalled();
179
- expect(baseProps.handleUpdatePreview).toHaveBeenCalled();
180
- });
156
+ describe('JSON view (showJSON = true)', () => {
157
+ it('renders a textarea with the formatted JSON', () => {
158
+ const customValues = { name: 'Alice', age: 30 };
159
+ render(
160
+ <TestWrapper>
161
+ <CustomValuesEditor {...defaultProps} showJSON customValues={customValues} />
162
+ </TestWrapper>
163
+ );
164
+ const textarea = document.querySelector('textarea.json-textarea');
165
+ expect(textarea).toBeTruthy();
166
+ expect(textarea.value).toBe(JSON.stringify(customValues, null, 2));
167
+ });
181
168
 
182
- it('skips sections with no tags', () => {
183
- render(
184
- <IntlProvider locale="en" messages={{}}>
185
- <CustomValuesEditor
186
- {...baseProps}
187
- sections={[
188
- { key: 'empty', requiredTags: [], optionalTags: [] },
189
- baseProps.sections[0],
190
- ]}
191
- />
192
- </IntlProvider>,
193
- );
194
- expect(screen.getAllByTestId('tag-input').length).toBeGreaterThan(0);
195
- });
169
+ it('renders line numbers matching the number of JSON lines', () => {
170
+ const customValues = { a: 1, b: 2 };
171
+ render(
172
+ <TestWrapper>
173
+ <CustomValuesEditor {...defaultProps} showJSON customValues={customValues} />
174
+ </TestWrapper>
175
+ );
176
+ const lineCount = JSON.stringify(customValues, null, 2).split('\n').length;
177
+ const lineNumbers = document.querySelectorAll('.line-number');
178
+ expect(lineNumbers).toHaveLength(lineCount);
179
+ });
196
180
 
197
- it('renders section title as FormattedMessage when title is an intl message object (not a string)', () => {
198
- render(
199
- <IntlProvider locale="en" messages={{ 'section.title.id': 'Intl Section Title' }}>
200
- <CustomValuesEditor
201
- {...baseProps}
202
- sections={[
203
- {
204
- key: 'intl-sec',
205
- title: { id: 'section.title.id', defaultMessage: 'Intl Section Title' },
206
- requiredTags: [{ fullPath: 'tag.a', name: 'a' }],
207
- optionalTags: [],
208
- },
209
- ]}
210
- />
211
- </IntlProvider>,
212
- );
213
- expect(screen.getByText('Intl Section Title')).toBeInTheDocument();
214
- });
181
+ it('calls handleJSONTextChange when the textarea value changes', () => {
182
+ render(
183
+ <TestWrapper>
184
+ <CustomValuesEditor {...defaultProps} showJSON />
185
+ </TestWrapper>
186
+ );
187
+ const textarea = document.querySelector('textarea.json-textarea');
188
+ fireEvent.change(textarea, { target: { value: '{"key": "val"}' } });
189
+ expect(defaultProps.handleJSONTextChange).toHaveBeenCalledWith('{"key": "val"}');
190
+ });
215
191
 
216
- it('renders section title as plain text when title is a string', () => {
217
- render(
218
- <IntlProvider locale="en" messages={{}}>
219
- <CustomValuesEditor
220
- {...baseProps}
221
- sections={[
222
- {
223
- key: 'str-sec',
224
- title: 'Plain String Title',
225
- requiredTags: [{ fullPath: 'tag.a', name: 'a' }],
226
- optionalTags: [],
227
- },
228
- ]}
229
- />
230
- </IntlProvider>,
231
- );
232
- expect(screen.getByText('Plain String Title')).toBeInTheDocument();
192
+ it('does not render the tag table in JSON view', () => {
193
+ render(
194
+ <TestWrapper>
195
+ <CustomValuesEditor {...defaultProps} showJSON />
196
+ </TestWrapper>
197
+ );
198
+ expect(screen.queryByText('Personalization Tags')).toBeNull();
199
+ });
233
200
  });
234
201
 
235
- it('renders nothing for section title when title is null (does not crash)', () => {
236
- render(
237
- <IntlProvider locale="en" messages={{}}>
238
- <CustomValuesEditor
239
- {...baseProps}
240
- sections={[
241
- {
242
- key: 'no-title-sec',
243
- title: null,
244
- requiredTags: [{ fullPath: 'tag.a', name: 'a' }],
245
- optionalTags: [],
246
- },
247
- ]}
248
- />
249
- </IntlProvider>,
250
- );
251
- const inputs = screen.getAllByTestId('tag-input');
252
- expect(inputs.length).toBeGreaterThan(0);
253
- });
202
+ describe('tag table view (showJSON = false)', () => {
203
+ it('renders Personalization Tags and Custom Values headers when extractedTags is non-empty', () => {
204
+ render(
205
+ <TestWrapper>
206
+ <CustomValuesEditor {...defaultProps} showJSON={false} />
207
+ </TestWrapper>
208
+ );
209
+ expect(screen.getByText('Personalization Tags')).toBeTruthy();
210
+ expect(screen.getByText('Custom Values')).toBeTruthy();
211
+ });
254
212
 
255
- it('uses tag.name as column label when fullPath is absent', () => {
256
- render(
257
- <IntlProvider locale="en" messages={{}}>
258
- <CustomValuesEditor
259
- {...baseProps}
260
- sections={[
261
- {
262
- key: 'name-only',
263
- title: 'Section',
264
- requiredTags: [{ name: 'myTag' }],
265
- optionalTags: [],
266
- },
267
- ]}
268
- />
269
- </IntlProvider>,
270
- );
271
- expect(screen.getByText('myTag')).toBeInTheDocument();
272
- });
213
+ it('renders required tags with a required indicator (*)', () => {
214
+ render(
215
+ <TestWrapper>
216
+ <CustomValuesEditor {...defaultProps} showJSON={false} />
217
+ </TestWrapper>
218
+ );
219
+ expect(screen.getByText('{{customer.name}}')).toBeTruthy();
220
+ expect(screen.getByText('*')).toBeTruthy();
221
+ });
273
222
 
274
- it('uses empty string as column label when both fullPath and name are absent', () => {
275
- render(
276
- <IntlProvider locale="en" messages={{}}>
277
- <CustomValuesEditor
278
- {...baseProps}
279
- sections={[
280
- {
281
- key: 'no-label',
282
- title: 'Section',
283
- requiredTags: [{}],
284
- optionalTags: [],
285
- },
286
- ]}
287
- />
288
- </IntlProvider>,
289
- );
290
- const inputs = screen.getAllByTestId('tag-input');
291
- expect(inputs.length).toBeGreaterThan(0);
292
- });
223
+ it('renders optional tags without a required indicator', () => {
224
+ render(
225
+ <TestWrapper>
226
+ <CustomValuesEditor {...defaultProps} showJSON={false} />
227
+ </TestWrapper>
228
+ );
229
+ expect(screen.getByText('{{customer.email}}')).toBeTruthy();
230
+ });
293
231
 
294
- it('renders optional-only section (no requiredTags) correctly', () => {
295
- render(
296
- <IntlProvider locale="en" messages={{}}>
297
- <CustomValuesEditor
298
- {...baseProps}
299
- sections={[
300
- {
301
- key: 'optional-only',
302
- title: 'Optional Section',
303
- requiredTags: [],
304
- optionalTags: [{ fullPath: 'tag.opt', name: 'opt' }],
305
- },
306
- ]}
307
- />
308
- </IntlProvider>,
309
- );
310
- expect(screen.getByText('Optional Section')).toBeInTheDocument();
311
- const inputs = screen.getAllByTestId('tag-input');
312
- expect(inputs.length).toBeGreaterThan(0);
313
- });
232
+ it('does not render the table when extractedTags is empty', () => {
233
+ render(
234
+ <TestWrapper>
235
+ <CustomValuesEditor
236
+ {...defaultProps}
237
+ showJSON={false}
238
+ extractedTags={[]}
239
+ requiredTags={[]}
240
+ optionalTags={[]}
241
+ />
242
+ </TestWrapper>
243
+ );
244
+ expect(screen.queryByText('Personalization Tags')).toBeNull();
245
+ });
314
246
 
315
- it('renders required-only section (no optionalTags) correctly', () => {
316
- render(
317
- <IntlProvider locale="en" messages={{}}>
318
- <CustomValuesEditor
319
- {...baseProps}
320
- sections={[
321
- {
322
- key: 'required-only',
323
- title: 'Required Section',
324
- requiredTags: [{ fullPath: 'tag.req', name: 'req' }],
325
- optionalTags: [],
326
- },
327
- ]}
328
- />
329
- </IntlProvider>,
330
- );
331
- expect(screen.getByText('Required Section')).toBeInTheDocument();
332
- const inputs = screen.getAllByTestId('tag-input');
333
- expect(inputs.length).toBeGreaterThan(0);
247
+ it('calls handleCustomValueChange with the tag path and new value for required tags', () => {
248
+ render(
249
+ <TestWrapper>
250
+ <CustomValuesEditor {...defaultProps} showJSON={false} />
251
+ </TestWrapper>
252
+ );
253
+ const inputs = screen.getAllByTestId('cap-input');
254
+ fireEvent.change(inputs[0], { target: { value: 'Bob' } });
255
+ expect(defaultProps.handleCustomValueChange).toHaveBeenCalledWith('{{customer.name}}', 'Bob');
256
+ });
257
+
258
+ it('calls handleCustomValueChange with the tag path and new value for optional tags', () => {
259
+ render(
260
+ <TestWrapper>
261
+ <CustomValuesEditor {...defaultProps} showJSON={false} />
262
+ </TestWrapper>
263
+ );
264
+ const inputs = screen.getAllByTestId('cap-input');
265
+ fireEvent.change(inputs[1], { target: { value: 'bob@example.com' } });
266
+ expect(defaultProps.handleCustomValueChange).toHaveBeenCalledWith(
267
+ '{{customer.email}}',
268
+ 'bob@example.com'
269
+ );
270
+ });
334
271
  });
335
272
 
336
- it('renders line numbers in JSON mode matching JSON line count', () => {
337
- const multiValueCustomValues = { a: 'val1', b: 'val2', c: 'val3' };
338
- render(
339
- <IntlProvider locale="en" messages={{}}>
340
- <CustomValuesEditor
341
- {...baseProps}
342
- showJSON
343
- customValues={multiValueCustomValues}
344
- />
345
- </IntlProvider>,
346
- );
347
- const lineNumbers = document.querySelectorAll('.line-number');
348
- const expectedLineCount = JSON.stringify(multiValueCustomValues, null, 2).split('\n').length;
349
- expect(lineNumbers.length).toBe(expectedLineCount);
273
+ describe('JSON / tag toggle', () => {
274
+ it('calls setShowJSON(true) when toggle is clicked from tag view', () => {
275
+ render(
276
+ <TestWrapper>
277
+ <CustomValuesEditor {...defaultProps} showJSON={false} />
278
+ </TestWrapper>
279
+ );
280
+ fireEvent.click(screen.getByTestId('cap-switch'));
281
+ expect(defaultProps.setShowJSON).toHaveBeenCalledWith(true);
282
+ });
283
+
284
+ it('calls setShowJSON(false) when toggle is clicked from JSON view', () => {
285
+ render(
286
+ <TestWrapper>
287
+ <CustomValuesEditor {...defaultProps} showJSON />
288
+ </TestWrapper>
289
+ );
290
+ fireEvent.click(screen.getByTestId('cap-switch'));
291
+ expect(defaultProps.setShowJSON).toHaveBeenCalledWith(false);
292
+ });
350
293
  });
351
294
 
352
- it('passes fullPath as key for required tag row when fullPath is present', () => {
353
- const handleChange = jest.fn();
354
- render(
355
- <IntlProvider locale="en" messages={{}}>
356
- <CustomValuesEditor
357
- {...baseProps}
358
- sections={[
359
- {
360
- key: 'sec',
361
- title: 'S',
362
- requiredTags: [{ fullPath: 'my.full.path', name: 'name' }],
363
- optionalTags: [],
364
- },
365
- ]}
366
- handleCustomValueChange={handleChange}
367
- customValues={{ 'my.full.path': 'existing' }}
368
- />
369
- </IntlProvider>,
370
- );
371
- const input = screen.getByTestId('tag-input');
372
- expect(input.value).toBe('existing');
373
- fireEvent.change(input, { target: { value: 'updated' } });
374
- expect(handleChange).toHaveBeenCalledWith('my.full.path', 'updated');
295
+ describe('action buttons', () => {
296
+ it('calls handleDiscardCustomValues when the discard button is clicked', () => {
297
+ render(
298
+ <TestWrapper>
299
+ <CustomValuesEditor {...defaultProps} />
300
+ </TestWrapper>
301
+ );
302
+ const buttons = screen.getAllByTestId('cap-button');
303
+ fireEvent.click(buttons[0]); // discard is first
304
+ expect(defaultProps.handleDiscardCustomValues).toHaveBeenCalled();
305
+ });
306
+
307
+ it('calls handleUpdatePreview when the update-preview button is clicked', () => {
308
+ render(
309
+ <TestWrapper>
310
+ <CustomValuesEditor {...defaultProps} />
311
+ </TestWrapper>
312
+ );
313
+ const buttons = screen.getAllByTestId('cap-button');
314
+ fireEvent.click(buttons[1]); // update is second
315
+ expect(defaultProps.handleUpdatePreview).toHaveBeenCalled();
316
+ });
317
+
318
+ it('disables the update-preview button when isUpdatePreviewDisabled is true', () => {
319
+ render(
320
+ <TestWrapper>
321
+ <CustomValuesEditor {...defaultProps} isUpdatePreviewDisabled />
322
+ </TestWrapper>
323
+ );
324
+ const buttons = screen.getAllByTestId('cap-button');
325
+ expect(buttons[1].disabled).toBe(true);
326
+ });
327
+
328
+ it('disables the update-preview button when isUpdatingPreview is true', () => {
329
+ render(
330
+ <TestWrapper>
331
+ <CustomValuesEditor {...defaultProps} isUpdatingPreview />
332
+ </TestWrapper>
333
+ );
334
+ const buttons = screen.getAllByTestId('cap-button');
335
+ expect(buttons[1].disabled).toBe(true);
336
+ });
337
+
338
+ it('enables the update-preview button when neither flag is set', () => {
339
+ render(
340
+ <TestWrapper>
341
+ <CustomValuesEditor
342
+ {...defaultProps}
343
+ isUpdatePreviewDisabled={false}
344
+ isUpdatingPreview={false}
345
+ />
346
+ </TestWrapper>
347
+ );
348
+ const buttons = screen.getAllByTestId('cap-button');
349
+ expect(buttons[1].disabled).toBe(false);
350
+ });
375
351
  });
376
352
  });