@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
package/package.json
CHANGED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for imageUrlUpload utils: fetchImageFromUrl and uploadImageFromUrlHelper
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { fetchImageFromUrl, uploadImageFromUrlHelper } from '../imageUrlUpload';
|
|
6
|
+
|
|
7
|
+
describe('imageUrlUpload', () => {
|
|
8
|
+
const formatMessage = jest.fn((msg) => msg?.id ?? msg?.defaultMessage ?? 'formatted');
|
|
9
|
+
const messages = {
|
|
10
|
+
imageTypeInvalid: { id: 'imageTypeInvalid' },
|
|
11
|
+
imageSizeInvalid: { id: 'imageSizeInvalid' },
|
|
12
|
+
imageLoadError: { id: 'imageLoadError' },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
jest.clearAllMocks();
|
|
17
|
+
formatMessage.mockImplementation((msg) => (msg && typeof msg === 'object' && msg.id) ? msg.id : 'formatted');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('fetchImageFromUrl', () => {
|
|
21
|
+
const validUrl = 'https://example.com/image.jpg';
|
|
22
|
+
|
|
23
|
+
it('throws when url is missing', async () => {
|
|
24
|
+
await expect(fetchImageFromUrl()).rejects.toThrow('URL is required');
|
|
25
|
+
await expect(fetchImageFromUrl(null)).rejects.toThrow('URL is required');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('throws when url is empty string', async () => {
|
|
29
|
+
await expect(fetchImageFromUrl('')).rejects.toThrow('URL is required');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('throws when url is only whitespace', async () => {
|
|
33
|
+
await expect(fetchImageFromUrl(' ')).rejects.toThrow('URL is required');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('trims url before fetching', async () => {
|
|
37
|
+
const mockResponse = { ok: true, headers: new Headers() };
|
|
38
|
+
global.fetch = jest.fn().mockResolvedValue(mockResponse);
|
|
39
|
+
|
|
40
|
+
await fetchImageFromUrl(' https://example.com/img.png ');
|
|
41
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
42
|
+
'https://example.com/img.png',
|
|
43
|
+
expect.objectContaining({ method: 'GET', redirect: 'follow', mode: 'cors' })
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('returns response when fetch is ok', async () => {
|
|
48
|
+
const mockResponse = { ok: true, status: 200, headers: new Headers() };
|
|
49
|
+
global.fetch = jest.fn().mockResolvedValue(mockResponse);
|
|
50
|
+
|
|
51
|
+
const result = await fetchImageFromUrl(validUrl);
|
|
52
|
+
expect(result).toBe(mockResponse);
|
|
53
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
54
|
+
validUrl,
|
|
55
|
+
expect.objectContaining({ method: 'GET', redirect: 'follow', mode: 'cors' })
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('throws when response is not ok', async () => {
|
|
60
|
+
const mockResponse = { ok: false, status: 404, statusText: 'Not Found' };
|
|
61
|
+
global.fetch = jest.fn().mockResolvedValue(mockResponse);
|
|
62
|
+
|
|
63
|
+
await expect(fetchImageFromUrl(validUrl)).rejects.toThrow(
|
|
64
|
+
'Failed to fetch image: 404 Not Found'
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('throws on network or CORS error', async () => {
|
|
69
|
+
global.fetch = jest.fn().mockRejectedValue(new Error('Network error'));
|
|
70
|
+
|
|
71
|
+
await expect(fetchImageFromUrl(validUrl)).rejects.toThrow('Network error');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('uploadImageFromUrlHelper', () => {
|
|
76
|
+
const uploadAssetFn = jest.fn();
|
|
77
|
+
const fileNamePrefix = 'test-image';
|
|
78
|
+
const maxSize = 5000000;
|
|
79
|
+
|
|
80
|
+
it('returns error when content type is not allowed', async () => {
|
|
81
|
+
const response = {
|
|
82
|
+
ok: true,
|
|
83
|
+
headers: new Headers({ 'Content-Type': 'text/html' }),
|
|
84
|
+
blob: jest.fn().mockResolvedValue(new Blob()),
|
|
85
|
+
};
|
|
86
|
+
global.fetch = jest.fn().mockResolvedValue(response);
|
|
87
|
+
|
|
88
|
+
const result = await uploadImageFromUrlHelper(
|
|
89
|
+
'https://example.com/page',
|
|
90
|
+
formatMessage,
|
|
91
|
+
messages,
|
|
92
|
+
uploadAssetFn,
|
|
93
|
+
fileNamePrefix,
|
|
94
|
+
maxSize,
|
|
95
|
+
['image/jpeg', 'image/png']
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
expect(result).toEqual({ success: false, error: 'imageTypeInvalid' });
|
|
99
|
+
expect(formatMessage).toHaveBeenCalledWith(messages.imageTypeInvalid);
|
|
100
|
+
expect(uploadAssetFn).not.toHaveBeenCalled();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('normalizes content type by stripping charset and trimming', async () => {
|
|
104
|
+
const response = {
|
|
105
|
+
ok: true,
|
|
106
|
+
headers: new Headers({ 'Content-Type': ' image/PNG; charset=utf-8 ' }),
|
|
107
|
+
blob: jest.fn().mockResolvedValue(new Blob(['x'], { type: 'image/png' })),
|
|
108
|
+
};
|
|
109
|
+
global.fetch = jest.fn().mockResolvedValue(response);
|
|
110
|
+
|
|
111
|
+
const OriginalImage = global.Image;
|
|
112
|
+
global.Image = class MockImage {
|
|
113
|
+
constructor() {
|
|
114
|
+
this.width = 10;
|
|
115
|
+
this.height = 10;
|
|
116
|
+
setTimeout(() => { if (this.onload) this.onload(); }, 0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
get src() { return this._src; }
|
|
120
|
+
|
|
121
|
+
set src(v) { this._src = v; }
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const result = await uploadImageFromUrlHelper(
|
|
125
|
+
'https://example.com/img.png',
|
|
126
|
+
formatMessage,
|
|
127
|
+
messages,
|
|
128
|
+
uploadAssetFn,
|
|
129
|
+
fileNamePrefix,
|
|
130
|
+
maxSize,
|
|
131
|
+
['image/png']
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
global.Image = OriginalImage;
|
|
135
|
+
|
|
136
|
+
expect(result.success).toBe(true);
|
|
137
|
+
expect(uploadAssetFn).toHaveBeenCalledWith(
|
|
138
|
+
expect.any(File),
|
|
139
|
+
'image',
|
|
140
|
+
expect.objectContaining({ width: 10, height: 10, error: false })
|
|
141
|
+
);
|
|
142
|
+
const file = uploadAssetFn.mock.calls[0][0];
|
|
143
|
+
expect(file.name).toMatch(/\.png$/);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('returns error when blob size exceeds maxSize', async () => {
|
|
147
|
+
const largeBlob = new Blob([new ArrayBuffer(maxSize + 1)]);
|
|
148
|
+
const response = {
|
|
149
|
+
ok: true,
|
|
150
|
+
headers: new Headers({ 'Content-Type': 'image/jpeg' }),
|
|
151
|
+
blob: jest.fn().mockResolvedValue(largeBlob),
|
|
152
|
+
};
|
|
153
|
+
global.fetch = jest.fn().mockResolvedValue(response);
|
|
154
|
+
|
|
155
|
+
const result = await uploadImageFromUrlHelper(
|
|
156
|
+
'https://example.com/large.jpg',
|
|
157
|
+
formatMessage,
|
|
158
|
+
messages,
|
|
159
|
+
uploadAssetFn,
|
|
160
|
+
fileNamePrefix,
|
|
161
|
+
maxSize
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
expect(result).toEqual({ success: false, error: 'imageSizeInvalid' });
|
|
165
|
+
expect(formatMessage).toHaveBeenCalledWith(messages.imageSizeInvalid);
|
|
166
|
+
expect(uploadAssetFn).not.toHaveBeenCalled();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('returns error when image fails to load (invalid image data)', async () => {
|
|
170
|
+
const blob = new Blob(['not-an-image'], { type: 'image/jpeg' });
|
|
171
|
+
const response = {
|
|
172
|
+
ok: true,
|
|
173
|
+
headers: new Headers({ 'Content-Type': 'image/jpeg' }),
|
|
174
|
+
blob: jest.fn().mockResolvedValue(blob),
|
|
175
|
+
};
|
|
176
|
+
global.fetch = jest.fn().mockResolvedValue(response);
|
|
177
|
+
|
|
178
|
+
const OriginalImage = global.Image;
|
|
179
|
+
global.Image = class MockImage {
|
|
180
|
+
constructor() {
|
|
181
|
+
setTimeout(() => {
|
|
182
|
+
if (this.onerror) this.onerror(new Event('error'));
|
|
183
|
+
}, 0);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
get src() { return this._src; }
|
|
187
|
+
|
|
188
|
+
set src(v) { this._src = v; }
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const result = await uploadImageFromUrlHelper(
|
|
192
|
+
'https://example.com/bad.jpg',
|
|
193
|
+
formatMessage,
|
|
194
|
+
messages,
|
|
195
|
+
uploadAssetFn,
|
|
196
|
+
fileNamePrefix,
|
|
197
|
+
maxSize
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
global.Image = OriginalImage;
|
|
201
|
+
|
|
202
|
+
expect(result.success).toBe(false);
|
|
203
|
+
expect(result.error).toBe('imageLoadError');
|
|
204
|
+
expect(formatMessage).toHaveBeenCalledWith(messages.imageLoadError);
|
|
205
|
+
expect(uploadAssetFn).not.toHaveBeenCalled();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('returns error on fetch failure and uses imageLoadError message', async () => {
|
|
209
|
+
global.fetch = jest.fn().mockRejectedValue(new Error('CORS'));
|
|
210
|
+
|
|
211
|
+
const result = await uploadImageFromUrlHelper(
|
|
212
|
+
'https://example.com/image.jpg',
|
|
213
|
+
formatMessage,
|
|
214
|
+
messages,
|
|
215
|
+
uploadAssetFn,
|
|
216
|
+
fileNamePrefix,
|
|
217
|
+
maxSize
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
expect(result).toEqual({ success: false, error: 'imageLoadError' });
|
|
221
|
+
expect(formatMessage).toHaveBeenCalledWith(messages.imageLoadError);
|
|
222
|
+
expect(uploadAssetFn).not.toHaveBeenCalled();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('trims url before processing', async () => {
|
|
226
|
+
const response = {
|
|
227
|
+
ok: true,
|
|
228
|
+
headers: new Headers({ 'Content-Type': 'image/jpeg' }),
|
|
229
|
+
blob: jest.fn().mockResolvedValue(new Blob()),
|
|
230
|
+
};
|
|
231
|
+
global.fetch = jest.fn().mockResolvedValue(response);
|
|
232
|
+
|
|
233
|
+
await uploadImageFromUrlHelper(
|
|
234
|
+
' https://example.com/img.jpg ',
|
|
235
|
+
formatMessage,
|
|
236
|
+
messages,
|
|
237
|
+
uploadAssetFn,
|
|
238
|
+
fileNamePrefix,
|
|
239
|
+
maxSize
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
expect(global.fetch).toHaveBeenCalledWith(
|
|
243
|
+
'https://example.com/img.jpg',
|
|
244
|
+
expect.any(Object)
|
|
245
|
+
);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('calls uploadAssetFn with file, type and fileParams on success', async () => {
|
|
249
|
+
const blob = new Blob([new ArrayBuffer(100)], { type: 'image/png' });
|
|
250
|
+
const response = {
|
|
251
|
+
ok: true,
|
|
252
|
+
headers: new Headers({ 'Content-Type': 'image/png' }),
|
|
253
|
+
blob: jest.fn().mockResolvedValue(blob),
|
|
254
|
+
};
|
|
255
|
+
global.fetch = jest.fn().mockResolvedValue(response);
|
|
256
|
+
|
|
257
|
+
const OriginalImage = global.Image;
|
|
258
|
+
global.Image = class MockImage {
|
|
259
|
+
constructor() {
|
|
260
|
+
this.width = 100;
|
|
261
|
+
this.height = 80;
|
|
262
|
+
setTimeout(() => {
|
|
263
|
+
if (this.onload) this.onload();
|
|
264
|
+
}, 0);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
get src() { return this._src; }
|
|
268
|
+
|
|
269
|
+
set src(v) { this._src = v; }
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const result = await uploadImageFromUrlHelper(
|
|
273
|
+
'https://example.com/valid.png',
|
|
274
|
+
formatMessage,
|
|
275
|
+
messages,
|
|
276
|
+
uploadAssetFn,
|
|
277
|
+
fileNamePrefix,
|
|
278
|
+
maxSize
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
global.Image = OriginalImage;
|
|
282
|
+
|
|
283
|
+
expect(result).toEqual({ success: true, error: '' });
|
|
284
|
+
expect(uploadAssetFn).toHaveBeenCalledTimes(1);
|
|
285
|
+
const [file, type, fileParams] = uploadAssetFn.mock.calls[0];
|
|
286
|
+
expect(file).toBeInstanceOf(File);
|
|
287
|
+
expect(file.name).toMatch(new RegExp(`^${fileNamePrefix}\\.png$`));
|
|
288
|
+
expect(type).toBe('image');
|
|
289
|
+
expect(fileParams).toEqual(
|
|
290
|
+
expect.objectContaining({
|
|
291
|
+
width: 100,
|
|
292
|
+
height: 80,
|
|
293
|
+
error: false,
|
|
294
|
+
})
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
});
|
|
@@ -643,6 +643,7 @@ export function SlideBoxContent(props) {
|
|
|
643
643
|
{isEmailCreate && (
|
|
644
644
|
<EmailWrapper
|
|
645
645
|
key="creatives-email-wrapper"
|
|
646
|
+
isEditEmail={false}
|
|
646
647
|
date={new Date().getMilliseconds()}
|
|
647
648
|
setIsLoadingContent={setIsLoadingContent}
|
|
648
649
|
onEmailModeChange={onEmailModeChange}
|
|
@@ -723,6 +724,7 @@ export function SlideBoxContent(props) {
|
|
|
723
724
|
return (
|
|
724
725
|
<EmailWrapper
|
|
725
726
|
key="cretives-container-email-edit-wrapper"
|
|
727
|
+
isEditEmail
|
|
726
728
|
setIsLoadingContent={setIsLoadingContent}
|
|
727
729
|
onEmailModeChange={onEmailModeChange}
|
|
728
730
|
emailCreateMode="editor"
|
|
@@ -8,6 +8,7 @@ import messages from './messages';
|
|
|
8
8
|
import ErrorInfoNote from '../../v2Components/ErrorInfoNote';
|
|
9
9
|
import { PREVIEW } from './constants';
|
|
10
10
|
import { EMAIL_CREATE_MODES } from '../EmailWrapper/constants';
|
|
11
|
+
import { hasSupportCKEditor } from '../../utils/common';
|
|
11
12
|
|
|
12
13
|
function getFullModeSaveBtn(slidBoxContent, isCreatingTemplate) {
|
|
13
14
|
if (isCreatingTemplate) {
|
|
@@ -72,8 +73,10 @@ function SlideBoxFooter(props) {
|
|
|
72
73
|
const isBEEEditor = selectedEmailCreateMode === EMAIL_CREATE_MODES.DRAG_DROP
|
|
73
74
|
|| (emailCreateMode === EMAIL_CREATE_MODES.EDITOR && !isHTMLEditorMode)
|
|
74
75
|
|| (isEditMode && !isHtmlEditorValidationStateActive);
|
|
76
|
+
const isSupportCKEditor = hasSupportCKEditor();
|
|
75
77
|
// Only check validation for HTML Editor mode, not for BEE/DragDrop editor
|
|
76
|
-
|
|
78
|
+
// In upload mode the legacy Email (CK) component does not report validation state, so do not disable buttons
|
|
79
|
+
const shouldCheckValidation = isEmailChannel && htmlEditorValidationState && isHTMLEditorMode && !isBEEEditor && !isSupportCKEditor;
|
|
77
80
|
const isContentEmpty = shouldCheckValidation ? (htmlEditorValidationState?.isContentEmpty ?? true) : false;
|
|
78
81
|
// Check if validation has completed
|
|
79
82
|
const validationComplete = shouldCheckValidation ? (htmlEditorValidationState?.validationComplete ?? false) : true;
|
|
@@ -123,7 +126,7 @@ function SlideBoxFooter(props) {
|
|
|
123
126
|
const isBEEEditorMode = isBEEEditorModeInEdit || isBEEEditorModeInCreate;
|
|
124
127
|
const hasBEEEditorErrors = isEmailChannel && isBEEEditorMode && (hasStandardErrors || hasLiquidErrors) && (!htmlEditorValidationState || !htmlEditorHasErrors);
|
|
125
128
|
|
|
126
|
-
const shouldShowErrorInfoNote = hasBEEEditorErrors;
|
|
129
|
+
const shouldShowErrorInfoNote = hasBEEEditorErrors || isSupportCKEditor;
|
|
127
130
|
return (
|
|
128
131
|
<div className="template-footer-width">
|
|
129
132
|
{shouldShowErrorInfoNote && (
|
|
@@ -1781,16 +1781,20 @@ export class Creatives extends React.Component {
|
|
|
1781
1781
|
const {
|
|
1782
1782
|
slidBoxContent, templateStep, currentChannel, emailCreateMode, mobilePushCreateMode, inAppEditorType, weChatTemplateType,
|
|
1783
1783
|
} = this.state;
|
|
1784
|
+
const { isFullMode } = this.props;
|
|
1785
|
+
const channel = currentChannel?.toUpperCase?.() || '';
|
|
1786
|
+
// In library/embedded mode show Continue only for EMAIL and MOBILE_PUSH; hide for other channels
|
|
1787
|
+
if (!isFullMode && channel !== constants.EMAIL && channel !== constants.MOBILE_PUSH) {
|
|
1788
|
+
return false;
|
|
1789
|
+
}
|
|
1784
1790
|
let isShowContinueFooter = false;
|
|
1785
1791
|
const currentStep = this.creativesTemplateSteps[templateStep];
|
|
1786
|
-
const channel = currentChannel.toUpperCase();
|
|
1787
1792
|
// Check if supportCKEditor is false (new flow)
|
|
1788
1793
|
const supportCKEditor = commonUtil.hasSupportCKEditor(); // Default to legacy flow
|
|
1789
1794
|
if (channel === constants.EMAIL || channel === constants.SMS) {
|
|
1790
|
-
// New flow: Show Continue button when supportCKEditor is false and in modeSelection
|
|
1791
|
-
// Always show it (even if disabled) - visibility is separate from enabled state
|
|
1795
|
+
// New flow: Show Continue button when supportCKEditor is false and in modeSelection (full mode only)
|
|
1792
1796
|
if (!supportCKEditor && currentStep === 'modeSelection' && slidBoxContent === 'createTemplate') {
|
|
1793
|
-
return true;
|
|
1797
|
+
return true;
|
|
1794
1798
|
}
|
|
1795
1799
|
|
|
1796
1800
|
// Legacy flow: Original logic (only when supportCKEditor is true)
|
|
@@ -1799,9 +1803,9 @@ export class Creatives extends React.Component {
|
|
|
1799
1803
|
isEmailCreate = currentChannel.toUpperCase() === constants.EMAIL && ((emailCreateMode === "upload" && currentStep !== 'createTemplateContent') || (emailCreateMode === "editor" && currentStep !== 'createTemplateContent' && currentStep !== "templateSelection"));
|
|
1800
1804
|
isShowContinueFooter = isEmailCreate && emailCreateMode;
|
|
1801
1805
|
}
|
|
1802
|
-
} else if (
|
|
1806
|
+
} else if (channel === constants.MOBILE_PUSH) {
|
|
1803
1807
|
isShowContinueFooter = !isEmpty(mobilePushCreateMode) && currentStep === "modeSelection";
|
|
1804
|
-
} else if (
|
|
1808
|
+
} else if (channel === constants.WECHAT) {
|
|
1805
1809
|
isShowContinueFooter = !isEmpty(weChatTemplateType) && currentStep === "modeSelection";
|
|
1806
1810
|
}
|
|
1807
1811
|
|
|
@@ -11,6 +11,11 @@ import {
|
|
|
11
11
|
} from '../../../utils/test-utils';
|
|
12
12
|
import SlideBoxFooter from "../SlideBoxFooter";
|
|
13
13
|
|
|
14
|
+
jest.mock('../../../utils/common', () => ({
|
|
15
|
+
...jest.requireActual('../../../utils/common'),
|
|
16
|
+
hasSupportCKEditor: jest.fn(),
|
|
17
|
+
}));
|
|
18
|
+
|
|
14
19
|
const ComponentToRender = injectIntl(SlideBoxFooter);
|
|
15
20
|
const renderComponent = props => {
|
|
16
21
|
const store = configureStore({}, initialReducer, history);
|
|
@@ -21,49 +26,168 @@ const renderComponent = props => {
|
|
|
21
26
|
);
|
|
22
27
|
};
|
|
23
28
|
|
|
29
|
+
const baseFooterProps = {
|
|
30
|
+
shouldShowDoneFooter: () => true,
|
|
31
|
+
shouldShowContinueFooter: () => false,
|
|
32
|
+
shouldShowFooter: () => true,
|
|
33
|
+
shouldShowHeader: () => true,
|
|
34
|
+
shouldShowTemplateName: () => true,
|
|
35
|
+
onSave: jest.fn(),
|
|
36
|
+
isFullMode: true,
|
|
37
|
+
messages: {},
|
|
38
|
+
slidBoxContent: 'editTemplate',
|
|
39
|
+
templateStep: 'modeSelection',
|
|
40
|
+
isTemplateNameEmpty: false,
|
|
41
|
+
isCreatingTemplate: false,
|
|
42
|
+
};
|
|
43
|
+
|
|
24
44
|
describe("test for empty email empty template name", () => {
|
|
25
45
|
it("check the error message and disabled button", async () => {
|
|
26
|
-
const
|
|
27
|
-
|
|
46
|
+
const { hasSupportCKEditor } = require('../../../utils/common');
|
|
47
|
+
hasSupportCKEditor.mockReturnValue(true);
|
|
48
|
+
const shouldShowDoneFooter = jest.fn().mockReturnValue(true);
|
|
28
49
|
const shouldShowContinueFooter = jest.fn();
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
50
|
+
const props = {
|
|
51
|
+
...baseFooterProps,
|
|
52
|
+
shouldShowDoneFooter,
|
|
53
|
+
shouldShowContinueFooter,
|
|
54
|
+
currentChannel: "EMAIL",
|
|
55
|
+
isTemplateNameEmpty: true,
|
|
56
|
+
htmlEditorValidationState: {
|
|
57
|
+
isContentEmpty: false,
|
|
58
|
+
issueCounts: { html: 0, label: 0, liquid: 0, total: 0 },
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
renderComponent(props);
|
|
62
|
+
const errorMessage = await screen.findByText(/template name cannot be empty/i);
|
|
63
|
+
expect(errorMessage).toBeInTheDocument();
|
|
64
|
+
const updateBtn = screen.getByRole('button', { name: /update/i });
|
|
65
|
+
expect(updateBtn).toBeDisabled();
|
|
66
|
+
renderComponent({
|
|
67
|
+
...props,
|
|
68
|
+
isTemplateNameEmpty: false,
|
|
69
|
+
htmlEditorValidationState: {
|
|
70
|
+
isContentEmpty: false,
|
|
71
|
+
issueCounts: { html: 0, label: 0, liquid: 0, total: 0 },
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
const updateBtns = screen.getAllByRole('button', { name: /update/i });
|
|
75
|
+
expect(updateBtns[1]).toBeEnabled();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('shouldCheckValidation (line 79)', () => {
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
jest.clearAllMocks();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('disables Save when shouldCheckValidation is true and validation has errors not acknowledged (HTML Editor, EMAIL, no CK)', () => {
|
|
85
|
+
const { hasSupportCKEditor } = require('../../../utils/common');
|
|
86
|
+
hasSupportCKEditor.mockReturnValue(false);
|
|
87
|
+
renderComponent({
|
|
88
|
+
...baseFooterProps,
|
|
89
|
+
currentChannel: 'EMAIL',
|
|
90
|
+
slidBoxContent: 'editTemplate',
|
|
91
|
+
htmlEditorValidationState: {
|
|
92
|
+
validationComplete: true,
|
|
93
|
+
hasErrors: true,
|
|
94
|
+
errorsAcknowledged: false,
|
|
95
|
+
isContentEmpty: false,
|
|
96
|
+
issueCounts: { html: 1, label: 0, liquid: 0, total: 1 },
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
const updateBtn = screen.getByRole('button', { name: /update/i });
|
|
100
|
+
expect(updateBtn).toBeDisabled();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('enables Save when shouldCheckValidation is true and validation complete with no blocking errors', () => {
|
|
104
|
+
const { hasSupportCKEditor } = require('../../../utils/common');
|
|
105
|
+
hasSupportCKEditor.mockReturnValue(false);
|
|
106
|
+
renderComponent({
|
|
107
|
+
...baseFooterProps,
|
|
108
|
+
currentChannel: 'EMAIL',
|
|
109
|
+
slidBoxContent: 'editTemplate',
|
|
110
|
+
htmlEditorValidationState: {
|
|
111
|
+
validationComplete: true,
|
|
112
|
+
hasErrors: false,
|
|
113
|
+
errorsAcknowledged: false,
|
|
114
|
+
isContentEmpty: false,
|
|
115
|
+
issueCounts: { html: 0, label: 0, liquid: 0, total: 0 },
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
const updateBtn = screen.getByRole('button', { name: /update/i });
|
|
119
|
+
expect(updateBtn).toBeEnabled();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('does not disable Save based on validation when currentChannel is not EMAIL (shouldCheckValidation false)', () => {
|
|
123
|
+
const { hasSupportCKEditor } = require('../../../utils/common');
|
|
124
|
+
hasSupportCKEditor.mockReturnValue(false);
|
|
125
|
+
renderComponent({
|
|
126
|
+
...baseFooterProps,
|
|
127
|
+
currentChannel: 'SMS',
|
|
128
|
+
slidBoxContent: 'editTemplate',
|
|
129
|
+
htmlEditorValidationState: {
|
|
130
|
+
validationComplete: true,
|
|
131
|
+
hasErrors: true,
|
|
132
|
+
errorsAcknowledged: false,
|
|
133
|
+
isContentEmpty: false,
|
|
134
|
+
issueCounts: { html: 1, label: 0, liquid: 0, total: 1 },
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
const updateBtn = screen.getByRole('button', { name: /update/i });
|
|
138
|
+
expect(updateBtn).toBeEnabled();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('does not disable Save based on validation when htmlEditorValidationState is not provided (shouldCheckValidation false)', () => {
|
|
142
|
+
const { hasSupportCKEditor } = require('../../../utils/common');
|
|
143
|
+
hasSupportCKEditor.mockReturnValue(false);
|
|
144
|
+
renderComponent({
|
|
145
|
+
...baseFooterProps,
|
|
146
|
+
currentChannel: 'EMAIL',
|
|
147
|
+
slidBoxContent: 'editTemplate',
|
|
148
|
+
htmlEditorValidationState: null,
|
|
149
|
+
});
|
|
150
|
+
const updateBtn = screen.getByRole('button', { name: /update/i });
|
|
151
|
+
expect(updateBtn).toBeEnabled();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('does not disable Save based on validation when hasSupportCKEditor is true (shouldCheckValidation false)', () => {
|
|
155
|
+
const { hasSupportCKEditor } = require('../../../utils/common');
|
|
156
|
+
hasSupportCKEditor.mockReturnValue(true);
|
|
157
|
+
renderComponent({
|
|
158
|
+
...baseFooterProps,
|
|
159
|
+
currentChannel: 'EMAIL',
|
|
160
|
+
slidBoxContent: 'editTemplate',
|
|
161
|
+
htmlEditorValidationState: {
|
|
162
|
+
validationComplete: true,
|
|
163
|
+
hasErrors: true,
|
|
164
|
+
errorsAcknowledged: false,
|
|
165
|
+
isContentEmpty: false,
|
|
166
|
+
issueCounts: { html: 1, label: 0, liquid: 0, total: 1 },
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
const updateBtn = screen.getByRole('button', { name: /update/i });
|
|
170
|
+
expect(updateBtn).toBeEnabled();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('disables Save when shouldCheckValidation true and content empty in create mode', () => {
|
|
174
|
+
const { hasSupportCKEditor } = require('../../../utils/common');
|
|
175
|
+
hasSupportCKEditor.mockReturnValue(false);
|
|
176
|
+
renderComponent({
|
|
177
|
+
...baseFooterProps,
|
|
178
|
+
currentChannel: 'EMAIL',
|
|
179
|
+
slidBoxContent: 'createTemplate',
|
|
180
|
+
emailCreateMode: 'html_editor',
|
|
181
|
+
selectedEmailCreateMode: 'html_editor',
|
|
182
|
+
htmlEditorValidationState: {
|
|
183
|
+
validationComplete: true,
|
|
184
|
+
hasErrors: false,
|
|
185
|
+
errorsAcknowledged: false,
|
|
186
|
+
isContentEmpty: true,
|
|
187
|
+
issueCounts: { html: 0, label: 0, liquid: 0, total: 0 },
|
|
188
|
+
},
|
|
68
189
|
});
|
|
190
|
+
const saveBtn = screen.getByRole('button', { name: /create/i });
|
|
191
|
+
expect(saveBtn).toBeDisabled();
|
|
192
|
+
});
|
|
69
193
|
});
|
|
@@ -10,6 +10,7 @@ exports[`Test SlideBoxContent container Email component isTestAndPreviewMode IIF
|
|
|
10
10
|
getCmsTemplatesInProgress={false}
|
|
11
11
|
handleCloseTestAndPreview={[MockFunction]}
|
|
12
12
|
handleTestAndPreview={[MockFunction]}
|
|
13
|
+
isEditEmail={true}
|
|
13
14
|
isLoyaltyModule={false}
|
|
14
15
|
isTestAndPreviewMode={true}
|
|
15
16
|
key="cretives-container-email-edit-wrapper"
|
|
@@ -60,6 +61,7 @@ exports[`Test SlideBoxContent container Email component isTestAndPreviewMode IIF
|
|
|
60
61
|
getCmsTemplatesInProgress={false}
|
|
61
62
|
handleCloseTestAndPreview={[MockFunction]}
|
|
62
63
|
handleTestAndPreview={[MockFunction]}
|
|
64
|
+
isEditEmail={true}
|
|
63
65
|
isLoyaltyModule={false}
|
|
64
66
|
isTestAndPreviewMode={false}
|
|
65
67
|
key="cretives-container-email-edit-wrapper"
|
|
@@ -110,6 +112,7 @@ exports[`Test SlideBoxContent container Email component isTestAndPreviewMode IIF
|
|
|
110
112
|
getCmsTemplatesInProgress={false}
|
|
111
113
|
handleCloseTestAndPreview={[MockFunction]}
|
|
112
114
|
handleTestAndPreview={[MockFunction]}
|
|
115
|
+
isEditEmail={true}
|
|
113
116
|
isLoyaltyModule={false}
|
|
114
117
|
isTestAndPreviewMode={true}
|
|
115
118
|
key="cretives-container-email-edit-wrapper"
|
|
@@ -159,6 +162,7 @@ exports[`Test SlideBoxContent container Email component isTestAndPreviewMode IIF
|
|
|
159
162
|
getCmsTemplatesInProgress={false}
|
|
160
163
|
handleCloseTestAndPreview={[MockFunction]}
|
|
161
164
|
handleTestAndPreview={[MockFunction]}
|
|
165
|
+
isEditEmail={true}
|
|
162
166
|
isLoyaltyModule={false}
|
|
163
167
|
isTestAndPreviewMode={false}
|
|
164
168
|
key="cretives-container-email-edit-wrapper"
|
|
@@ -1277,6 +1281,7 @@ exports[`Test SlideBoxContent container Should render correctly with isTestAndPr
|
|
|
1277
1281
|
<Connect(UserIsAuthenticated(Component))
|
|
1278
1282
|
date={0}
|
|
1279
1283
|
getCmsTemplatesInProgress={false}
|
|
1284
|
+
isEditEmail={false}
|
|
1280
1285
|
isTestAndPreviewMode={false}
|
|
1281
1286
|
key="creatives-email-wrapper"
|
|
1282
1287
|
templateData={
|
|
@@ -1352,6 +1357,7 @@ exports[`Test SlideBoxContent container Should render correctly with isTestAndPr
|
|
|
1352
1357
|
<Connect(UserIsAuthenticated(Component))
|
|
1353
1358
|
date={0}
|
|
1354
1359
|
getCmsTemplatesInProgress={false}
|
|
1360
|
+
isEditEmail={false}
|
|
1355
1361
|
isTestAndPreviewMode={true}
|
|
1356
1362
|
key="creatives-email-wrapper"
|
|
1357
1363
|
templateData={
|