@capillarytech/creatives-library 8.0.213 → 8.0.214-beta.0

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 (37) hide show
  1. package/HOW_BEE_EDITOR_WORKS.md +375 -0
  2. package/constants/unified.js +1 -0
  3. package/package.json +1 -1
  4. package/services/api.js +5 -0
  5. package/utils/common.js +6 -1
  6. package/v2Components/CapTagList/index.js +2 -1
  7. package/v2Components/CapTagListWithInput/index.js +5 -1
  8. package/v2Components/CapTagListWithInput/messages.js +1 -1
  9. package/v2Components/ErrorInfoNote/style.scss +1 -1
  10. package/v2Components/HtmlEditor/HTMLEditor.js +86 -14
  11. package/v2Components/HtmlEditor/_htmlEditor.scss +4 -4
  12. package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
  13. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +107 -96
  14. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +68 -92
  15. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  16. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  17. package/v2Containers/CreativesContainer/SlideBoxContent.js +85 -35
  18. package/v2Containers/CreativesContainer/SlideBoxFooter.js +9 -3
  19. package/v2Containers/CreativesContainer/index.js +107 -35
  20. package/v2Containers/CreativesContainer/messages.js +4 -0
  21. package/v2Containers/Email/actions.js +7 -0
  22. package/v2Containers/Email/constants.js +5 -1
  23. package/v2Containers/Email/index.js +13 -0
  24. package/v2Containers/Email/messages.js +32 -0
  25. package/v2Containers/Email/reducer.js +12 -1
  26. package/v2Containers/Email/sagas.js +17 -0
  27. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1005 -0
  28. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +193 -7
  29. package/v2Containers/EmailWrapper/constants.js +2 -0
  30. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +470 -71
  31. package/v2Containers/EmailWrapper/index.js +102 -23
  32. package/v2Containers/EmailWrapper/messages.js +61 -1
  33. package/v2Containers/EmailWrapper/tests/EmailHTMLEditor.test.js +177 -0
  34. package/v2Containers/EmailWrapper/tests/EmailHTMLEditorValidation.test.js +90 -0
  35. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +49 -49
  36. package/v2Containers/TagList/index.js +2 -0
  37. package/v2Containers/Templates/index.js +5 -0
@@ -9,6 +9,7 @@ import { connect } from 'react-redux';
9
9
  import { injectIntl, intlShape } from 'react-intl';
10
10
  import { createStructuredSelector } from 'reselect';
11
11
  import { bindActionCreators } from 'redux';
12
+ import { compose } from 'redux';
12
13
  import { UserIsAuthenticated } from '../../utils/authWrapper';
13
14
  import {
14
15
  selectEmailLayout,
@@ -18,10 +19,17 @@ import {
18
19
  selectCmsTemplatesLoader,
19
20
  } from '../Templates/selectors';
20
21
  import * as templatesActionsCreators from '../Templates/actions';
21
- import { selectCurrentOrgDetails } from "../../v2Containers/Cap/selectors";
22
+ import { selectCurrentOrgDetails, makeSelectMetaEntities, isLoadingMetaEntities, setInjectedTags, selectLiquidStateDetails } from "../../v2Containers/Cap/selectors";
23
+ import { makeSelectEmail, selectLoadingStatus, makeSelectFetchingCmsData } from '../Email/selectors';
24
+ import * as emailActionsCreators from '../Email/actions';
25
+ import * as globalActions from '../Cap/actions';
22
26
  import EmailWrapperView from './components/EmailWrapperView';
23
27
  import useEmailWrapper from './hooks/useEmailWrapper';
24
28
  import HTMLEditorTesting from './components/HTMLEditorTesting';
29
+ import injectReducer from '../../utils/injectReducer';
30
+ import injectSaga from '../../utils/injectSaga';
31
+ import v2EmailReducer from '../Email/reducer';
32
+ import { v2EmailSagas } from '../Email/sagas';
25
33
 
26
34
 
27
35
  const EmailWrapper = (props) => {
@@ -64,6 +72,18 @@ const EmailWrapper = (props) => {
64
72
  handleTestAndPreview,
65
73
  handleCloseTestAndPreview,
66
74
  isTestAndPreviewMode,
75
+ location,
76
+ emailActions,
77
+ Email,
78
+ metaEntities,
79
+ loadingTags,
80
+ injectedTags,
81
+ globalActions: globalActionsProp,
82
+ templateData,
83
+ params,
84
+ fetchingLiquidTags,
85
+ createTemplateInProgress,
86
+ fetchingCmsData,
67
87
  } = props;
68
88
 
69
89
  // Pass destructured props to the custom hook
@@ -76,9 +96,12 @@ const EmailWrapper = (props) => {
76
96
  emailProps,
77
97
  cmsTemplatesProps,
78
98
  uploadButtonLabel,
99
+ showNextButton,
100
+ isNextButtonEnabled,
79
101
  onTemplateNameChange,
80
102
  onChange,
81
103
  useFileUpload,
104
+ handleNextClick,
82
105
  } = useEmailWrapper({
83
106
  emailCreateMode,
84
107
  step,
@@ -117,32 +140,68 @@ const EmailWrapper = (props) => {
117
140
  handleTestAndPreview,
118
141
  handleCloseTestAndPreview,
119
142
  isTestAndPreviewMode,
143
+ location,
144
+ emailActions,
145
+ Email,
146
+ templateData,
147
+ params,
120
148
  });
121
149
 
122
150
  // Render using the presentation component with data from the hook
123
151
  return (
124
152
  <div>
125
153
  <EmailWrapperView
126
- isUploading={isUploading}
127
- emailCreateMode={emailCreateMode}
128
- step={step}
129
- isFullMode={isFullMode}
130
- templateName={templateName}
131
- onTemplateNameChange={onTemplateNameChange}
132
- isTemplateNameEmpty={isTemplateNameEmpty}
133
- modes={modes}
134
- onChange={onChange}
135
- EmailLayout={EmailLayout}
136
- modeContent={modeContent}
137
- useFileUpload={useFileUpload}
138
- uploadButtonLabel={uploadButtonLabel}
139
- isShowEmailCreate={isShowEmailCreate}
140
- emailProps={emailProps}
141
- cmsTemplatesProps={cmsTemplatesProps}
142
- onPreviewContentClicked={onPreviewContentClicked}
143
- onTestContentClicked={onTestContentClicked}
144
- editor={editor}
145
- />
154
+ isUploading={isUploading}
155
+ emailCreateMode={emailCreateMode}
156
+ step={step}
157
+ isFullMode={isFullMode}
158
+ templateName={templateName}
159
+ onTemplateNameChange={onTemplateNameChange}
160
+ isTemplateNameEmpty={isTemplateNameEmpty}
161
+ modes={modes}
162
+ onChange={onChange}
163
+ EmailLayout={EmailLayout}
164
+ modeContent={modeContent}
165
+ useFileUpload={useFileUpload}
166
+ uploadButtonLabel={uploadButtonLabel}
167
+ isShowEmailCreate={isShowEmailCreate}
168
+ emailProps={emailProps}
169
+ cmsTemplatesProps={cmsTemplatesProps}
170
+ onPreviewContentClicked={onPreviewContentClicked}
171
+ onTestContentClicked={onTestContentClicked}
172
+ editor={editor}
173
+ // New flow props - only used when supportCKEditor is false
174
+ // These props are needed for EmailHTMLEditor component (tag loading, validation, etc.)
175
+ metaEntities={metaEntities}
176
+ loadingTags={loadingTags}
177
+ injectedTags={injectedTags}
178
+ globalActions={globalActionsProp}
179
+ supportedTags={props.supportedTags}
180
+ getDefaultTags={type}
181
+ location={location}
182
+ currentOrgDetails={currentOrgDetails}
183
+ onValidationFail={onValidationFail}
184
+ forwardedTags={forwardedTags}
185
+ selectedOfferDetails={selectedOfferDetails}
186
+ eventContextTags={eventContextTags}
187
+ getFormdata={getFormdata}
188
+ isGetFormData={isGetFormData}
189
+ getLiquidTags={globalActionsProp?.getLiquidTags}
190
+ showLiquidErrorInFooter={showLiquidErrorInFooter}
191
+ intl={intl}
192
+ emailActions={emailActions}
193
+ showTestAndPreviewSlidebox={showTestAndPreviewSlidebox}
194
+ handleTestAndPreview={handleTestAndPreview}
195
+ handleCloseTestAndPreview={handleCloseTestAndPreview}
196
+ Email={Email}
197
+ setIsLoadingContent={setIsLoadingContent}
198
+ templateData={templateData}
199
+ params={params}
200
+ showTemplateName={showTemplateName}
201
+ fetchingLiquidTags={fetchingLiquidTags}
202
+ createTemplateInProgress={createTemplateInProgress}
203
+ fetchingCmsData={fetchingCmsData}
204
+ />
146
205
 
147
206
  {/* HTMLEditor Testing Component - Console Controlled */}
148
207
  <HTMLEditorTesting />
@@ -159,7 +218,7 @@ EmailWrapper.propTypes = {
159
218
  templatesActions: PropTypes.object,
160
219
  CmsTemplates: PropTypes.arrayOf(PropTypes.object),
161
220
  SelectedEdmDefaultTemplate: PropTypes.object,
162
- step: PropTypes.string,
221
+ step: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
163
222
  showNextStep: PropTypes.func,
164
223
  getFormdata: PropTypes.func,
165
224
  intl: intlShape,
@@ -187,6 +246,7 @@ EmailWrapper.propTypes = {
187
246
  handleTestAndPreview: PropTypes.func,
188
247
  handleCloseTestAndPreview: PropTypes.func,
189
248
  isTestAndPreviewMode: PropTypes.bool,
249
+ location: PropTypes.object,
190
250
  };
191
251
 
192
252
  const mapStateToProps = createStructuredSelector({
@@ -196,12 +256,31 @@ const mapStateToProps = createStructuredSelector({
196
256
  isUploading: uploadSelector(),
197
257
  cmsTemplatesLoader: selectCmsTemplatesLoader(),
198
258
  currentOrgDetails: selectCurrentOrgDetails(),
259
+ Email: makeSelectEmail(),
260
+ metaEntities: makeSelectMetaEntities(),
261
+ loadingTags: isLoadingMetaEntities(),
262
+ injectedTags: setInjectedTags(),
263
+ fetchingLiquidTags: selectLiquidStateDetails(),
264
+ createTemplateInProgress: selectLoadingStatus(),
265
+ fetchingCmsData: makeSelectFetchingCmsData(),
199
266
  });
200
267
 
201
268
  function mapDispatchToProps(dispatch) {
202
269
  return {
203
270
  templatesActions: bindActionCreators(templatesActionsCreators, dispatch),
271
+ emailActions: bindActionCreators(emailActionsCreators, dispatch),
272
+ globalActions: bindActionCreators(globalActions, dispatch),
204
273
  };
205
274
  }
206
275
 
207
- export default UserIsAuthenticated(connect(mapStateToProps, mapDispatchToProps)(injectIntl(EmailWrapper)));
276
+ // Inject Email reducer and saga so that getCmsSetting action can be handled
277
+ const withReducer = injectReducer({ key: 'email', reducer: v2EmailReducer });
278
+ const withEmailSaga = injectSaga({ key: 'email', saga: v2EmailSagas });
279
+
280
+ const ConnectedEmailWrapper = connect(mapStateToProps, mapDispatchToProps)(injectIntl(EmailWrapper));
281
+
282
+ export default compose(
283
+ UserIsAuthenticated,
284
+ withReducer,
285
+ withEmailSaga,
286
+ )(ConnectedEmailWrapper);
@@ -60,6 +60,66 @@ export default defineMessages({
60
60
  },
61
61
  emptyTemplateName: {
62
62
  id: `creatives.containersV2.EmailWrapper.emptyTemplateName`,
63
- defaultMessage: `Please enter template name.`
63
+ defaultMessage: `Please enter template name.`,
64
+ },
65
+ htmlEditorTitle: {
66
+ id: 'creatives.EmailWrapper.htmlEditorTitle',
67
+ defaultMessage: 'HTML editor',
68
+ },
69
+ htmlEditorDesc: {
70
+ id: 'creatives.EmailWrapper.htmlEditorDesc',
71
+ defaultMessage: 'Use a basic HTML editor to write and format your content. Suitable if you are familiar with HTML.',
72
+ },
73
+ dragDropEditorTitle: {
74
+ id: 'creatives.EmailWrapper.dragDropEditorTitle',
75
+ defaultMessage: 'Drag & drop editor',
76
+ },
77
+ dragDropEditorDesc: {
78
+ id: 'creatives.EmailWrapper.dragDropEditorDesc',
79
+ defaultMessage: 'Create your content visually by dragging blocks — no coding needed. Great for quick, easy designs.',
80
+ },
81
+ uploadZipTitle: {
82
+ id: 'creatives.EmailWrapper.uploadZipTitle',
83
+ defaultMessage: 'Upload zip file',
84
+ },
85
+ uploadZipDesc: {
86
+ id: 'creatives.EmailWrapper.uploadZipDesc',
87
+ defaultMessage: 'Upload a ZIP containing your custom HTML, images, and assets. Ideal if your content is already built.',
88
+ },
89
+ next: {
90
+ id: 'creatives.EmailWrapper.next',
91
+ defaultMessage: 'Next',
92
+ },
93
+ beeEditorDisabledTooltip: {
94
+ id: 'creatives.EmailWrapper.beeEditorDisabledTooltip',
95
+ defaultMessage: 'Please get the drag & drop editor enabled for your brand',
96
+ },
97
+ emailContent: {
98
+ id: 'creatives.containersV2.Email.emailContent',
99
+ defaultMessage: 'Email content',
100
+ },
101
+ create: {
102
+ id: 'creatives.containersV2.EmailWrapper.create',
103
+ defaultMessage: 'Create',
104
+ },
105
+ update: {
106
+ id: 'creatives.containersV2.EmailWrapper.update',
107
+ defaultMessage: 'Update',
108
+ },
109
+ previewAndTest: {
110
+ id: 'creatives.containersV2.EmailWrapper.previewAndTest',
111
+ defaultMessage: 'Preview and Test',
112
+ },
113
+ required: {
114
+ id: 'creatives.containersV2.EmailWrapper.required',
115
+ defaultMessage: 'Required',
116
+ },
117
+ subject: {
118
+ id: 'creatives.containersV2.EmailWrapper.subject',
119
+ defaultMessage: 'Subject',
120
+ },
121
+ enterEmailSubject: {
122
+ id: 'creatives.containersV2.EmailWrapper.enterEmailSubject',
123
+ defaultMessage: 'Enter Email Subject',
64
124
  },
65
125
  });
@@ -0,0 +1,177 @@
1
+ import React from 'react';
2
+ import { render, fireEvent, screen, waitFor } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { IntlProvider } from 'react-intl';
5
+ import EmailHTMLEditor from '../components/EmailHTMLEditor';
6
+
7
+ // Mock dependencies
8
+ jest.mock('../messages', () => ({
9
+ create: { id: 'create', defaultMessage: 'Create' },
10
+ update: { id: 'update', defaultMessage: 'Update' },
11
+ previewAndTest: { id: 'previewAndTest', defaultMessage: 'Preview and Test' },
12
+ required: { id: 'required', defaultMessage: 'Required' },
13
+ subject: { id: 'subject', defaultMessage: 'Subject' },
14
+ emailContent: { id: 'emailContent', defaultMessage: 'Email Content' },
15
+ enterEmailSubject: { id: 'enterEmailSubject', defaultMessage: 'Enter Email Subject' },
16
+ }));
17
+ jest.mock('../../../v2Components/HtmlEditor', () => ({
18
+ __esModule: true,
19
+ default: ({ onContentChange, initialContent, onSave }) => (
20
+ <div data-testid="html-editor">
21
+ <textarea
22
+ data-testid="html-editor-textarea"
23
+ value={initialContent}
24
+ onChange={(e) => onContentChange(e.target.value)}
25
+ />
26
+ <button data-testid="html-editor-save" onClick={onSave}>Save in Editor</button>
27
+ </div>
28
+ ),
29
+ }));
30
+
31
+ jest.mock('../../../v2Components/CapTagListWithInput', () => ({
32
+ __esModule: true,
33
+ default: ({ inputOnChange, inputValue, inputErrorMessage, onTagSelect }) => (
34
+ <div data-testid="cap-tag-list-input">
35
+ <input
36
+ data-testid="subject-input"
37
+ value={inputValue}
38
+ onChange={inputOnChange}
39
+ />
40
+ {inputErrorMessage && <div data-testid="subject-error">{inputErrorMessage}</div>}
41
+ <button data-testid="add-tag-btn" onClick={() => onTagSelect('FirstName')}>Add Tag</button>
42
+ </div>
43
+ ),
44
+ }));
45
+
46
+ jest.mock('@capillarytech/cap-ui-library/CapNotification', () => ({
47
+ __esModule: true,
48
+ default: ({ message, description }) => (
49
+ <div data-testid="cap-notification">
50
+ {message}
51
+ {description}
52
+ </div>
53
+ ),
54
+ }));
55
+
56
+ jest.mock('@capillarytech/cap-ui-library/CapSpin', () => ({
57
+ __esModule: true,
58
+ default: ({ children, spinning }) => (
59
+ <div data-testid="cap-spin" data-spinning={spinning}>
60
+ {children}
61
+ </div>
62
+ ),
63
+ }));
64
+
65
+ jest.mock('@capillarytech/cap-ui-library/CapButton', () => ({
66
+ __esModule: true,
67
+ default: ({ children, onClick, type }) => (
68
+ <button data-testid={`cap-button-${type}`} onClick={onClick}>
69
+ {children}
70
+ </button>
71
+ ),
72
+ }));
73
+
74
+ describe('EmailHTMLEditor', () => {
75
+ const mockOnSave = jest.fn();
76
+ const mockOnPreview = jest.fn();
77
+ const mockOnSubjectChange = jest.fn();
78
+ const mockOnContentChange = jest.fn();
79
+
80
+ const defaultProps = {
81
+ intl: { formatMessage: ({ id, defaultMessage }) => defaultMessage || id },
82
+ onSave: mockOnSave,
83
+ onPreview: mockOnPreview,
84
+ onSubjectChange: mockOnSubjectChange,
85
+ onContentChange: mockOnContentChange,
86
+ loadingTags: false,
87
+ fetchingLiquidTags: false,
88
+ createTemplateInProgress: false,
89
+ fetchingCmsData: false,
90
+ };
91
+
92
+ const renderComponent = (props = {}) => {
93
+ return render(
94
+ <IntlProvider locale="en" messages={{}}>
95
+ <EmailHTMLEditor {...defaultProps} {...props} />
96
+ </IntlProvider>
97
+ );
98
+ };
99
+
100
+ beforeEach(() => {
101
+ jest.clearAllMocks();
102
+ });
103
+
104
+ it('renders correctly', () => {
105
+ renderComponent();
106
+ expect(screen.getByTestId('html-editor')).toBeInTheDocument();
107
+ expect(screen.getByTestId('cap-tag-list-input')).toBeInTheDocument();
108
+ expect(screen.getByTestId('cap-button-primary')).toHaveTextContent('Create');
109
+ expect(screen.getByTestId('cap-button-secondary')).toHaveTextContent('Preview and Test');
110
+ });
111
+
112
+ it('renders Update button in edit mode', () => {
113
+ renderComponent({ isEditMode: true });
114
+ expect(screen.getByTestId('cap-button-primary')).toHaveTextContent('Update');
115
+ });
116
+
117
+ it('updates subject state on change', () => {
118
+ renderComponent();
119
+ const input = screen.getByTestId('subject-input');
120
+ fireEvent.change(input, { target: { value: 'New Subject' } });
121
+ expect(mockOnSubjectChange).toHaveBeenCalledWith('New Subject');
122
+ expect(input.value).toBe('New Subject');
123
+ });
124
+
125
+ it('updates content state on change', () => {
126
+ renderComponent();
127
+ const textarea = screen.getByTestId('html-editor-textarea');
128
+ fireEvent.change(textarea, { target: { value: '<p>New Content</p>' } });
129
+ expect(mockOnContentChange).toHaveBeenCalledWith('<p>New Content</p>');
130
+ });
131
+
132
+ it('validates empty subject on save', () => {
133
+ renderComponent();
134
+ const saveBtn = screen.getByTestId('cap-button-primary');
135
+ fireEvent.click(saveBtn);
136
+
137
+ expect(mockOnSave).not.toHaveBeenCalled();
138
+ // Expect internal error state to be set (mocked CapTagListWithInput displays it)
139
+ // Note: The mock implementation displays inputErrorMessage prop.
140
+ // EmailHTMLEditor passes `subjectError || internalSubjectError`.
141
+ // We need to wait for state update if necessary, but fireEvent is synchronous for state updates in tests usually.
142
+ expect(screen.getByTestId('subject-error')).toBeInTheDocument();
143
+ });
144
+
145
+ it('calls onSave with valid data', () => {
146
+ renderComponent({ initialSubject: 'Valid Subject', initialContent: '<p>Valid Content</p>' });
147
+ const saveBtn = screen.getByTestId('cap-button-primary');
148
+ fireEvent.click(saveBtn);
149
+
150
+ expect(mockOnSave).toHaveBeenCalledWith({
151
+ subject: 'Valid Subject',
152
+ content: '<p>Valid Content</p>',
153
+ });
154
+ });
155
+
156
+ it('calls onPreview with valid data', () => {
157
+ renderComponent({ initialSubject: 'Subject', initialContent: 'Content' });
158
+ const previewBtn = screen.getByTestId('cap-button-secondary');
159
+ fireEvent.click(previewBtn);
160
+
161
+ expect(mockOnPreview).toHaveBeenCalledWith({
162
+ subject: 'Subject',
163
+ content: 'Content',
164
+ });
165
+ });
166
+
167
+ it('handles tag insertion into subject', () => {
168
+ renderComponent({ initialSubject: 'Hello ' });
169
+ const addTagBtn = screen.getByTestId('add-tag-btn');
170
+ fireEvent.click(addTagBtn);
171
+
172
+ // Mock implementation appends tag
173
+ // The component logic handles insertion at cursor or append
174
+ // Here we just check if onSubjectChange was called with appended tag
175
+ expect(mockOnSubjectChange).toHaveBeenCalledWith('Hello {{FirstName}}');
176
+ });
177
+ });
@@ -0,0 +1,90 @@
1
+ import React from 'react';
2
+ import { render, waitFor } from '@testing-library/react';
3
+ import { IntlProvider } from 'react-intl';
4
+ import EmailHTMLEditor from '../components/EmailHTMLEditor';
5
+
6
+ // Mock dependencies
7
+ jest.mock('../../../v2Components/HtmlEditor', () => () => <div data-testid="html-editor" />);
8
+ jest.mock('../../../v2Components/CapTagListWithInput', () => () => <div data-testid="cap-tag-list" />);
9
+ jest.mock('@capillarytech/cap-ui-library/CapSpin', () => ({ children }) => <div>{children}</div>);
10
+ jest.mock('@capillarytech/cap-ui-library/CapRow', () => ({ children }) => <div>{children}</div>);
11
+ jest.mock('@capillarytech/cap-ui-library/CapColumn', () => ({ children }) => <div>{children}</div>);
12
+ jest.mock('@capillarytech/cap-ui-library/CapNotification', () => () => <div />);
13
+ jest.mock('../../../utils/commonUtils', () => ({
14
+ validateLiquidTemplateContent: jest.fn(),
15
+ hasLiquidSupportFeature: jest.fn(() => true),
16
+ }));
17
+ jest.mock('../../../utils/common', () => ({
18
+ hasLiquidSupportFeature: jest.fn(() => true),
19
+ }));
20
+
21
+ describe('EmailHTMLEditor Validation', () => {
22
+ const mockOnValidationFail = jest.fn();
23
+ const mockGetFormdata = jest.fn();
24
+ const defaultProps = {
25
+ intl: { formatMessage: ({ defaultMessage }) => defaultMessage },
26
+ onValidationFail: mockOnValidationFail,
27
+ isGetFormData: false,
28
+ getFormdata: mockGetFormdata,
29
+ location: {},
30
+ Email: {},
31
+ emailActions: {},
32
+ currentOrgDetails: {},
33
+ };
34
+
35
+ beforeEach(() => {
36
+ jest.clearAllMocks();
37
+ });
38
+
39
+ it('calls onValidationFail when subject is empty and save is triggered', async () => {
40
+ const { rerender } = render(
41
+ <IntlProvider locale="en">
42
+ <EmailHTMLEditor {...defaultProps} />
43
+ </IntlProvider>
44
+ );
45
+
46
+ // Trigger save by setting isGetFormData to true
47
+ rerender(
48
+ <IntlProvider locale="en">
49
+ <EmailHTMLEditor {...defaultProps} isGetFormData={true} />
50
+ </IntlProvider>
51
+ );
52
+
53
+ await waitFor(() => {
54
+ expect(mockOnValidationFail).toHaveBeenCalled();
55
+ });
56
+ });
57
+
58
+ it('calls onValidationFail when liquid validation fails', async () => {
59
+ const mockGetLiquidTags = jest.fn();
60
+ // Mock validateLiquidTemplateContent to trigger onError
61
+ const { validateLiquidTemplateContent } = require('../../../utils/commonUtils');
62
+ validateLiquidTemplateContent.mockImplementation(({ onError }) => {
63
+ onError({ standardErrors: ['Error'], liquidErrors: [] });
64
+ });
65
+
66
+ const { rerender } = render(
67
+ <IntlProvider locale="en">
68
+ <EmailHTMLEditor
69
+ {...defaultProps}
70
+ getLiquidTags={mockGetLiquidTags}
71
+ />
72
+ </IntlProvider>
73
+ );
74
+
75
+ // Trigger save
76
+ rerender(
77
+ <IntlProvider locale="en">
78
+ <EmailHTMLEditor
79
+ {...defaultProps}
80
+ getLiquidTags={mockGetLiquidTags}
81
+ isGetFormData={true}
82
+ />
83
+ </IntlProvider>
84
+ );
85
+
86
+ await waitFor(() => {
87
+ expect(mockOnValidationFail).toHaveBeenCalled();
88
+ });
89
+ });
90
+ });