@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.
- package/HOW_BEE_EDITOR_WORKS.md +375 -0
- package/constants/unified.js +1 -0
- package/package.json +1 -1
- package/services/api.js +5 -0
- package/utils/common.js +6 -1
- package/v2Components/CapTagList/index.js +2 -1
- package/v2Components/CapTagListWithInput/index.js +5 -1
- package/v2Components/CapTagListWithInput/messages.js +1 -1
- package/v2Components/ErrorInfoNote/style.scss +1 -1
- package/v2Components/HtmlEditor/HTMLEditor.js +86 -14
- package/v2Components/HtmlEditor/_htmlEditor.scss +4 -4
- package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +107 -96
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +68 -92
- package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
- package/v2Containers/CreativesContainer/SlideBoxContent.js +85 -35
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +9 -3
- package/v2Containers/CreativesContainer/index.js +107 -35
- package/v2Containers/CreativesContainer/messages.js +4 -0
- package/v2Containers/Email/actions.js +7 -0
- package/v2Containers/Email/constants.js +5 -1
- package/v2Containers/Email/index.js +13 -0
- package/v2Containers/Email/messages.js +32 -0
- package/v2Containers/Email/reducer.js +12 -1
- package/v2Containers/Email/sagas.js +17 -0
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1005 -0
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +193 -7
- package/v2Containers/EmailWrapper/constants.js +2 -0
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +470 -71
- package/v2Containers/EmailWrapper/index.js +102 -23
- package/v2Containers/EmailWrapper/messages.js +61 -1
- package/v2Containers/EmailWrapper/tests/EmailHTMLEditor.test.js +177 -0
- package/v2Containers/EmailWrapper/tests/EmailHTMLEditorValidation.test.js +90 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +49 -49
- package/v2Containers/TagList/index.js +2 -0
- 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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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
|
+
});
|