@capillarytech/creatives-library 8.0.276 → 8.0.278
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/package.json +1 -1
- package/utils/tests/imageUrlUpload.test.js +298 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +2 -0
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +5 -2
- package/v2Containers/CreativesContainer/index.js +10 -6
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +165 -41
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +6 -0
- package/v2Containers/Email/index.js +75 -9
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +14 -8
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +6 -2
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +189 -6
- package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +137 -0
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +7 -3
- package/v2Containers/EmailWrapper/index.js +3 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +26 -0
- package/v2Containers/Facebook/Advertisement/index.js +1 -1
- package/v2Containers/Line/Container/_lineCreate.scss +1 -0
- package/v2Containers/Line/Container/style.js +1 -1
- package/v2Containers/MobilePush/Edit/index.js +6 -5
- package/v2Containers/SmsTrai/Create/index.scss +1 -1
- package/v2Containers/SmsTrai/Edit/index.js +9 -3
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +11682 -86
- package/v2Containers/SmsTrai/Edit/tests/index.test.js +5 -0
- package/v2Containers/Viber/index.js +7 -0
- package/v2Containers/Viber/index.scss +4 -1
- package/v2Containers/Viber/style.js +0 -2
|
@@ -26,7 +26,7 @@ import * as globalActions from '../Cap/actions';
|
|
|
26
26
|
import './_email.scss';
|
|
27
27
|
import {getMessageObject} from '../../utils/messageUtils';
|
|
28
28
|
import EmailPreview from '../../v2Components/EmailPreview';
|
|
29
|
-
import { getDecodedFileName ,
|
|
29
|
+
import { getDecodedFileName, hasLiquidSupportFeature, hasSupportCKEditor } from '../../utils/common';
|
|
30
30
|
import Pagination from '../../v2Components/Pagination';
|
|
31
31
|
import * as creativesContainerActions from '../CreativesContainer/actions';
|
|
32
32
|
import withCreatives from '../../hoc/withCreatives';
|
|
@@ -95,6 +95,8 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
95
95
|
|
|
96
96
|
this.isTagLoaded = false;
|
|
97
97
|
this.edmEvent = undefined;
|
|
98
|
+
// When schema is set after CmsSettings (e.g. library create BEE), allow BEE init to run once
|
|
99
|
+
this.schemaJustFilledForBee = false;
|
|
98
100
|
this.supportedLanguages = this.getSupportedLanguages(props);
|
|
99
101
|
this.map = {
|
|
100
102
|
"template-name": {
|
|
@@ -235,7 +237,12 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
235
237
|
this.props.actions.getCmsSetting(BEE_PLUGIN, dragDropId, 'create', undefined, isBEESupport, isBEEAppEnable);
|
|
236
238
|
}
|
|
237
239
|
}
|
|
238
|
-
|
|
240
|
+
const hasUploadedContent = !this.props.params?.id && this.props.Templates?.selectedEmailLayout && !_.isEmpty(formData);
|
|
241
|
+
this.setState({
|
|
242
|
+
content: (this.props.Templates.selectedEmailLayout ? this.props.Templates.selectedEmailLayout : ''),
|
|
243
|
+
formData,
|
|
244
|
+
...(hasUploadedContent ? { loadingStatus: 2 } : {}),
|
|
245
|
+
});
|
|
239
246
|
|
|
240
247
|
// setTimeout(() => {
|
|
241
248
|
// // this.getFormData();
|
|
@@ -286,6 +293,12 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
286
293
|
}
|
|
287
294
|
}
|
|
288
295
|
}
|
|
296
|
+
|
|
297
|
+
// When SUPPORT_CK_EDITOR: after zip/HTML upload we show with selectedEmailLayout. Ensure spinner stops
|
|
298
|
+
// even if formData was empty on first mount (e.g. currentOrgDetails not yet available).
|
|
299
|
+
if (!this.props.params?.id && this.props.Templates?.selectedEmailLayout) {
|
|
300
|
+
this.setState((prev) => ({ loadingStatus: Math.max(prev.loadingStatus, 2) }));
|
|
301
|
+
}
|
|
289
302
|
}
|
|
290
303
|
|
|
291
304
|
|
|
@@ -313,11 +326,10 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
313
326
|
// }
|
|
314
327
|
const {isFullMode, isGetFormData} = nextProps;
|
|
315
328
|
if (isFullMode && isGetFormData && !_.isEqual(isGetFormData, this.props.isGetFormData) && !this.state.isDragDrop) {
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
// Don't start validation if we're in Test & Preview mode
|
|
329
|
+
// Only for CK editor: when Done is clicked in full mode, trigger the same chain as library
|
|
330
|
+
// so that FormBuilder validation and saveFormData run reliably (getFormData -> saveValidationData -> startValidation).
|
|
319
331
|
if (!nextProps.isTestAndPreviewMode) {
|
|
320
|
-
this.
|
|
332
|
+
this.getFormData();
|
|
321
333
|
}
|
|
322
334
|
}
|
|
323
335
|
if (this.state.languageDataSet && nextProps.Templates.selectedEmailLayout && nextProps.Templates.selectedEmailLayout !== '' && !_.isEqual(this.props.Templates.selectedEmailLayout, nextProps.Templates.selectedEmailLayout )) {
|
|
@@ -325,7 +337,9 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
325
337
|
}
|
|
326
338
|
if (nextProps.metaEntities && nextProps.metaEntities.layouts && nextProps.metaEntities.layouts.length > 0 && _.isEmpty(this.state.schema)) {
|
|
327
339
|
const newSchema = this.injectEvents(nextProps.metaEntities.layouts[0].definition);
|
|
328
|
-
|
|
340
|
+
this.applyTabOptionIconVisibility(newSchema);
|
|
341
|
+
// So BEE init can run when CmsSettings already arrived (e.g. library create after default template selected)
|
|
342
|
+
this.schemaJustFilledForBee = true;
|
|
329
343
|
this.setState({schema: newSchema, loadingStatus: this.state.loadingStatus + 1});
|
|
330
344
|
if (this.props.location.query.module !== 'library' || (this.props.location.query.module === 'library' && this.props.getDefaultTags)) {
|
|
331
345
|
const query = {
|
|
@@ -487,7 +501,11 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
487
501
|
}
|
|
488
502
|
}
|
|
489
503
|
|
|
490
|
-
|
|
504
|
+
const cmsSettingsChanged = !_.isEqual(this.props.Email.CmsSettings, nextProps.Email.CmsSettings);
|
|
505
|
+
const hasCmsSettingsAndSchema = !_.isEmpty(nextProps.Email.CmsSettings) && !_.isEmpty(this.state.schema);
|
|
506
|
+
const shouldRunBeeInit = hasCmsSettingsAndSchema && (cmsSettingsChanged || this.schemaJustFilledForBee);
|
|
507
|
+
if (shouldRunBeeInit) {
|
|
508
|
+
this.schemaJustFilledForBee = false;
|
|
491
509
|
const apiLangId = nextProps.Email.CmsSettings.langId;
|
|
492
510
|
const langId = nextProps.Email.CmsSettings.langId !== "undefined" ? nextProps.Email.CmsSettings.langId : nextProps.currentOrgDetails.basic_details.base_language;
|
|
493
511
|
|
|
@@ -1560,7 +1578,12 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
1560
1578
|
newFormData[0] = baseData;
|
|
1561
1579
|
newFormData[0].base = true;
|
|
1562
1580
|
}
|
|
1563
|
-
|
|
1581
|
+
// Don't clear selectedEmailLayout when we're in create mode with uploaded content -
|
|
1582
|
+
// EmailWrapper uses it to keep isShowEmailCreate true. Clearing it would unmount Email and show wrong UI.
|
|
1583
|
+
const isCreateWithUpload = !props.params?.id && props.Templates?.selectedEmailLayout;
|
|
1584
|
+
if (!isCreateWithUpload) {
|
|
1585
|
+
this.props.templatesActions.resetUploadData();
|
|
1586
|
+
}
|
|
1564
1587
|
return newFormData;
|
|
1565
1588
|
}
|
|
1566
1589
|
|
|
@@ -1752,6 +1775,16 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
1752
1775
|
newTabPopoverSection.cols[0].style.display = isDragDrop ? "" : "none";
|
|
1753
1776
|
}
|
|
1754
1777
|
});
|
|
1778
|
+
// Hide tab-option-icon (switch editor trigger) when SUPPORT_CK_EDITOR is disabled
|
|
1779
|
+
if (containerInputFieldCol.value && containerInputFieldCol.value.sections) {
|
|
1780
|
+
_.forEach(containerInputFieldCol.value.sections[0].inputFields, (valueInputField) => {
|
|
1781
|
+
_.forEach(valueInputField.cols, (valueCol) => {
|
|
1782
|
+
if (valueCol.id === 'tab-option-icon') {
|
|
1783
|
+
valueCol.colStyle = { ...valueCol.colStyle, display: hasSupportCKEditor() ? 'flex' : 'none' };
|
|
1784
|
+
}
|
|
1785
|
+
});
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1755
1788
|
}
|
|
1756
1789
|
});
|
|
1757
1790
|
}
|
|
@@ -1760,6 +1793,28 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
1760
1793
|
this.setState({schema, isSchemaChanged: true});
|
|
1761
1794
|
}
|
|
1762
1795
|
|
|
1796
|
+
/**
|
|
1797
|
+
* Hides the tab-option-icon (switch editor trigger) in schema when SUPPORT_CK_EDITOR is disabled,
|
|
1798
|
+
* so users cannot switch editor if the feature is not enabled.
|
|
1799
|
+
*/
|
|
1800
|
+
applyTabOptionIconVisibility = (schema) => {
|
|
1801
|
+
if (!schema || !schema.containers) return;
|
|
1802
|
+
_.forEach(schema.containers, (container) => {
|
|
1803
|
+
if (!container.isActive || !container.tabBarExtraContent?.sections?.[0]?.inputFields?.[0]?.cols) return;
|
|
1804
|
+
_.forEach(container.tabBarExtraContent.sections[0].inputFields[0].cols, (col) => {
|
|
1805
|
+
if (col.id === 'tab-options-popover' && col.value?.sections?.[0]?.inputFields) {
|
|
1806
|
+
_.forEach(col.value.sections[0].inputFields, (inputField) => {
|
|
1807
|
+
_.forEach(inputField.cols, (c) => {
|
|
1808
|
+
if (c.id === 'tab-option-icon') {
|
|
1809
|
+
c.colStyle = { ...(c.colStyle || {}), display: hasSupportCKEditor() ? 'flex' : 'none' };
|
|
1810
|
+
}
|
|
1811
|
+
});
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
});
|
|
1815
|
+
});
|
|
1816
|
+
};
|
|
1817
|
+
|
|
1763
1818
|
showInsertImageButton = (passedSchema) => {
|
|
1764
1819
|
const schema = passedSchema || _.cloneDeep(this.state.schema);
|
|
1765
1820
|
_.forEach(schema.containers, (container) => {
|
|
@@ -2869,6 +2924,17 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
|
|
|
2869
2924
|
if (!isLoading) {
|
|
2870
2925
|
isLoading = this.state.loadingStatus < 2;
|
|
2871
2926
|
}
|
|
2927
|
+
|
|
2928
|
+
// When SUPPORT_CK_EDITOR: after zip/HTML upload we show Email component with selectedEmailLayout.
|
|
2929
|
+
// Don't show spinner once we have uploaded content in formData (create mode, no params.id).
|
|
2930
|
+
if (isLoading && !this.props.params?.id && this.props.Templates?.selectedEmailLayout) {
|
|
2931
|
+
const content = this.state.formData?.[0]?.['template-content']
|
|
2932
|
+
|| _.get(this.state.formData, 'base.template-content')
|
|
2933
|
+
|| (this.state.formData?.base && this.state.formData.base[this.state.formData.base?.activeTab]?.['template-content']);
|
|
2934
|
+
if (content) {
|
|
2935
|
+
isLoading = false;
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2872
2938
|
// if (isLoading) {
|
|
2873
2939
|
// this.props.creativesContainerActions.hideCreativesContanerLoader();
|
|
2874
2940
|
// }
|
|
@@ -73,6 +73,7 @@ const EmailHTMLEditor = (props) => {
|
|
|
73
73
|
getFormdata,
|
|
74
74
|
// Library mode props
|
|
75
75
|
templateData: templateDataProp,
|
|
76
|
+
isEditEmail = true,
|
|
76
77
|
// Uploaded content from zip file
|
|
77
78
|
EmailLayout,
|
|
78
79
|
// Liquid validation
|
|
@@ -174,11 +175,13 @@ const EmailHTMLEditor = (props) => {
|
|
|
174
175
|
// Check if liquid support is enabled
|
|
175
176
|
const isLiquidEnabled = hasLiquidSupportFeature();
|
|
176
177
|
|
|
177
|
-
// Detect edit mode
|
|
178
|
+
// Detect edit mode: when isEditEmail is false (create flow), never treat as edit or fetch template details
|
|
178
179
|
const hasParamsId = params?.id || location?.query?.id || location?.params?.id || location?.pathname?.includes('/edit/');
|
|
179
|
-
const currentTemplateId =
|
|
180
|
-
|| location?.
|
|
181
|
-
|
|
180
|
+
const currentTemplateId = isEditEmail
|
|
181
|
+
? (templateDataProp?._id || params?.id || location?.query?.id || location?.params?.id
|
|
182
|
+
|| location?.pathname?.match(/\/edit\/([^/]+)/)?.[1])
|
|
183
|
+
: (params?.id || location?.query?.id || location?.params?.id || location?.pathname?.match(/\/edit\/([^/]+)/)?.[1]);
|
|
184
|
+
const isEditMode = isEditEmail && (!!currentTemplateId || !!hasParamsId);
|
|
182
185
|
|
|
183
186
|
// Load tags on component mount
|
|
184
187
|
useEffect(() => {
|
|
@@ -685,9 +688,10 @@ const EmailHTMLEditor = (props) => {
|
|
|
685
688
|
return;
|
|
686
689
|
}
|
|
687
690
|
|
|
688
|
-
// 2. Validate Unsubscribe Tag (
|
|
689
|
-
//
|
|
690
|
-
|
|
691
|
+
// 2. Validate Unsubscribe Tag when feature is OFF (when flag is false we require unsubscribe)
|
|
692
|
+
// When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is true: do NOT validate for unsubscribe (aligned with FormBuilder).
|
|
693
|
+
// When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is false: validate and require unsubscribe tag.
|
|
694
|
+
if (!isEmailUnsubscribeTagMandatory() && moduleType === OUTBOUND) {
|
|
691
695
|
// Check if content contains unsubscribe tag (either {{unsubscribe}} or {{unsubscribe(#...)})
|
|
692
696
|
const unsubscribeRegex = /{{unsubscribe(\(#[a-zA-Z\d]{6}\))?}}/g; // eslint-disable-line no-useless-escape
|
|
693
697
|
const hasUnsubscribeTag = unsubscribeRegex.test(htmlContent);
|
|
@@ -706,7 +710,7 @@ const EmailHTMLEditor = (props) => {
|
|
|
706
710
|
if (onValidationFail) {
|
|
707
711
|
onValidationFail();
|
|
708
712
|
}
|
|
709
|
-
// Block save - unsubscribe tag is
|
|
713
|
+
// Block save - unsubscribe tag is required when validation is enabled
|
|
710
714
|
return;
|
|
711
715
|
}
|
|
712
716
|
}
|
|
@@ -1204,6 +1208,7 @@ EmailHTMLEditor.propTypes = {
|
|
|
1204
1208
|
onValidationFail: PropTypes.func,
|
|
1205
1209
|
moduleType: PropTypes.string,
|
|
1206
1210
|
onHtmlEditorValidationStateChange: PropTypes.func,
|
|
1211
|
+
isEditEmail: PropTypes.bool,
|
|
1207
1212
|
};
|
|
1208
1213
|
|
|
1209
1214
|
EmailHTMLEditor.defaultProps = {
|
|
@@ -1239,6 +1244,7 @@ EmailHTMLEditor.defaultProps = {
|
|
|
1239
1244
|
onValidationFail: null,
|
|
1240
1245
|
moduleType: null,
|
|
1241
1246
|
onHtmlEditorValidationStateChange: null,
|
|
1247
|
+
isEditEmail: true,
|
|
1242
1248
|
};
|
|
1243
1249
|
|
|
1244
1250
|
const EmailHTMLEditorWithIntl = injectIntl(EmailHTMLEditor);
|
|
@@ -182,6 +182,7 @@ const EmailWrapperView = ({
|
|
|
182
182
|
Email,
|
|
183
183
|
templateData: templateDataProp,
|
|
184
184
|
params,
|
|
185
|
+
isEditEmail = true,
|
|
185
186
|
fetchingLiquidTags,
|
|
186
187
|
createTemplateInProgress,
|
|
187
188
|
fetchingCmsData,
|
|
@@ -202,8 +203,9 @@ const EmailWrapperView = ({
|
|
|
202
203
|
let isHTMLEditorMode = false;
|
|
203
204
|
|
|
204
205
|
if (supportCKEditor) {
|
|
205
|
-
//
|
|
206
|
-
|
|
206
|
+
// UPLOAD mode should use Email component (CKEditor), NOT HTML editor
|
|
207
|
+
// This ensures consistency between create and edit flows when SUPPORT_CK_EDITOR is enabled
|
|
208
|
+
isHTMLEditorMode = false; // Always use Email component (CKEditor) in legacy flow
|
|
207
209
|
} else if (isEditModeForEditor) {
|
|
208
210
|
isHTMLEditorMode = !isExplicitlyBEEEditor;
|
|
209
211
|
} else {
|
|
@@ -257,6 +259,7 @@ const EmailWrapperView = ({
|
|
|
257
259
|
getFormdata,
|
|
258
260
|
// Library mode props
|
|
259
261
|
templateData: templateDataProp,
|
|
262
|
+
isEditEmail,
|
|
260
263
|
// Uploaded content from zip file
|
|
261
264
|
EmailLayout,
|
|
262
265
|
// Liquid validation
|
|
@@ -372,6 +375,7 @@ EmailWrapperView.propTypes = {
|
|
|
372
375
|
Email: PropTypes.object,
|
|
373
376
|
templateData: PropTypes.object,
|
|
374
377
|
params: PropTypes.object,
|
|
378
|
+
isEditEmail: PropTypes.bool,
|
|
375
379
|
fetchingLiquidTags: PropTypes.bool,
|
|
376
380
|
createTemplateInProgress: PropTypes.bool,
|
|
377
381
|
fetchingCmsData: PropTypes.bool,
|
|
@@ -386,6 +386,7 @@ const defaultProps = {
|
|
|
386
386
|
isGetFormData: false,
|
|
387
387
|
getFormdata: jest.fn(),
|
|
388
388
|
templateData: null,
|
|
389
|
+
isEditEmail: true,
|
|
389
390
|
EmailLayout: null,
|
|
390
391
|
getLiquidTags: jest.fn((content, callback) => callback({ askAiraResponse: { data: [] }, isError: false })),
|
|
391
392
|
showLiquidErrorInFooter: jest.fn(),
|
|
@@ -642,6 +643,45 @@ describe('EmailHTMLEditor', () => {
|
|
|
642
643
|
// Should trigger fetch for new template ID
|
|
643
644
|
expect(emailActions.getTemplateDetails).toHaveBeenCalled();
|
|
644
645
|
});
|
|
646
|
+
|
|
647
|
+
it('does not fetch template details when isEditEmail is false even if templateData has _id (create flow)', async () => {
|
|
648
|
+
const emailActions = {
|
|
649
|
+
...defaultProps.emailActions,
|
|
650
|
+
getTemplateDetails: jest.fn(),
|
|
651
|
+
};
|
|
652
|
+
renderWithIntl({
|
|
653
|
+
isEditEmail: false,
|
|
654
|
+
templateData: { _id: 'stale-template-id', name: 'Stale' },
|
|
655
|
+
params: {},
|
|
656
|
+
location: { query: {}, pathname: '/email/create' },
|
|
657
|
+
Email: { templateDetails: null, getTemplateDetailsInProgress: false, fetchingCmsData: false },
|
|
658
|
+
emailActions,
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
await waitFor(() => {
|
|
662
|
+
expect(emailActions.getTemplateDetails).not.toHaveBeenCalled();
|
|
663
|
+
}, { timeout: 500 });
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
it('uses templateData._id for currentTemplateId and fetches when isEditEmail is true', async () => {
|
|
667
|
+
const emailActions = {
|
|
668
|
+
...defaultProps.emailActions,
|
|
669
|
+
getTemplateDetails: jest.fn(),
|
|
670
|
+
};
|
|
671
|
+
const templateId = 'edit-template-id';
|
|
672
|
+
renderWithIntl({
|
|
673
|
+
isEditEmail: true,
|
|
674
|
+
templateData: { _id: templateId, name: 'Edit Template' },
|
|
675
|
+
params: {},
|
|
676
|
+
location: { query: {}, pathname: '/email/create' },
|
|
677
|
+
Email: { templateDetails: null, getTemplateDetailsInProgress: false, fetchingCmsData: false },
|
|
678
|
+
emailActions,
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
await waitFor(() => {
|
|
682
|
+
expect(emailActions.getTemplateDetails).toHaveBeenCalledWith(templateId, 'email');
|
|
683
|
+
}, { timeout: 500 });
|
|
684
|
+
});
|
|
645
685
|
});
|
|
646
686
|
|
|
647
687
|
describe('Subject Handling', () => {
|
|
@@ -976,8 +1016,9 @@ describe('EmailHTMLEditor', () => {
|
|
|
976
1016
|
}, { timeout: 3000 });
|
|
977
1017
|
});
|
|
978
1018
|
|
|
979
|
-
it('blocks save when unsubscribe
|
|
980
|
-
|
|
1019
|
+
it('blocks save when unsubscribe validation is on (flag false) and tag is missing', async () => {
|
|
1020
|
+
// When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is false we validate and require unsubscribe
|
|
1021
|
+
isEmailUnsubscribeTagMandatory.mockReturnValue(false);
|
|
981
1022
|
const onValidationFail = jest.fn();
|
|
982
1023
|
const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
|
|
983
1024
|
|
|
@@ -1005,15 +1046,15 @@ describe('EmailHTMLEditor', () => {
|
|
|
1005
1046
|
}, { timeout: 3000 });
|
|
1006
1047
|
});
|
|
1007
1048
|
|
|
1008
|
-
it('allows save when unsubscribe tag is present', () => {
|
|
1009
|
-
isEmailUnsubscribeTagMandatory.mockReturnValue(
|
|
1049
|
+
it('allows save when unsubscribe validation is on and tag is present', () => {
|
|
1050
|
+
isEmailUnsubscribeTagMandatory.mockReturnValue(false);
|
|
1010
1051
|
renderWithIntl({
|
|
1011
1052
|
isGetFormData: true,
|
|
1012
1053
|
subject: 'Valid Subject',
|
|
1013
1054
|
htmlContent: '<p>Content {{unsubscribe}}</p>',
|
|
1014
1055
|
moduleType: 'OUTBOUND',
|
|
1015
1056
|
});
|
|
1016
|
-
// Should proceed with save
|
|
1057
|
+
// Should proceed with save (validation passes)
|
|
1017
1058
|
});
|
|
1018
1059
|
|
|
1019
1060
|
it('blocks save for non-liquid orgs when tag validation fails', async () => {
|
|
@@ -1897,6 +1938,117 @@ describe('EmailHTMLEditor', () => {
|
|
|
1897
1938
|
});
|
|
1898
1939
|
});
|
|
1899
1940
|
|
|
1941
|
+
describe('Template content extraction (lines 291-309)', () => {
|
|
1942
|
+
it('extracts content and subject from templateDataProp.base branch', async () => {
|
|
1943
|
+
const ref = React.createRef();
|
|
1944
|
+
const templateData = {
|
|
1945
|
+
base: {
|
|
1946
|
+
'template-content': '<p>Base branch content</p>',
|
|
1947
|
+
"subject": 'Base branch subject',
|
|
1948
|
+
},
|
|
1949
|
+
};
|
|
1950
|
+
render(
|
|
1951
|
+
<IntlProvider locale="en" messages={{}}>
|
|
1952
|
+
<EmailHTMLEditor
|
|
1953
|
+
{...defaultProps}
|
|
1954
|
+
ref={ref}
|
|
1955
|
+
params={{ id: '123' }}
|
|
1956
|
+
templateData={templateData}
|
|
1957
|
+
Email={{
|
|
1958
|
+
templateDetails: { _id: '123' },
|
|
1959
|
+
getTemplateDetailsInProgress: false,
|
|
1960
|
+
fetchingCmsData: false,
|
|
1961
|
+
}}
|
|
1962
|
+
/>
|
|
1963
|
+
</IntlProvider>
|
|
1964
|
+
);
|
|
1965
|
+
await waitFor(() => {
|
|
1966
|
+
expect(ref.current).toBeTruthy();
|
|
1967
|
+
}, { timeout: 3000 });
|
|
1968
|
+
await waitFor(() => {
|
|
1969
|
+
const content = ref.current.getContentForPreview();
|
|
1970
|
+
const formData = ref.current.getFormDataForPreview();
|
|
1971
|
+
expect(content).toBe('<p>Base branch content</p>');
|
|
1972
|
+
expect(formData['template-subject']).toBe('Base branch subject');
|
|
1973
|
+
}, { timeout: 3000 });
|
|
1974
|
+
});
|
|
1975
|
+
|
|
1976
|
+
it('extracts content and subject from templateDataProp.versions.base branch with activeTab', async () => {
|
|
1977
|
+
const ref = React.createRef();
|
|
1978
|
+
const templateData = {
|
|
1979
|
+
versions: {
|
|
1980
|
+
base: {
|
|
1981
|
+
activeTab: 'en',
|
|
1982
|
+
en: {
|
|
1983
|
+
'template-content': '<p>Versions base en content</p>',
|
|
1984
|
+
"html_content": '<p>fallback html_content</p>',
|
|
1985
|
+
},
|
|
1986
|
+
subject: 'Versions base subject',
|
|
1987
|
+
emailSubject: 'Fallback email subject',
|
|
1988
|
+
},
|
|
1989
|
+
},
|
|
1990
|
+
};
|
|
1991
|
+
render(
|
|
1992
|
+
<IntlProvider locale="en" messages={{}}>
|
|
1993
|
+
<EmailHTMLEditor
|
|
1994
|
+
{...defaultProps}
|
|
1995
|
+
ref={ref}
|
|
1996
|
+
params={{ id: '123' }}
|
|
1997
|
+
templateData={templateData}
|
|
1998
|
+
Email={{
|
|
1999
|
+
templateDetails: { _id: '123' },
|
|
2000
|
+
getTemplateDetailsInProgress: false,
|
|
2001
|
+
fetchingCmsData: false,
|
|
2002
|
+
}}
|
|
2003
|
+
/>
|
|
2004
|
+
</IntlProvider>
|
|
2005
|
+
);
|
|
2006
|
+
await waitFor(() => {
|
|
2007
|
+
expect(ref.current).toBeTruthy();
|
|
2008
|
+
}, { timeout: 3000 });
|
|
2009
|
+
await waitFor(() => {
|
|
2010
|
+
const content = ref.current.getContentForPreview();
|
|
2011
|
+
const formData = ref.current.getFormDataForPreview();
|
|
2012
|
+
expect(content).toBe('<p>Versions base en content</p>');
|
|
2013
|
+
expect(formData['template-subject']).toBe('Versions base subject');
|
|
2014
|
+
}, { timeout: 3000 });
|
|
2015
|
+
});
|
|
2016
|
+
|
|
2017
|
+
it('extracts content and subject from flat templateDataProp (else branch)', async () => {
|
|
2018
|
+
const ref = React.createRef();
|
|
2019
|
+
const templateData = {
|
|
2020
|
+
'template-content': '<p>Flat content</p>',
|
|
2021
|
+
"emailSubject": 'Flat email subject',
|
|
2022
|
+
"html_content": '<p>flat html_content</p>',
|
|
2023
|
+
"subject": 'Flat subject',
|
|
2024
|
+
};
|
|
2025
|
+
render(
|
|
2026
|
+
<IntlProvider locale="en" messages={{}}>
|
|
2027
|
+
<EmailHTMLEditor
|
|
2028
|
+
{...defaultProps}
|
|
2029
|
+
ref={ref}
|
|
2030
|
+
params={{ id: '123' }}
|
|
2031
|
+
templateData={templateData}
|
|
2032
|
+
Email={{
|
|
2033
|
+
templateDetails: { _id: '123' },
|
|
2034
|
+
getTemplateDetailsInProgress: false,
|
|
2035
|
+
fetchingCmsData: false,
|
|
2036
|
+
}}
|
|
2037
|
+
/>
|
|
2038
|
+
</IntlProvider>
|
|
2039
|
+
);
|
|
2040
|
+
await waitFor(() => {
|
|
2041
|
+
expect(ref.current).toBeTruthy();
|
|
2042
|
+
}, { timeout: 3000 });
|
|
2043
|
+
await waitFor(() => {
|
|
2044
|
+
const content = ref.current.getContentForPreview();
|
|
2045
|
+
const formData = ref.current.getFormDataForPreview();
|
|
2046
|
+
expect(content).toBe('<p>Flat content</p>');
|
|
2047
|
+
expect(formData['template-subject']).toBe('Flat email subject');
|
|
2048
|
+
}, { timeout: 3000 });
|
|
2049
|
+
});
|
|
2050
|
+
});
|
|
2051
|
+
|
|
1900
2052
|
describe('setIsLoadingContent callback', () => {
|
|
1901
2053
|
it('should call setIsLoadingContent when uploaded content is available', async () => {
|
|
1902
2054
|
const setIsLoadingContent = jest.fn();
|
|
@@ -1933,6 +2085,37 @@ describe('EmailHTMLEditor', () => {
|
|
|
1933
2085
|
});
|
|
1934
2086
|
});
|
|
1935
2087
|
|
|
2088
|
+
describe('location.query and tagList (lines 127, 129, 185)', () => {
|
|
2089
|
+
it('should handle missing location.query (destructure from empty object)', () => {
|
|
2090
|
+
renderWithIntl({
|
|
2091
|
+
location: {},
|
|
2092
|
+
supportedTags: [],
|
|
2093
|
+
metaEntities: { tags: { standard: [{ name: 'standard.tag' }] } },
|
|
2094
|
+
});
|
|
2095
|
+
expect(screen.getByTestId('html-editor')).toBeInTheDocument();
|
|
2096
|
+
});
|
|
2097
|
+
|
|
2098
|
+
it('should handle null location (query defaults to {})', () => {
|
|
2099
|
+
renderWithIntl({
|
|
2100
|
+
location: null,
|
|
2101
|
+
supportedTags: [],
|
|
2102
|
+
metaEntities: { tags: { standard: [] } },
|
|
2103
|
+
});
|
|
2104
|
+
expect(screen.getByTestId('html-editor')).toBeInTheDocument();
|
|
2105
|
+
});
|
|
2106
|
+
|
|
2107
|
+
it('should use tagList from supportedTags when type=embedded, module=library and no getDefaultTags (line 129)', () => {
|
|
2108
|
+
const supportedTags = [{ name: 'custom.a' }, { name: 'custom.b' }];
|
|
2109
|
+
renderWithIntl({
|
|
2110
|
+
location: { query: { type: 'embedded', module: 'library' } },
|
|
2111
|
+
getDefaultTags: null,
|
|
2112
|
+
supportedTags,
|
|
2113
|
+
metaEntities: { tags: { standard: [{ name: 'standard.only' }] } },
|
|
2114
|
+
});
|
|
2115
|
+
expect(screen.getByTestId('html-editor')).toBeInTheDocument();
|
|
2116
|
+
});
|
|
2117
|
+
});
|
|
2118
|
+
|
|
1936
2119
|
describe('tags useMemo (lines 125-132)', () => {
|
|
1937
2120
|
it('should use supportedTags when in EMBEDDED mode with LIBRARY module and no getDefaultTags', () => {
|
|
1938
2121
|
const supportedTags = [{ name: 'custom.tag1' }, { name: 'custom.tag2' }];
|
|
@@ -2050,7 +2233,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
2050
2233
|
expect(formData).toBeDefined();
|
|
2051
2234
|
expect(formData['0']).toBeDefined();
|
|
2052
2235
|
expect(formData['0'].fr).toBeDefined(); // Uses base_language 'fr'
|
|
2053
|
-
expect(formData['0'].fr
|
|
2236
|
+
expect(formData['0'].fr.is_drag_drop).toBe(false);
|
|
2054
2237
|
expect(formData['0'].activeTab).toBe('fr');
|
|
2055
2238
|
expect(formData['0'].selectedLanguages).toEqual(['fr']);
|
|
2056
2239
|
expect(formData['0'].base).toBe(true);
|
|
@@ -517,4 +517,141 @@ describe('EmailWrapperView', () => {
|
|
|
517
517
|
// Should handle gracefully if ref methods don't exist
|
|
518
518
|
});
|
|
519
519
|
});
|
|
520
|
+
|
|
521
|
+
describe('Issue 1: Beefree template should open in Beefree editor in Edit mode', () => {
|
|
522
|
+
beforeEach(() => {
|
|
523
|
+
const { hasSupportCKEditor } = require('../../../../utils/common');
|
|
524
|
+
hasSupportCKEditor.mockReturnValue(false); // New flow
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('should render Email component (not HTML editor) when editing Beefree template', () => {
|
|
528
|
+
const emailPropsWithBEE = {
|
|
529
|
+
...defaultProps.emailProps,
|
|
530
|
+
editor: 'BEE',
|
|
531
|
+
selectedEditorMode: null,
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
renderWithIntl({
|
|
535
|
+
step: STEPS.CREATE_TEMPLATE_CONTENT,
|
|
536
|
+
isShowEmailCreate: true,
|
|
537
|
+
emailProps: emailPropsWithBEE,
|
|
538
|
+
emailCreateMode: EMAIL_CREATE_MODES.DRAG_DROP,
|
|
539
|
+
params: { id: 'beefree-template-1' },
|
|
540
|
+
location: { query: {}, pathname: '/email/edit/beefree-template-1' },
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
// Should render Email component (BEE editor), not HTML editor
|
|
544
|
+
expect(screen.queryByTestId('email-html-editor')).not.toBeInTheDocument();
|
|
545
|
+
// Email component would be rendered (we're using EmailWithoutSaga which doesn't have a testid)
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it('should render HTML editor when editing non-Beefree template', () => {
|
|
549
|
+
const emailPropsWithHTML = {
|
|
550
|
+
...defaultProps.emailProps,
|
|
551
|
+
editor: 'HTML',
|
|
552
|
+
selectedEditorMode: EMAIL_CREATE_MODES.HTML_EDITOR,
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
renderWithIntl({
|
|
556
|
+
step: STEPS.CREATE_TEMPLATE_CONTENT,
|
|
557
|
+
isShowEmailCreate: true,
|
|
558
|
+
emailProps: emailPropsWithHTML,
|
|
559
|
+
emailCreateMode: EMAIL_CREATE_MODES.HTML_EDITOR,
|
|
560
|
+
params: { id: 'html-template-1' },
|
|
561
|
+
location: { query: {}, pathname: '/email/edit/html-template-1' },
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
// Should render HTML editor for non-Beefree template
|
|
565
|
+
expect(screen.getByTestId('email-html-editor')).toBeInTheDocument();
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('should render Email component when emailCreateMode is DRAG_DROP in create mode', () => {
|
|
569
|
+
const emailPropsWithBEE = {
|
|
570
|
+
...defaultProps.emailProps,
|
|
571
|
+
editor: 'BEE',
|
|
572
|
+
selectedEditorMode: null,
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
renderWithIntl({
|
|
576
|
+
step: STEPS.CREATE_TEMPLATE_CONTENT,
|
|
577
|
+
isShowEmailCreate: true,
|
|
578
|
+
emailProps: emailPropsWithBEE,
|
|
579
|
+
emailCreateMode: EMAIL_CREATE_MODES.DRAG_DROP,
|
|
580
|
+
params: {},
|
|
581
|
+
location: { query: {}, pathname: '/email/create' },
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// Should render Email component (BEE editor), not HTML editor
|
|
585
|
+
expect(screen.queryByTestId('email-html-editor')).not.toBeInTheDocument();
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
describe('Issue 2: Upload Zip should open in CKEditor when SUPPORT_CK_EDITOR is enabled', () => {
|
|
590
|
+
beforeEach(() => {
|
|
591
|
+
const { hasSupportCKEditor } = require('../../../../utils/common');
|
|
592
|
+
hasSupportCKEditor.mockReturnValue(true); // Legacy flow
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it('should render Email component (CKEditor) for UPLOAD mode in create flow', () => {
|
|
596
|
+
const emailPropsForCKEditor = {
|
|
597
|
+
...defaultProps.emailProps,
|
|
598
|
+
editor: undefined, // Default CKEditor
|
|
599
|
+
selectedEditorMode: null,
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
renderWithIntl({
|
|
603
|
+
step: STEPS.CREATE_TEMPLATE_CONTENT,
|
|
604
|
+
isShowEmailCreate: true,
|
|
605
|
+
emailProps: emailPropsForCKEditor,
|
|
606
|
+
emailCreateMode: EMAIL_CREATE_MODES.UPLOAD,
|
|
607
|
+
EmailLayout: { html: '<p>Uploaded content</p>' },
|
|
608
|
+
params: {},
|
|
609
|
+
location: { query: {}, pathname: '/email/create' },
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// Should render Email component (CKEditor), NOT HTML editor
|
|
613
|
+
expect(screen.queryByTestId('email-html-editor')).not.toBeInTheDocument();
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
it('should render Email component (CKEditor) for UPLOAD mode in edit flow', () => {
|
|
617
|
+
const emailPropsForCKEditor = {
|
|
618
|
+
...defaultProps.emailProps,
|
|
619
|
+
editor: undefined,
|
|
620
|
+
selectedEditorMode: null,
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
renderWithIntl({
|
|
624
|
+
step: STEPS.CREATE_TEMPLATE_CONTENT,
|
|
625
|
+
isShowEmailCreate: true,
|
|
626
|
+
emailProps: emailPropsForCKEditor,
|
|
627
|
+
emailCreateMode: EMAIL_CREATE_MODES.UPLOAD,
|
|
628
|
+
EmailLayout: { html: '<p>Edited uploaded content</p>' },
|
|
629
|
+
params: { id: 'uploaded-template-1' },
|
|
630
|
+
location: { query: {}, pathname: '/email/edit/uploaded-template-1' },
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// Should render Email component (CKEditor), NOT HTML editor
|
|
634
|
+
expect(screen.queryByTestId('email-html-editor')).not.toBeInTheDocument();
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
it('should NOT render HTML editor for EDITOR mode when SUPPORT_CK_EDITOR is enabled', () => {
|
|
638
|
+
const emailPropsForCKEditor = {
|
|
639
|
+
...defaultProps.emailProps,
|
|
640
|
+
editor: undefined,
|
|
641
|
+
selectedEditorMode: null,
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
renderWithIntl({
|
|
645
|
+
step: STEPS.CREATE_TEMPLATE_CONTENT,
|
|
646
|
+
isShowEmailCreate: true,
|
|
647
|
+
emailProps: emailPropsForCKEditor,
|
|
648
|
+
emailCreateMode: EMAIL_CREATE_MODES.EDITOR,
|
|
649
|
+
params: {},
|
|
650
|
+
location: { query: {}, pathname: '/email/create' },
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// Should render Email component (CKEditor), NOT HTML editor
|
|
654
|
+
expect(screen.queryByTestId('email-html-editor')).not.toBeInTheDocument();
|
|
655
|
+
});
|
|
656
|
+
});
|
|
520
657
|
});
|