@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.
Files changed (26) hide show
  1. package/package.json +1 -1
  2. package/utils/tests/imageUrlUpload.test.js +298 -0
  3. package/v2Containers/CreativesContainer/SlideBoxContent.js +2 -0
  4. package/v2Containers/CreativesContainer/SlideBoxFooter.js +5 -2
  5. package/v2Containers/CreativesContainer/index.js +10 -6
  6. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +165 -41
  7. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +6 -0
  8. package/v2Containers/Email/index.js +75 -9
  9. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +14 -8
  10. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +6 -2
  11. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +189 -6
  12. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +137 -0
  13. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +7 -3
  14. package/v2Containers/EmailWrapper/index.js +3 -0
  15. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +26 -0
  16. package/v2Containers/Facebook/Advertisement/index.js +1 -1
  17. package/v2Containers/Line/Container/_lineCreate.scss +1 -0
  18. package/v2Containers/Line/Container/style.js +1 -1
  19. package/v2Containers/MobilePush/Edit/index.js +6 -5
  20. package/v2Containers/SmsTrai/Create/index.scss +1 -1
  21. package/v2Containers/SmsTrai/Edit/index.js +9 -3
  22. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +11682 -86
  23. package/v2Containers/SmsTrai/Edit/tests/index.test.js +5 -0
  24. package/v2Containers/Viber/index.js +7 -0
  25. package/v2Containers/Viber/index.scss +4 -1
  26. 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 ,hasLiquidSupportFeature} from '../../utils/common';
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
- this.setState({ content: (this.props.Templates.selectedEmailLayout ? this.props.Templates.selectedEmailLayout : ''), formData});
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
- //only for ck editor
317
- //when create button is clicked in full mode
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.startValidation(true);
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
- if (!_.isEmpty(nextProps.Email.CmsSettings) && !_.isEqual(this.props.Email.CmsSettings, nextProps.Email.CmsSettings) && !_.isEmpty(this.state.schema)) {
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
- this.props.templatesActions.resetUploadData();
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 = templateDataProp?._id || params?.id || location?.query?.id || location?.params?.id
180
- || location?.pathname?.match(/\/edit\/([^/]+)/)?.[1];
181
- const isEditMode = !!currentTemplateId || !!hasParamsId;
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 (if mandatory)
689
- // Check if unsubscribe tag is mandatory and if it exists in content
690
- if (isEmailUnsubscribeTagMandatory() && moduleType === OUTBOUND) {
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 mandatory
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
- // Legacy flow: use HTML editor for UPLOAD so zip/HTML content (EmailLayout) is shown; use Email component for EDITOR
206
- isHTMLEditorMode = emailCreateMode === EMAIL_CREATE_MODES.UPLOAD;
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 tag is mandatory and missing', async () => {
980
- isEmailUnsubscribeTagMandatory.mockReturnValue(true);
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(true);
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['is_drag_drop']).toBe(false);
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
  });