@capillarytech/creatives-library 9.0.61-alpha.3 → 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.
Files changed (59) hide show
  1. package/constants/unified.js +8 -2
  2. package/package.json +1 -1
  3. package/services/api.js +5 -0
  4. package/services/tests/api.test.js +44 -0
  5. package/utils/common.js +8 -0
  6. package/utils/downloadFile.js +57 -0
  7. package/utils/tests/downloadFile.test.js +219 -0
  8. package/v2Components/FormBuilder/Classic.js +43 -9
  9. package/v2Components/FormBuilder/Functional/FormBuilderShell.js +324 -69
  10. package/v2Components/FormBuilder/Functional/channels/mobilepush/buildSubmitPayload.js +10 -0
  11. package/v2Components/FormBuilder/Functional/channels/mobilepush/config.js +75 -0
  12. package/v2Components/FormBuilder/Functional/channels/mobilepush/getEditorErrorDescriptor.js +74 -0
  13. package/v2Components/FormBuilder/Functional/channels/mobilepush/index.js +28 -0
  14. package/v2Components/FormBuilder/Functional/channels/mobilepush/modals.js +21 -0
  15. package/v2Components/FormBuilder/Functional/channels/mobilepush/runSubmitPipeline.js +45 -0
  16. package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/getEditorErrorDescriptor.test.js +162 -0
  17. package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/modals.test.js +37 -0
  18. package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/runSubmitPipeline.test.js +70 -0
  19. package/v2Components/FormBuilder/Functional/channels/mobilepush/tests/validate.test.js +274 -0
  20. package/v2Components/FormBuilder/Functional/channels/mobilepush/validate.js +251 -0
  21. package/v2Components/FormBuilder/Functional/channels/registry.js +2 -0
  22. package/v2Components/FormBuilder/Functional/constants.js +46 -8
  23. package/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js +175 -28
  24. package/v2Components/FormBuilder/Functional/core/store/formReducer.js +75 -6
  25. package/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js +10 -20
  26. package/v2Components/FormBuilder/Functional/layout/FieldSlot.js +9 -1
  27. package/v2Components/FormBuilder/Functional/layout/SchemaForm.js +20 -3
  28. package/v2Components/FormBuilder/Functional/layout/Section.js +6 -1
  29. package/v2Components/FormBuilder/Functional/layout/TabsContainer.js +85 -0
  30. package/v2Components/FormBuilder/Functional/renderers/mpushRenderers.js +451 -0
  31. package/v2Components/FormBuilder/Functional/tests/fieldSlot.test.js +9 -2
  32. package/v2Components/FormBuilder/Functional/tests/manual.typingPerf.test.js +135 -0
  33. package/v2Components/FormBuilder/Functional/tests/mpush.crossFlowParity.test.js +430 -0
  34. package/v2Components/FormBuilder/Functional/tests/mpush.ctaFlows.parity.test.js +320 -0
  35. package/v2Components/FormBuilder/Functional/tests/mpush.editContainer.parity.test.js +148 -0
  36. package/v2Components/FormBuilder/Functional/tests/mpush.engine.test.js +306 -0
  37. package/v2Components/FormBuilder/Functional/tests/mpush.libraryValidity.parity.test.js +152 -0
  38. package/v2Components/FormBuilder/Functional/tests/mpush.shellEvents.test.js +200 -0
  39. package/v2Components/FormBuilder/Functional/tests/mpushRenderers.test.js +382 -0
  40. package/v2Components/FormBuilder/Functional/tests/schemaForm.test.js +17 -0
  41. package/v2Components/FormBuilder/Functional/tests/tabsContainer.test.js +112 -0
  42. package/v2Components/FormBuilder/_formBuilder.scss +11 -0
  43. package/v2Components/FormBuilder/index.js +27 -17
  44. package/v2Components/FormBuilder/tests/entryGate.test.js +90 -7
  45. package/v2Components/FormBuilder/tests/mpush.characterization.test.js +459 -0
  46. package/v2Containers/BeeEditor/index.js +8 -0
  47. package/v2Containers/Email/index.js +118 -16
  48. package/v2Containers/Email/initialSchema.js +25 -3
  49. package/v2Containers/Email/messages.js +16 -0
  50. package/v2Containers/Email/tests/index.test.js +597 -0
  51. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +55 -1
  52. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +141 -0
  53. package/v2Containers/EmailWrapper/components/_emailHTMLEditor.scss +11 -0
  54. package/v2Containers/Templates/index.js +53 -0
  55. package/v2Containers/Templates/messages.js +8 -0
  56. package/v2Containers/Templates/tests/index.test.js +170 -0
  57. package/v2Containers/Viber/constants.js +0 -3
  58. package/v2Containers/Viber/sagas.js +3 -1
  59. package/v2Containers/Viber/tests/saga.test.js +21 -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
+ });