@capillarytech/creatives-library 8.0.345-alpha.12 → 8.0.345-alpha.14

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 (129) hide show
  1. package/constants/unified.js +0 -29
  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/CapActionButton/constants.js +0 -7
  6. package/v2Components/CapActionButton/index.js +109 -167
  7. package/v2Components/CapActionButton/index.scss +6 -157
  8. package/v2Components/CapActionButton/messages.js +3 -19
  9. package/v2Components/CapActionButton/tests/index.test.js +17 -41
  10. package/v2Components/CapTagList/index.js +0 -10
  11. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -70
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  13. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -207
  14. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  15. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  17. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
  18. package/v2Components/CommonTestAndPreview/SendTestMessage.js +5 -10
  19. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +15 -160
  20. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +76 -341
  21. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
  22. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -11
  23. package/v2Components/CommonTestAndPreview/constants.js +2 -38
  24. package/v2Components/CommonTestAndPreview/index.js +186 -676
  25. package/v2Components/CommonTestAndPreview/messages.js +3 -49
  26. package/v2Components/CommonTestAndPreview/sagas.js +6 -15
  27. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +284 -308
  28. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
  29. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
  30. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
  31. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +1 -8
  32. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +13 -34
  33. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +283 -281
  34. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
  35. package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -132
  36. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  37. package/v2Components/FormBuilder/index.js +10 -8
  38. package/v2Components/TemplatePreview/_templatePreview.scss +23 -33
  39. package/v2Components/TemplatePreview/index.js +28 -143
  40. package/v2Components/TemplatePreview/tests/index.test.js +0 -142
  41. package/v2Components/TestAndPreviewSlidebox/index.js +1 -13
  42. package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
  43. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
  44. package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
  45. package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
  46. package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
  47. package/v2Containers/CreativesContainer/constants.js +0 -9
  48. package/v2Containers/CreativesContainer/index.js +103 -300
  49. package/v2Containers/CreativesContainer/index.scss +1 -51
  50. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
  51. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  52. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  53. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
  54. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +15 -20
  55. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  56. package/v2Containers/Email/reducer.js +11 -3
  57. package/v2Containers/Email/sagas.js +9 -5
  58. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +4 -0
  59. package/v2Containers/Email/tests/sagas.test.js +21 -3
  60. package/v2Containers/Rcs/constants.js +8 -119
  61. package/v2Containers/Rcs/index.js +812 -2375
  62. package/v2Containers/Rcs/index.scss +6 -276
  63. package/v2Containers/Rcs/messages.js +3 -38
  64. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +70345 -98302
  65. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
  66. package/v2Containers/Rcs/tests/index.test.js +121 -152
  67. package/v2Containers/Rcs/tests/mockData.js +0 -38
  68. package/v2Containers/Rcs/tests/utils.test.js +30 -646
  69. package/v2Containers/Rcs/utils.js +11 -478
  70. package/v2Containers/Sms/Create/index.js +40 -100
  71. package/v2Containers/SmsTrai/Create/index.js +4 -9
  72. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  73. package/v2Containers/SmsTrai/Edit/index.js +130 -636
  74. package/v2Containers/SmsTrai/Edit/messages.js +4 -14
  75. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2296 -4249
  76. package/v2Containers/SmsWrapper/index.js +8 -37
  77. package/v2Containers/TagList/index.js +0 -6
  78. package/v2Containers/Templates/_templates.scss +2 -163
  79. package/v2Containers/Templates/actions.js +0 -11
  80. package/v2Containers/Templates/constants.js +0 -2
  81. package/v2Containers/Templates/index.js +54 -119
  82. package/v2Containers/Templates/sagas.js +12 -57
  83. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1079 -1043
  84. package/v2Containers/Templates/tests/sagas.test.js +123 -193
  85. package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
  86. package/v2Containers/TemplatesV2/index.js +23 -86
  87. package/v2Containers/Whatsapp/index.js +20 -3
  88. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -578
  89. package/utils/rcsPayloadUtils.js +0 -92
  90. package/utils/templateVarUtils.js +0 -201
  91. package/utils/tests/templateVarUtils.test.js +0 -204
  92. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +0 -18
  93. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  94. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  95. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -87
  96. package/v2Components/SmsFallback/constants.js +0 -73
  97. package/v2Components/SmsFallback/index.js +0 -955
  98. package/v2Components/SmsFallback/index.scss +0 -265
  99. package/v2Components/SmsFallback/messages.js +0 -78
  100. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -118
  101. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  102. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  103. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  104. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -197
  105. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -277
  106. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  107. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  108. package/v2Components/TemplatePreview/constants.js +0 -2
  109. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  110. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  111. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  112. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
  113. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -67
  114. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  115. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  116. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  117. package/v2Containers/Rcs/index.js.rej +0 -1336
  118. package/v2Containers/Rcs/index.scss.rej +0 -74
  119. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -225
  120. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +0 -128
  121. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
  122. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  123. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  124. package/v2Containers/SmsTrai/Edit/index.scss +0 -121
  125. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  126. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  127. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  128. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  129. 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
  });