@capillarytech/creatives-library 8.0.329 → 8.0.330

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 (122) hide show
  1. package/constants/unified.js +0 -14
  2. package/package.json +1 -1
  3. package/services/api.js +0 -17
  4. package/services/tests/api.test.js +0 -85
  5. package/utils/commonUtils.js +0 -10
  6. package/utils/tests/commonUtil.test.js +0 -169
  7. package/v2Components/CapTagList/index.js +0 -10
  8. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -70
  9. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  10. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -207
  11. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
  13. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  14. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
  15. package/v2Components/CommonTestAndPreview/SendTestMessage.js +53 -87
  16. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +1 -20
  17. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
  18. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +34 -145
  19. package/v2Components/CommonTestAndPreview/actions.js +0 -10
  20. package/v2Components/CommonTestAndPreview/constants.js +1 -53
  21. package/v2Components/CommonTestAndPreview/index.js +168 -1006
  22. package/v2Components/CommonTestAndPreview/messages.js +3 -147
  23. package/v2Components/CommonTestAndPreview/reducer.js +0 -10
  24. package/v2Components/CommonTestAndPreview/sagas.js +6 -15
  25. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +286 -328
  26. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
  27. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
  28. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
  29. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +24 -65
  30. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
  31. package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -31
  32. package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -168
  33. package/v2Components/CommonTestAndPreview/tests/reducer.test.js +0 -71
  34. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  35. package/v2Components/CommonTestAndPreview/tests/selectors.test.js +0 -17
  36. package/v2Components/FormBuilder/index.js +1 -7
  37. package/v2Components/TestAndPreviewSlidebox/index.js +1 -8
  38. package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
  39. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
  40. package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
  41. package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
  42. package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
  43. package/v2Containers/CreativesContainer/constants.js +0 -9
  44. package/v2Containers/CreativesContainer/index.js +93 -286
  45. package/v2Containers/CreativesContainer/index.scss +1 -51
  46. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
  47. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  48. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  49. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
  50. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +10 -20
  51. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  52. package/v2Containers/Rcs/constants.js +1 -34
  53. package/v2Containers/Rcs/index.js +884 -999
  54. package/v2Containers/Rcs/index.scss +6 -85
  55. package/v2Containers/Rcs/messages.js +1 -10
  56. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +2453 -41456
  57. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
  58. package/v2Containers/Rcs/tests/index.test.js +38 -41
  59. package/v2Containers/Rcs/tests/mockData.js +0 -38
  60. package/v2Containers/Rcs/tests/utils.test.js +1 -379
  61. package/v2Containers/Rcs/utils.js +10 -358
  62. package/v2Containers/Sms/Create/index.js +38 -100
  63. package/v2Containers/SmsTrai/Create/index.js +4 -9
  64. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  65. package/v2Containers/SmsTrai/Edit/index.js +128 -609
  66. package/v2Containers/SmsTrai/Edit/messages.js +4 -9
  67. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2600 -4586
  68. package/v2Containers/SmsWrapper/index.js +8 -37
  69. package/v2Containers/TagList/index.js +0 -6
  70. package/v2Containers/Templates/_templates.scss +2 -63
  71. package/v2Containers/Templates/actions.js +0 -11
  72. package/v2Containers/Templates/constants.js +0 -2
  73. package/v2Containers/Templates/index.js +40 -90
  74. package/v2Containers/Templates/sagas.js +12 -57
  75. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1079 -1043
  76. package/v2Containers/Templates/tests/sagas.test.js +123 -193
  77. package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
  78. package/v2Containers/TemplatesV2/index.js +23 -86
  79. package/v2Containers/Whatsapp/index.js +20 -3
  80. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +4872 -5790
  81. package/utils/templateVarUtils.js +0 -172
  82. package/utils/tests/templateVarUtils.test.js +0 -160
  83. package/v2Components/CommonTestAndPreview/AddTestCustomer.js +0 -42
  84. package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +0 -155
  85. package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +0 -93
  86. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  87. package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +0 -66
  88. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +0 -648
  89. package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +0 -174
  90. package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +0 -114
  91. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  92. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -87
  93. package/v2Components/SmsFallback/constants.js +0 -73
  94. package/v2Components/SmsFallback/index.js +0 -955
  95. package/v2Components/SmsFallback/index.scss +0 -265
  96. package/v2Components/SmsFallback/messages.js +0 -78
  97. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -107
  98. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  99. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  100. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  101. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -197
  102. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -261
  103. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  104. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  105. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  106. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  107. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  108. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
  109. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -67
  110. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  111. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  112. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  113. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -205
  114. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -251
  115. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  116. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  117. package/v2Containers/SmsTrai/Edit/index.scss +0 -121
  118. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  119. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  120. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  121. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  122. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
@@ -1,376 +1,334 @@
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';
55
+
56
+ const TestWrapper = ({ children }) => (
57
+ <IntlProvider locale="en">{children}</IntlProvider>
58
+ );
59
+ TestWrapper.propTypes = { children: PropTypes.node.isRequired };
65
60
 
66
- const baseProps = {
61
+ const defaultProps = {
67
62
  isExtractingTags: false,
68
63
  isUpdatePreviewDisabled: false,
69
64
  showJSON: false,
70
65
  setShowJSON: jest.fn(),
71
- customValues: { 'tag.a': 'v1' },
66
+ customValues: { '{{customer.name}}': 'Alice', '{{customer.email}}': '' },
72
67
  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
- },
68
+ extractedTags: [
69
+ { fullPath: '{{customer.name}}' },
70
+ { fullPath: '{{customer.email}}' },
80
71
  ],
72
+ requiredTags: [{ fullPath: '{{customer.name}}' }],
73
+ optionalTags: [{ fullPath: '{{customer.email}}' }],
81
74
  handleCustomValueChange: jest.fn(),
82
75
  handleDiscardCustomValues: jest.fn(),
83
76
  handleUpdatePreview: jest.fn(),
84
77
  isUpdatingPreview: false,
85
- formatMessage,
78
+ formatMessage: jest.fn((msg) => msg.defaultMessage || msg.id),
86
79
  };
87
80
 
88
- // IntlProvider wrapper for incoming tests
89
- const scope = 'app.v2Components.TestAndPreviewSlidebox';
90
- const mockMessages = {
91
- [`${scope}.extractingTags`]: 'Extracting tags...',
92
- [`${scope}.valuesMissing`]: 'Some values are missing',
93
- [`${scope}.showJSON`]: 'Show JSON',
94
- [`${scope}.personalizationTags`]: 'Personalization Tags',
95
- [`${scope}.customValues`]: 'Custom Values',
96
- [`${scope}.enterValue`]: 'Enter value',
97
- [`${scope}.discardCustomValues`]: 'Discard custom values',
98
- [`${scope}.updatePreview`]: 'Update Preview',
99
- };
81
+ describe('CustomValuesEditor', () => {
82
+ beforeEach(() => jest.clearAllMocks());
100
83
 
101
- const TestWrapper = ({ children }) => (
102
- <IntlProvider locale="en" messages={mockMessages}>
103
- {children}
104
- </IntlProvider>
105
- );
84
+ describe('loading state (isExtractingTags)', () => {
85
+ it('renders a spinner and extracting-tags message when isExtractingTags is true', () => {
86
+ render(
87
+ <TestWrapper>
88
+ <CustomValuesEditor {...defaultProps} isExtractingTags />
89
+ </TestWrapper>
90
+ );
91
+ expect(screen.getByTestId('cap-spin')).toBeTruthy();
92
+ expect(screen.getByText('Extracting tags...')).toBeTruthy();
93
+ });
106
94
 
107
- TestWrapper.propTypes = {
108
- children: PropTypes.node.isRequired,
109
- };
95
+ it('does not render the editor toggle when extracting tags', () => {
96
+ render(
97
+ <TestWrapper>
98
+ <CustomValuesEditor {...defaultProps} isExtractingTags />
99
+ </TestWrapper>
100
+ );
101
+ expect(screen.queryByTestId('cap-switch')).toBeNull();
102
+ expect(screen.queryAllByTestId('cap-button')).toHaveLength(0);
103
+ });
110
104
 
111
- describe('CustomValuesEditor', () => {
112
- beforeEach(() => {
113
- jest.clearAllMocks();
105
+ it('renders the editor (not spinner) when isExtractingTags is false', () => {
106
+ render(
107
+ <TestWrapper>
108
+ <CustomValuesEditor {...defaultProps} isExtractingTags={false} />
109
+ </TestWrapper>
110
+ );
111
+ expect(screen.queryByTestId('cap-spin')).toBeNull();
112
+ expect(screen.getByTestId('cap-switch')).toBeTruthy();
113
+ });
114
114
  });
115
115
 
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
- });
116
+ describe('values missing warning', () => {
117
+ it('shows values-missing label when isUpdatePreviewDisabled is true', () => {
118
+ render(
119
+ <TestWrapper>
120
+ <CustomValuesEditor {...defaultProps} isUpdatePreviewDisabled />
121
+ </TestWrapper>
122
+ );
123
+ expect(screen.getByText('Values missing for some of the personalization tags')).toBeTruthy();
124
+ });
124
125
 
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();
126
+ it('does not show values-missing label when isUpdatePreviewDisabled is false', () => {
127
+ render(
128
+ <TestWrapper>
129
+ <CustomValuesEditor {...defaultProps} isUpdatePreviewDisabled={false} />
130
+ </TestWrapper>
131
+ );
132
+ expect(
133
+ screen.queryByText('Values missing for some of the personalization tags')
134
+ ).toBeNull();
135
+ });
132
136
  });
133
137
 
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();
143
- });
138
+ describe('JSON view (showJSON = true)', () => {
139
+ it('renders a textarea with the formatted JSON', () => {
140
+ const customValues = { name: 'Alice', age: 30 };
141
+ render(
142
+ <TestWrapper>
143
+ <CustomValuesEditor {...defaultProps} showJSON customValues={customValues} />
144
+ </TestWrapper>
145
+ );
146
+ const textarea = document.querySelector('textarea.json-textarea');
147
+ expect(textarea).toBeTruthy();
148
+ expect(textarea.value).toBe(JSON.stringify(customValues, null, 2));
149
+ });
144
150
 
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
- });
151
+ it('renders line numbers matching the number of JSON lines', () => {
152
+ const customValues = { a: 1, b: 2 };
153
+ render(
154
+ <TestWrapper>
155
+ <CustomValuesEditor {...defaultProps} showJSON customValues={customValues} />
156
+ </TestWrapper>
157
+ );
158
+ const lineCount = JSON.stringify(customValues, null, 2).split('\n').length;
159
+ const lineNumbers = document.querySelectorAll('.line-number');
160
+ expect(lineNumbers).toHaveLength(lineCount);
161
+ });
156
162
 
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');
167
- });
163
+ it('calls handleJSONTextChange when the textarea value changes', () => {
164
+ render(
165
+ <TestWrapper>
166
+ <CustomValuesEditor {...defaultProps} showJSON />
167
+ </TestWrapper>
168
+ );
169
+ const textarea = document.querySelector('textarea.json-textarea');
170
+ fireEvent.change(textarea, { target: { value: '{"key": "val"}' } });
171
+ expect(defaultProps.handleJSONTextChange).toHaveBeenCalledWith('{"key": "val"}');
172
+ });
168
173
 
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();
174
+ it('does not render the tag table in JSON view', () => {
175
+ render(
176
+ <TestWrapper>
177
+ <CustomValuesEditor {...defaultProps} showJSON />
178
+ </TestWrapper>
179
+ );
180
+ expect(screen.queryByText('Personalization Tags')).toBeNull();
181
+ });
180
182
  });
181
183
 
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
- });
184
+ describe('tag table view (showJSON = false)', () => {
185
+ it('renders Personalization Tags and Custom Values headers when extractedTags is non-empty', () => {
186
+ render(
187
+ <TestWrapper>
188
+ <CustomValuesEditor {...defaultProps} showJSON={false} />
189
+ </TestWrapper>
190
+ );
191
+ expect(screen.getByText('Personalization Tags')).toBeTruthy();
192
+ expect(screen.getByText('Custom Values')).toBeTruthy();
193
+ });
196
194
 
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
- });
195
+ it('renders required tags with a required indicator (*)', () => {
196
+ render(
197
+ <TestWrapper>
198
+ <CustomValuesEditor {...defaultProps} showJSON={false} />
199
+ </TestWrapper>
200
+ );
201
+ expect(screen.getByText('{{customer.name}}')).toBeTruthy();
202
+ expect(screen.getByText('*')).toBeTruthy();
203
+ });
215
204
 
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();
233
- });
205
+ it('renders optional tags without a required indicator', () => {
206
+ render(
207
+ <TestWrapper>
208
+ <CustomValuesEditor {...defaultProps} showJSON={false} />
209
+ </TestWrapper>
210
+ );
211
+ expect(screen.getByText('{{customer.email}}')).toBeTruthy();
212
+ });
234
213
 
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
- });
214
+ it('does not render the table when extractedTags is empty', () => {
215
+ render(
216
+ <TestWrapper>
217
+ <CustomValuesEditor
218
+ {...defaultProps}
219
+ showJSON={false}
220
+ extractedTags={[]}
221
+ requiredTags={[]}
222
+ optionalTags={[]}
223
+ />
224
+ </TestWrapper>
225
+ );
226
+ expect(screen.queryByText('Personalization Tags')).toBeNull();
227
+ });
254
228
 
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
- });
229
+ it('calls handleCustomValueChange with the tag path and new value for required tags', () => {
230
+ render(
231
+ <TestWrapper>
232
+ <CustomValuesEditor {...defaultProps} showJSON={false} />
233
+ </TestWrapper>
234
+ );
235
+ const inputs = screen.getAllByTestId('cap-input');
236
+ fireEvent.change(inputs[0], { target: { value: 'Bob' } });
237
+ expect(defaultProps.handleCustomValueChange).toHaveBeenCalledWith('{{customer.name}}', 'Bob');
238
+ });
273
239
 
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);
240
+ it('calls handleCustomValueChange with the tag path and new value for optional tags', () => {
241
+ render(
242
+ <TestWrapper>
243
+ <CustomValuesEditor {...defaultProps} showJSON={false} />
244
+ </TestWrapper>
245
+ );
246
+ const inputs = screen.getAllByTestId('cap-input');
247
+ fireEvent.change(inputs[1], { target: { value: 'bob@example.com' } });
248
+ expect(defaultProps.handleCustomValueChange).toHaveBeenCalledWith(
249
+ '{{customer.email}}',
250
+ 'bob@example.com'
251
+ );
252
+ });
292
253
  });
293
254
 
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
- });
255
+ describe('JSON / tag toggle', () => {
256
+ it('calls setShowJSON(true) when toggle is clicked from tag view', () => {
257
+ render(
258
+ <TestWrapper>
259
+ <CustomValuesEditor {...defaultProps} showJSON={false} />
260
+ </TestWrapper>
261
+ );
262
+ fireEvent.click(screen.getByTestId('cap-switch'));
263
+ expect(defaultProps.setShowJSON).toHaveBeenCalledWith(true);
264
+ });
314
265
 
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);
266
+ it('calls setShowJSON(false) when toggle is clicked from JSON view', () => {
267
+ render(
268
+ <TestWrapper>
269
+ <CustomValuesEditor {...defaultProps} showJSON />
270
+ </TestWrapper>
271
+ );
272
+ fireEvent.click(screen.getByTestId('cap-switch'));
273
+ expect(defaultProps.setShowJSON).toHaveBeenCalledWith(false);
274
+ });
334
275
  });
335
276
 
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);
350
- });
277
+ describe('action buttons', () => {
278
+ it('calls handleDiscardCustomValues when the discard button is clicked', () => {
279
+ render(
280
+ <TestWrapper>
281
+ <CustomValuesEditor {...defaultProps} />
282
+ </TestWrapper>
283
+ );
284
+ const buttons = screen.getAllByTestId('cap-button');
285
+ fireEvent.click(buttons[0]); // discard is first
286
+ expect(defaultProps.handleDiscardCustomValues).toHaveBeenCalled();
287
+ });
351
288
 
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');
289
+ it('calls handleUpdatePreview when the update-preview button is clicked', () => {
290
+ render(
291
+ <TestWrapper>
292
+ <CustomValuesEditor {...defaultProps} />
293
+ </TestWrapper>
294
+ );
295
+ const buttons = screen.getAllByTestId('cap-button');
296
+ fireEvent.click(buttons[1]); // update is second
297
+ expect(defaultProps.handleUpdatePreview).toHaveBeenCalled();
298
+ });
299
+
300
+ it('disables the update-preview button when isUpdatePreviewDisabled is true', () => {
301
+ render(
302
+ <TestWrapper>
303
+ <CustomValuesEditor {...defaultProps} isUpdatePreviewDisabled />
304
+ </TestWrapper>
305
+ );
306
+ const buttons = screen.getAllByTestId('cap-button');
307
+ expect(buttons[1].disabled).toBe(true);
308
+ });
309
+
310
+ it('disables the update-preview button when isUpdatingPreview is true', () => {
311
+ render(
312
+ <TestWrapper>
313
+ <CustomValuesEditor {...defaultProps} isUpdatingPreview />
314
+ </TestWrapper>
315
+ );
316
+ const buttons = screen.getAllByTestId('cap-button');
317
+ expect(buttons[1].disabled).toBe(true);
318
+ });
319
+
320
+ it('enables the update-preview button when neither flag is set', () => {
321
+ render(
322
+ <TestWrapper>
323
+ <CustomValuesEditor
324
+ {...defaultProps}
325
+ isUpdatePreviewDisabled={false}
326
+ isUpdatingPreview={false}
327
+ />
328
+ </TestWrapper>
329
+ );
330
+ const buttons = screen.getAllByTestId('cap-button');
331
+ expect(buttons[1].disabled).toBe(false);
332
+ });
375
333
  });
376
334
  });