@capillarytech/creatives-library 9.0.61 → 9.0.62
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.
- package/constants/unified.js +8 -2
- package/package.json +1 -1
- package/services/api.js +5 -0
- package/services/tests/api.test.js +44 -0
- package/utils/common.js +8 -0
- package/utils/downloadFile.js +57 -0
- package/utils/tests/downloadFile.test.js +219 -0
- package/v2Components/FormBuilder/Classic.js +43 -9
- package/v2Components/FormBuilder/Functional/FormBuilderShell.js +324 -69
- package/v2Components/FormBuilder/Functional/channels/mobilepush/buildSubmitPayload.js +10 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/config.js +75 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/getEditorErrorDescriptor.js +74 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/index.js +28 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/modals.js +21 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/runSubmitPipeline.js +45 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/getEditorErrorDescriptor.test.js +162 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/modals.test.js +37 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/runSubmitPipeline.test.js +70 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/validate.test.js +274 -0
- package/v2Components/FormBuilder/Functional/channels/mobilepush/validate.js +251 -0
- package/v2Components/FormBuilder/Functional/channels/registry.js +2 -0
- package/v2Components/FormBuilder/Functional/constants.js +46 -8
- package/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js +175 -28
- package/v2Components/FormBuilder/Functional/core/store/formReducer.js +75 -6
- package/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js +10 -20
- package/v2Components/FormBuilder/Functional/layout/FieldSlot.js +9 -1
- package/v2Components/FormBuilder/Functional/layout/SchemaForm.js +20 -3
- package/v2Components/FormBuilder/Functional/layout/Section.js +6 -1
- package/v2Components/FormBuilder/Functional/layout/TabsContainer.js +85 -0
- package/v2Components/FormBuilder/Functional/renderers/mpushRenderers.js +451 -0
- package/v2Components/FormBuilder/Functional/tests/fieldSlot.test.js +9 -2
- package/v2Components/FormBuilder/Functional/tests/manual.typingPerf.test.js +135 -0
- package/v2Components/FormBuilder/Functional/tests/mpush.crossFlowParity.test.js +430 -0
- package/v2Components/FormBuilder/Functional/tests/mpush.ctaFlows.parity.test.js +320 -0
- package/v2Components/FormBuilder/Functional/tests/mpush.editContainer.parity.test.js +148 -0
- package/v2Components/FormBuilder/Functional/tests/mpush.engine.test.js +306 -0
- package/v2Components/FormBuilder/Functional/tests/mpush.libraryValidity.parity.test.js +152 -0
- package/v2Components/FormBuilder/Functional/tests/mpush.shellEvents.test.js +200 -0
- package/v2Components/FormBuilder/Functional/tests/mpushRenderers.test.js +382 -0
- package/v2Components/FormBuilder/Functional/tests/schemaForm.test.js +17 -0
- package/v2Components/FormBuilder/Functional/tests/tabsContainer.test.js +112 -0
- package/v2Components/FormBuilder/_formBuilder.scss +11 -0
- package/v2Components/FormBuilder/index.js +27 -17
- package/v2Components/FormBuilder/tests/entryGate.test.js +90 -7
- package/v2Components/FormBuilder/tests/mpush.characterization.test.js +459 -0
- package/v2Containers/BeeEditor/index.js +8 -0
- package/v2Containers/Email/index.js +118 -16
- package/v2Containers/Email/initialSchema.js +25 -3
- package/v2Containers/Email/messages.js +16 -0
- package/v2Containers/Email/tests/index.test.js +597 -0
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +55 -1
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +141 -0
- package/v2Containers/EmailWrapper/components/_emailHTMLEditor.scss +11 -0
- package/v2Containers/Templates/index.js +53 -0
- package/v2Containers/Templates/messages.js +8 -0
- package/v2Containers/Templates/tests/index.test.js +170 -0
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import { shallowWithIntl } from '../../../helpers/intl-enzym-test-helpers';
|
|
4
|
+
import { Email } from '../index';
|
|
5
|
+
import { sanitizeFileName, exportEmailTemplateHtml } from '../../../utils/downloadFile';
|
|
6
|
+
import { response as initialSchemaResponse } from '../initialSchema';
|
|
7
|
+
import FormBuilder from '../../../v2Components/FormBuilder';
|
|
8
|
+
import { EXPORT_HTML_LABEL_ID, DISPLAY_NONE } from '../../../constants/unified';
|
|
9
|
+
|
|
10
|
+
jest.mock('../../../utils/downloadFile', () => ({
|
|
11
|
+
sanitizeFileName: jest.fn((name) => (name || 'template').replace(/[^a-zA-Z0-9-_ ]/g, '').trim() || 'template'),
|
|
12
|
+
exportEmailTemplateHtml: jest.fn(),
|
|
13
|
+
getExportHtmlDisabledTooltip: jest.fn((isEditMode, isContentLocked) => {
|
|
14
|
+
if (!isEditMode) return 'Export while creating not supported';
|
|
15
|
+
if (isContentLocked) return 'Email edited and Export HTML not supported in active session now';
|
|
16
|
+
return '';
|
|
17
|
+
}),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
jest.mock('@capillarytech/cap-ui-library', () => ({
|
|
21
|
+
...jest.requireActual('@capillarytech/cap-ui-library'),
|
|
22
|
+
CapNotification: { error: jest.fn(), success: jest.fn(), warning: jest.fn() },
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
// hasSupportCKEditor is a real feature-flag check (Auth.hasFeatureAccess) that returns
|
|
26
|
+
// false when auth isn't initialized (as in tests) - mock it so drag-drop/CKEditor-switch
|
|
27
|
+
// scenarios can be driven explicitly instead of always taking the "unsupported" branch.
|
|
28
|
+
jest.mock('../../../utils/common', () => ({
|
|
29
|
+
...jest.requireActual('../../../utils/common'),
|
|
30
|
+
hasSupportCKEditor: jest.fn(),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
// eslint-disable-next-line global-require
|
|
34
|
+
const { hasSupportCKEditor } = require('../../../utils/common');
|
|
35
|
+
|
|
36
|
+
// eslint-disable-next-line global-require
|
|
37
|
+
const CapNotification = require('@capillarytech/cap-ui-library').CapNotification;
|
|
38
|
+
|
|
39
|
+
describe('Email container', () => {
|
|
40
|
+
const emailDefinition = initialSchemaResponse.metaEntities[0].definition;
|
|
41
|
+
|
|
42
|
+
const buildProps = (overrides = {}) => ({
|
|
43
|
+
params: {},
|
|
44
|
+
location: { query: {}, pathname: '/email' },
|
|
45
|
+
templateData: null,
|
|
46
|
+
isFullMode: false,
|
|
47
|
+
editor: undefined,
|
|
48
|
+
getDefaultTags: undefined,
|
|
49
|
+
currentOrgDetails: { basic_details: { base_language: 'en', languages: {} } },
|
|
50
|
+
Templates: {},
|
|
51
|
+
Email: {},
|
|
52
|
+
metaEntities: {},
|
|
53
|
+
injectedTags: {},
|
|
54
|
+
tags: [],
|
|
55
|
+
selectedOfferDetails: [],
|
|
56
|
+
actions: {
|
|
57
|
+
getTemplateDetails: jest.fn(),
|
|
58
|
+
getCmsSetting: jest.fn(),
|
|
59
|
+
clearStoreValues: jest.fn(),
|
|
60
|
+
clearCRUDResponse: jest.fn(),
|
|
61
|
+
getCmsData: jest.fn(),
|
|
62
|
+
createTemplate: jest.fn(),
|
|
63
|
+
resetCmsData: jest.fn(),
|
|
64
|
+
},
|
|
65
|
+
templatesActions: {
|
|
66
|
+
resetTemplateData: jest.fn(),
|
|
67
|
+
resetUploadData: jest.fn(),
|
|
68
|
+
},
|
|
69
|
+
globalActions: {
|
|
70
|
+
fetchSchemaForEntity: jest.fn(),
|
|
71
|
+
addMessageToQueue: jest.fn(),
|
|
72
|
+
},
|
|
73
|
+
intl: {
|
|
74
|
+
formatMessage: (msg) => (msg && (msg.defaultMessage || msg.id)) || '',
|
|
75
|
+
formatDate: jest.fn(),
|
|
76
|
+
formatTime: jest.fn(),
|
|
77
|
+
formatRelative: jest.fn(),
|
|
78
|
+
formatNumber: jest.fn(),
|
|
79
|
+
formatPlural: jest.fn(),
|
|
80
|
+
formatHTMLMessage: jest.fn(),
|
|
81
|
+
now: jest.fn(),
|
|
82
|
+
},
|
|
83
|
+
router: {},
|
|
84
|
+
setIsLoadingContent: jest.fn(),
|
|
85
|
+
showTemplateName: jest.fn(),
|
|
86
|
+
...overrides,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const renderEmail = (overrides = {}) => shallowWithIntl(<Email {...buildProps(overrides)} />);
|
|
90
|
+
|
|
91
|
+
beforeEach(() => {
|
|
92
|
+
jest.clearAllMocks();
|
|
93
|
+
// jest config sets resetMocks: true, wiping the default implementation baked into
|
|
94
|
+
// the jest.mock(...) factory before every test - re-establish it here.
|
|
95
|
+
sanitizeFileName.mockImplementation(
|
|
96
|
+
(name) => (name || 'template').replace(/[^a-zA-Z0-9-_ ]/g, '').trim() || 'template'
|
|
97
|
+
);
|
|
98
|
+
hasSupportCKEditor.mockReturnValue(false);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('exportHtml', () => {
|
|
102
|
+
it('shows an error and does not call the shared helper when templateDetails is empty', () => {
|
|
103
|
+
const wrapper = renderEmail({ Email: { templateDetails: {} } });
|
|
104
|
+
|
|
105
|
+
wrapper.instance().exportHtml();
|
|
106
|
+
|
|
107
|
+
expect(CapNotification.error).toHaveBeenCalled();
|
|
108
|
+
expect(exportEmailTemplateHtml).not.toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('shows an error and does not call the shared helper when Export HTML is content-locked', () => {
|
|
112
|
+
const wrapper = renderEmail({ Email: { templateDetails: { name: 'My Template' } } });
|
|
113
|
+
wrapper.setState({ exportHtmlContentLocked: true });
|
|
114
|
+
|
|
115
|
+
wrapper.instance().exportHtml();
|
|
116
|
+
|
|
117
|
+
expect(CapNotification.error).toHaveBeenCalled();
|
|
118
|
+
expect(exportEmailTemplateHtml).not.toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('delegates to the shared exportEmailTemplateHtml helper with a sanitized filename and the base language', () => {
|
|
122
|
+
const templateDetails = { name: 'My Template', versions: { base: { en: { 'template-content': '<p>hi</p>' } } } };
|
|
123
|
+
const wrapper = renderEmail({ Email: { templateDetails } });
|
|
124
|
+
exportEmailTemplateHtml.mockResolvedValue();
|
|
125
|
+
|
|
126
|
+
wrapper.instance().exportHtml();
|
|
127
|
+
|
|
128
|
+
expect(exportEmailTemplateHtml).toHaveBeenCalledWith(templateDetails, 'My Template.html', 'en');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('shows an error notification when the shared helper rejects', async () => {
|
|
132
|
+
const templateDetails = { name: 'My Template', versions: { base: {} } };
|
|
133
|
+
const wrapper = renderEmail({ Email: { templateDetails } });
|
|
134
|
+
exportEmailTemplateHtml.mockRejectedValue(new Error('export failed'));
|
|
135
|
+
|
|
136
|
+
wrapper.instance().exportHtml();
|
|
137
|
+
await Promise.resolve().then(() => {}).then(() => {});
|
|
138
|
+
|
|
139
|
+
expect(CapNotification.error).toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('handleBeeContentChange', () => {
|
|
144
|
+
it('locks Export HTML on the base-language tab once content changes', () => {
|
|
145
|
+
const wrapper = renderEmail();
|
|
146
|
+
wrapper.setState({
|
|
147
|
+
currentTab: 1,
|
|
148
|
+
formData: { 0: { activeTab: 'en' } },
|
|
149
|
+
exportHtmlContentLocked: false,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
wrapper.instance().handleBeeContentChange();
|
|
153
|
+
|
|
154
|
+
expect(wrapper.state('exportHtmlContentLocked')).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('does nothing when the active tab is not the base language', () => {
|
|
158
|
+
const wrapper = renderEmail();
|
|
159
|
+
wrapper.setState({
|
|
160
|
+
currentTab: 1,
|
|
161
|
+
formData: { 0: { activeTab: 'fr' } },
|
|
162
|
+
exportHtmlContentLocked: false,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
wrapper.instance().handleBeeContentChange();
|
|
166
|
+
|
|
167
|
+
expect(wrapper.state('exportHtmlContentLocked')).toBe(false);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('is a no-op once already locked', () => {
|
|
171
|
+
const wrapper = renderEmail();
|
|
172
|
+
wrapper.setState({
|
|
173
|
+
currentTab: 1,
|
|
174
|
+
formData: { 0: { activeTab: 'en' } },
|
|
175
|
+
exportHtmlContentLocked: true,
|
|
176
|
+
});
|
|
177
|
+
const setStateSpy = jest.spyOn(wrapper.instance(), 'setState');
|
|
178
|
+
|
|
179
|
+
wrapper.instance().handleBeeContentChange();
|
|
180
|
+
|
|
181
|
+
expect(setStateSpy).not.toHaveBeenCalled();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('onChange (CKEditor content change)', () => {
|
|
186
|
+
const makeEvt = (html) => ({ editor: { getData: () => html } });
|
|
187
|
+
|
|
188
|
+
it('locks Export HTML when the base-language CKEditor content diverges from the captured baseline', () => {
|
|
189
|
+
const wrapper = renderEmail();
|
|
190
|
+
wrapper.instance().originalBaseTemplateContent = '<p>Original</p>';
|
|
191
|
+
wrapper.setState({
|
|
192
|
+
currentTab: 1,
|
|
193
|
+
exportHtmlContentLocked: false,
|
|
194
|
+
formData: {
|
|
195
|
+
0: { activeTab: 'en', en: { is_drag_drop: false, 'template-content': '<p>Original</p>' } },
|
|
196
|
+
base: { tabKey: 'k1', en: {} },
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
wrapper.setState((prev) => ({ formData: { ...prev.formData, 0: { ...prev.formData[0], tabKey: 'k1' } } }));
|
|
200
|
+
|
|
201
|
+
wrapper.instance().onChange(makeEvt('<p>Changed</p>'));
|
|
202
|
+
|
|
203
|
+
expect(wrapper.state('exportHtmlContentLocked')).toBe(true);
|
|
204
|
+
expect(wrapper.state('formData')[0].en['template-content']).toBe('<p>Changed</p>');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('does not lock Export HTML when the new content equals the captured baseline (no genuine edit)', () => {
|
|
208
|
+
const wrapper = renderEmail();
|
|
209
|
+
wrapper.instance().originalBaseTemplateContent = '<p>Same</p>';
|
|
210
|
+
wrapper.setState({
|
|
211
|
+
currentTab: 1,
|
|
212
|
+
exportHtmlContentLocked: false,
|
|
213
|
+
formData: {
|
|
214
|
+
0: { activeTab: 'en', en: { is_drag_drop: false, 'template-content': '<p>Same</p>' } },
|
|
215
|
+
base: { en: {} },
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
wrapper.instance().onChange(makeEvt('<p>Same</p>'));
|
|
220
|
+
|
|
221
|
+
expect(wrapper.state('exportHtmlContentLocked')).toBe(false);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('ignores Bee (drag-drop) tabs - onChange only reacts to CKEditor content', () => {
|
|
225
|
+
const wrapper = renderEmail();
|
|
226
|
+
wrapper.setState({
|
|
227
|
+
currentTab: 1,
|
|
228
|
+
exportHtmlContentLocked: false,
|
|
229
|
+
formData: {
|
|
230
|
+
0: { activeTab: 'en', en: { is_drag_drop: true, 'template-content': '<p>Original</p>' } },
|
|
231
|
+
base: { en: {} },
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
wrapper.instance().onChange(makeEvt('<p>Changed</p>'));
|
|
236
|
+
|
|
237
|
+
expect(wrapper.state('exportHtmlContentLocked')).toBe(false);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('lockExportHtml', () => {
|
|
242
|
+
it('sets exportHtmlContentLocked and disables the export-html-label menu item in the schema', () => {
|
|
243
|
+
const wrapper = renderEmail();
|
|
244
|
+
const schema = _.cloneDeep(emailDefinition);
|
|
245
|
+
schema.containers[0].isActive = true;
|
|
246
|
+
wrapper.setState({ schema });
|
|
247
|
+
|
|
248
|
+
wrapper.instance().lockExportHtml();
|
|
249
|
+
|
|
250
|
+
expect(wrapper.state('exportHtmlContentLocked')).toBe(true);
|
|
251
|
+
const col = wrapper.state('schema').containers[0].tabBarExtraContent.sections[0].inputFields[0].cols
|
|
252
|
+
.find((c) => c.id === 'tab-options-popover');
|
|
253
|
+
const exportLabel = col.content.sections[0].inputFields
|
|
254
|
+
.map((f) => f.cols[0])
|
|
255
|
+
.find((c) => c.id === EXPORT_HTML_LABEL_ID);
|
|
256
|
+
expect(exportLabel.style).toMatchObject({ opacity: 0.5, cursor: 'not-allowed', pointerEvents: 'none' });
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('does not touch inactive containers', () => {
|
|
260
|
+
const wrapper = renderEmail();
|
|
261
|
+
const schema = _.cloneDeep(emailDefinition);
|
|
262
|
+
schema.containers[0].isActive = false;
|
|
263
|
+
wrapper.setState({ schema });
|
|
264
|
+
|
|
265
|
+
wrapper.instance().lockExportHtml();
|
|
266
|
+
|
|
267
|
+
const col = wrapper.state('schema').containers[0].tabBarExtraContent.sections[0].inputFields[0].cols
|
|
268
|
+
.find((c) => c.id === 'tab-options-popover');
|
|
269
|
+
const exportLabel = col.content.sections[0].inputFields
|
|
270
|
+
.map((f) => f.cols[0])
|
|
271
|
+
.find((c) => c.id === EXPORT_HTML_LABEL_ID);
|
|
272
|
+
expect(exportLabel.style.display).toBe('');
|
|
273
|
+
expect(exportLabel.style.opacity).toBeUndefined();
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
describe('injectEvents (tab-options-popover export-html-label visual state)', () => {
|
|
278
|
+
const getExportLabel = (schema) => {
|
|
279
|
+
const container = schema.containers.find((c) => c.isActive) || schema.containers[0];
|
|
280
|
+
const col = container.tabBarExtraContent.sections[0].inputFields[0].cols
|
|
281
|
+
.find((c) => c.id === 'tab-options-popover');
|
|
282
|
+
return col.content.sections[0].inputFields
|
|
283
|
+
.map((f) => f.cols[0])
|
|
284
|
+
.find((c) => c.id === EXPORT_HTML_LABEL_ID);
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
it('renders export-html-label as enabled when editing and not content-locked', () => {
|
|
288
|
+
const wrapper = renderEmail();
|
|
289
|
+
wrapper.setState({ isEdit: true, exportHtmlContentLocked: false });
|
|
290
|
+
const schema = _.cloneDeep(emailDefinition);
|
|
291
|
+
schema.containers[0].isActive = true;
|
|
292
|
+
|
|
293
|
+
const result = wrapper.instance().injectEvents(schema);
|
|
294
|
+
|
|
295
|
+
const exportLabel = getExportLabel(result);
|
|
296
|
+
expect(exportLabel.style).toMatchObject({ opacity: 1, cursor: 'pointer', pointerEvents: 'auto' });
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('renders export-html-label as disabled when content-locked', () => {
|
|
300
|
+
const wrapper = renderEmail();
|
|
301
|
+
wrapper.setState({ isEdit: true, exportHtmlContentLocked: true });
|
|
302
|
+
const schema = _.cloneDeep(emailDefinition);
|
|
303
|
+
schema.containers[0].isActive = true;
|
|
304
|
+
|
|
305
|
+
const result = wrapper.instance().injectEvents(schema);
|
|
306
|
+
|
|
307
|
+
const exportLabel = getExportLabel(result);
|
|
308
|
+
expect(exportLabel.style).toMatchObject({ opacity: 0.5, cursor: 'not-allowed', pointerEvents: 'none' });
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('renders export-html-label as disabled when not in edit mode (create flow)', () => {
|
|
312
|
+
const wrapper = renderEmail();
|
|
313
|
+
wrapper.setState({ isEdit: false, exportHtmlContentLocked: false });
|
|
314
|
+
const schema = _.cloneDeep(emailDefinition);
|
|
315
|
+
schema.containers[0].isActive = true;
|
|
316
|
+
|
|
317
|
+
const result = wrapper.instance().injectEvents(schema);
|
|
318
|
+
|
|
319
|
+
const exportLabel = getExportLabel(result);
|
|
320
|
+
expect(exportLabel.style).toMatchObject({ opacity: 0.5, cursor: 'not-allowed', pointerEvents: 'none' });
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe('render(): FormBuilder receives the Bee content-change/lock wiring', () => {
|
|
325
|
+
it('passes handleBeeContentChange and the current exportHtmlContentLocked flag to FormBuilder', () => {
|
|
326
|
+
const wrapper = renderEmail();
|
|
327
|
+
const schema = _.cloneDeep(emailDefinition);
|
|
328
|
+
wrapper.setState({
|
|
329
|
+
schema,
|
|
330
|
+
exportHtmlContentLocked: true,
|
|
331
|
+
formData: { 0: { activeTab: 'en', tabKey: 'k1' } },
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const formBuilder = wrapper.find(FormBuilder);
|
|
335
|
+
expect(formBuilder.exists()).toBe(true);
|
|
336
|
+
expect(formBuilder.prop('onContentChange')).toBe(wrapper.instance().handleBeeContentChange);
|
|
337
|
+
expect(formBuilder.prop('exportHtmlLocked')).toBe(true);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('reflects exportHtmlLocked=false when Export HTML is not content-locked', () => {
|
|
341
|
+
const wrapper = renderEmail();
|
|
342
|
+
const schema = _.cloneDeep(emailDefinition);
|
|
343
|
+
wrapper.setState({
|
|
344
|
+
schema,
|
|
345
|
+
exportHtmlContentLocked: false,
|
|
346
|
+
formData: { 0: { activeTab: 'en', tabKey: 'k1' } },
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
expect(wrapper.find(FormBuilder).prop('exportHtmlLocked')).toBe(false);
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
describe('removeStandAlone (tab-options-popover visibility outside full mode)', () => {
|
|
354
|
+
const getSwitchEditorLabel = (schema) => {
|
|
355
|
+
const cols = schema.containers[0].tabBarExtraContent.sections[0].inputFields[0].cols;
|
|
356
|
+
const popoverCol = cols.find((c) => c.id === 'tab-options-popover');
|
|
357
|
+
return popoverCol.content.sections[0].inputFields
|
|
358
|
+
.map((f) => f.cols[0])
|
|
359
|
+
.find((c) => c.id === 'switch-editor-label');
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
it('hides add-language-button and switch-editor-label when not in full mode', () => {
|
|
363
|
+
const wrapper = renderEmail({ isFullMode: false });
|
|
364
|
+
const result = wrapper.instance().removeStandAlone(_.cloneDeep(emailDefinition));
|
|
365
|
+
|
|
366
|
+
const cols = result.containers[0].tabBarExtraContent.sections[0].inputFields[0].cols;
|
|
367
|
+
const addLangCol = cols.find((c) => c.id === 'add-language-button');
|
|
368
|
+
expect(addLangCol.colStyle.display).toBe(DISPLAY_NONE);
|
|
369
|
+
expect(getSwitchEditorLabel(result).style.display).toBe(DISPLAY_NONE);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('leaves add-language-button untouched in full mode', () => {
|
|
373
|
+
const wrapper = renderEmail({ isFullMode: true });
|
|
374
|
+
const result = wrapper.instance().removeStandAlone(_.cloneDeep(emailDefinition));
|
|
375
|
+
|
|
376
|
+
const cols = result.containers[0].tabBarExtraContent.sections[0].inputFields[0].cols;
|
|
377
|
+
const addLangCol = cols.find((c) => c.id === 'add-language-button');
|
|
378
|
+
expect(addLangCol.colStyle.display).toBe('');
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
describe('handleEdmSave (mode === "switchEditor")', () => {
|
|
383
|
+
it('permanently hides the switch-editor-label once switched from Bee to CKEditor', () => {
|
|
384
|
+
const wrapper = renderEmail();
|
|
385
|
+
const schema = _.cloneDeep(emailDefinition);
|
|
386
|
+
schema.containers[0].isActive = true;
|
|
387
|
+
wrapper.setState({
|
|
388
|
+
mode: 'switchEditor',
|
|
389
|
+
currentTab: 1,
|
|
390
|
+
schema,
|
|
391
|
+
formData: {
|
|
392
|
+
0: { activeTab: 'en', selectedLanguages: ['en'], en: { id: 'drag1' } },
|
|
393
|
+
base: { activeTab: 'en' },
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
wrapper.instance().handleEdmSave();
|
|
398
|
+
|
|
399
|
+
expect(wrapper.instance().props.actions.getCmsData).toHaveBeenCalled();
|
|
400
|
+
const cols = wrapper.state('schema').containers[0].tabBarExtraContent.sections[0].inputFields[0].cols;
|
|
401
|
+
const popoverCol = cols.find((c) => c.id === 'tab-options-popover');
|
|
402
|
+
const switchLabel = popoverCol.content.sections[0].inputFields
|
|
403
|
+
.map((f) => f.cols[0])
|
|
404
|
+
.find((c) => c.id === 'switch-editor-label');
|
|
405
|
+
expect(switchLabel.style.display).toBe(DISPLAY_NONE);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
describe('getBEEData (Bee editor save callback)', () => {
|
|
410
|
+
it('captures the Bee content into formData and permanently hides switch-editor-label', () => {
|
|
411
|
+
const wrapper = renderEmail();
|
|
412
|
+
const schema = _.cloneDeep(emailDefinition);
|
|
413
|
+
schema.containers[0].isActive = true;
|
|
414
|
+
wrapper.setState({
|
|
415
|
+
currentTab: 1,
|
|
416
|
+
schema,
|
|
417
|
+
formData: {
|
|
418
|
+
0: { activeTab: 'en', selectedLanguages: ['en'], en: {} },
|
|
419
|
+
base: { en: {} },
|
|
420
|
+
},
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
wrapper.instance().getBEEData('{"page":{}}', '<html>bee content</html>');
|
|
424
|
+
|
|
425
|
+
expect(wrapper.state('formData')[0].en['template-content']).toBe('<html>bee content</html>');
|
|
426
|
+
expect(wrapper.state('formData')[0].en.is_drag_drop).toBe(false);
|
|
427
|
+
const cols = wrapper.state('schema').containers[0].tabBarExtraContent.sections[0].inputFields[0].cols;
|
|
428
|
+
const popoverCol = cols.find((c) => c.id === 'tab-options-popover');
|
|
429
|
+
const switchLabel = popoverCol.content.sections[0].inputFields
|
|
430
|
+
.map((f) => f.cols[0])
|
|
431
|
+
.find((c) => c.id === 'switch-editor-label');
|
|
432
|
+
expect(switchLabel.style.display).toBe(DISPLAY_NONE);
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
describe('componentWillReceiveProps: originalBaseTemplateContent baseline capture (lines 471-477)', () => {
|
|
437
|
+
it('captures the base-language template content exactly once when templateDetails first arrives in edit mode', () => {
|
|
438
|
+
const wrapper = renderEmail({
|
|
439
|
+
Templates: {},
|
|
440
|
+
Email: {},
|
|
441
|
+
currentOrgDetails: { basic_details: { base_language: 'en' } },
|
|
442
|
+
});
|
|
443
|
+
wrapper.setState({ isEdit: true, formData: { 'template-name': '' } });
|
|
444
|
+
const instance = wrapper.instance();
|
|
445
|
+
expect(instance.originalContentCaptured).toBe(false);
|
|
446
|
+
|
|
447
|
+
const templateDetails = { versions: { base: { en: { 'template-content': '<p>Baseline</p>' } } } };
|
|
448
|
+
instance.componentWillReceiveProps({
|
|
449
|
+
...wrapper.props(),
|
|
450
|
+
Templates: {},
|
|
451
|
+
Email: { templateDetails },
|
|
452
|
+
currentOrgDetails: { basic_details: { base_language: 'en' } },
|
|
453
|
+
isGetFormData: false,
|
|
454
|
+
location: { query: {} },
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
expect(instance.originalContentCaptured).toBe(true);
|
|
458
|
+
expect(instance.originalBaseTemplateContent).toBe('<p>Baseline</p>');
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('does not overwrite the baseline on a subsequent call (only captured once)', () => {
|
|
462
|
+
const wrapper = renderEmail({
|
|
463
|
+
Templates: {},
|
|
464
|
+
Email: {},
|
|
465
|
+
currentOrgDetails: { basic_details: { base_language: 'en' } },
|
|
466
|
+
});
|
|
467
|
+
wrapper.setState({ isEdit: true, formData: { 'template-name': '' } });
|
|
468
|
+
const instance = wrapper.instance();
|
|
469
|
+
instance.originalContentCaptured = true;
|
|
470
|
+
instance.originalBaseTemplateContent = '<p>First</p>';
|
|
471
|
+
|
|
472
|
+
const templateDetails = { versions: { base: { en: { 'template-content': '<p>Second</p>' } } } };
|
|
473
|
+
instance.componentWillReceiveProps({
|
|
474
|
+
...wrapper.props(),
|
|
475
|
+
Templates: {},
|
|
476
|
+
Email: { templateDetails },
|
|
477
|
+
currentOrgDetails: { basic_details: { base_language: 'en' } },
|
|
478
|
+
isGetFormData: false,
|
|
479
|
+
location: { query: {} },
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
expect(instance.originalBaseTemplateContent).toBe('<p>First</p>');
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('uses the incoming (nextProps) base language, not the stale current-props one, when both arrive together', () => {
|
|
486
|
+
// this.props.currentOrgDetails is stale/unset (as it would be before org details load);
|
|
487
|
+
// nextProps carries the real base language in the same update as templateDetails.
|
|
488
|
+
const wrapper = renderEmail({
|
|
489
|
+
Templates: {},
|
|
490
|
+
Email: {},
|
|
491
|
+
currentOrgDetails: { basic_details: {} },
|
|
492
|
+
});
|
|
493
|
+
wrapper.setState({ isEdit: true, formData: { 'template-name': '' } });
|
|
494
|
+
const instance = wrapper.instance();
|
|
495
|
+
|
|
496
|
+
const templateDetails = {
|
|
497
|
+
versions: {
|
|
498
|
+
base: {
|
|
499
|
+
fr: { 'template-content': '<p>Contenu français</p>' },
|
|
500
|
+
en: { 'template-content': '<p>English content</p>' },
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
};
|
|
504
|
+
instance.componentWillReceiveProps({
|
|
505
|
+
...wrapper.props(),
|
|
506
|
+
Templates: {},
|
|
507
|
+
Email: { templateDetails },
|
|
508
|
+
currentOrgDetails: { basic_details: { base_language: 'fr' } },
|
|
509
|
+
isGetFormData: false,
|
|
510
|
+
location: { query: {} },
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
expect(instance.originalBaseTemplateContent).toBe('<p>Contenu français</p>');
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
describe('componentWillReceiveProps: BEE init insert-image/switch-editor/tab-option-icon visibility (lines 538-551)', () => {
|
|
518
|
+
it('shows the switch-editor-label and hides insert-image when the BEE template is drag-drop enabled', () => {
|
|
519
|
+
hasSupportCKEditor.mockReturnValue(true);
|
|
520
|
+
const schema = _.cloneDeep(emailDefinition);
|
|
521
|
+
schema.containers[0].isActive = true;
|
|
522
|
+
const wrapper = renderEmail({
|
|
523
|
+
Templates: {},
|
|
524
|
+
Email: {},
|
|
525
|
+
isFullMode: true,
|
|
526
|
+
currentOrgDetails: { basic_details: { base_language: 'en' } },
|
|
527
|
+
});
|
|
528
|
+
wrapper.setState({
|
|
529
|
+
schema,
|
|
530
|
+
formData: {
|
|
531
|
+
0: { activeTab: 'en', selectedLanguages: ['en'], en: {} },
|
|
532
|
+
base: { en: {} },
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
const instance = wrapper.instance();
|
|
536
|
+
|
|
537
|
+
instance.componentWillReceiveProps({
|
|
538
|
+
...wrapper.props(),
|
|
539
|
+
currentOrgDetails: { basic_details: { base_language: 'en' } },
|
|
540
|
+
Templates: {},
|
|
541
|
+
Email: {
|
|
542
|
+
CmsSettings: { langId: 'en', isDragDrop: true, tokenData: 'tok', uuid: 'uuid-1', type: 'BEE_PLUGIN' },
|
|
543
|
+
},
|
|
544
|
+
isGetFormData: false,
|
|
545
|
+
location: { query: {} },
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
const cols = wrapper.state('schema').containers[0].tabBarExtraContent.sections[0].inputFields[0].cols;
|
|
549
|
+
const insertImageCol = cols.find((c) => c.id === 'insert-image');
|
|
550
|
+
expect(insertImageCol.style.display).toBe(DISPLAY_NONE);
|
|
551
|
+
const popoverCol = cols.find((c) => c.id === 'tab-options-popover');
|
|
552
|
+
const switchLabel = popoverCol.content.sections[0].inputFields
|
|
553
|
+
.map((f) => f.cols[0])
|
|
554
|
+
.find((c) => c.id === 'switch-editor-label');
|
|
555
|
+
expect(switchLabel.style.display).toBe('');
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it('hides insert-image visibility toggle when the BEE template is not drag-drop (CKEditor content)', () => {
|
|
559
|
+
const schema = _.cloneDeep(emailDefinition);
|
|
560
|
+
schema.containers[0].isActive = true;
|
|
561
|
+
const wrapper = renderEmail({
|
|
562
|
+
Templates: {},
|
|
563
|
+
Email: {},
|
|
564
|
+
isFullMode: true,
|
|
565
|
+
currentOrgDetails: { basic_details: { base_language: 'en' } },
|
|
566
|
+
});
|
|
567
|
+
wrapper.setState({
|
|
568
|
+
schema,
|
|
569
|
+
formData: {
|
|
570
|
+
0: { activeTab: 'en', selectedLanguages: ['en'], en: {} },
|
|
571
|
+
base: { en: {} },
|
|
572
|
+
},
|
|
573
|
+
});
|
|
574
|
+
const instance = wrapper.instance();
|
|
575
|
+
|
|
576
|
+
instance.componentWillReceiveProps({
|
|
577
|
+
...wrapper.props(),
|
|
578
|
+
currentOrgDetails: { basic_details: { base_language: 'en' } },
|
|
579
|
+
Templates: {},
|
|
580
|
+
Email: {
|
|
581
|
+
CmsSettings: { langId: 'en', isDragDrop: false, htmlContent: encodeURIComponent('<p>ck content</p>') },
|
|
582
|
+
},
|
|
583
|
+
isGetFormData: false,
|
|
584
|
+
location: { query: {} },
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
const cols = wrapper.state('schema').containers[0].tabBarExtraContent.sections[0].inputFields[0].cols;
|
|
588
|
+
const insertImageCol = cols.find((c) => c.id === 'insert-image');
|
|
589
|
+
expect(insertImageCol.style.display).toBe('');
|
|
590
|
+
const popoverCol = cols.find((c) => c.id === 'tab-options-popover');
|
|
591
|
+
const switchLabel = popoverCol.content.sections[0].inputFields
|
|
592
|
+
.map((f) => f.cols[0])
|
|
593
|
+
.find((c) => c.id === 'switch-editor-label');
|
|
594
|
+
expect(switchLabel.style.display).toBe(DISPLAY_NONE);
|
|
595
|
+
});
|
|
596
|
+
});
|
|
597
|
+
});
|