@capillarytech/creatives-library 9.0.15-alpha.1 → 9.0.16

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