@capillarytech/creatives-library 8.0.87-alpha.21 → 8.0.87-alpha.22
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/containers/Templates/constants.js +6 -0
- package/containers/Templates/index.js +44 -24
- package/package.json +1 -1
- package/services/api.js +20 -10
- package/services/tests/api.test.js +5 -1
- package/utils/commonUtils.js +64 -10
- package/utils/tests/commonUtil.test.js +108 -3
- package/utils/tests/transformerUtils.test.js +2127 -0
- package/v2Components/CapImageUpload/index.js +13 -10
- package/v2Components/CapVideoUpload/index.js +12 -9
- package/v2Components/CapWhatsappCTA/messages.js +4 -0
- package/v2Components/CapWhatsappCarouselButton/constant.js +56 -0
- package/v2Components/CapWhatsappCarouselButton/index.js +446 -0
- package/v2Components/CapWhatsappCarouselButton/index.scss +39 -0
- package/v2Components/CapWhatsappCarouselButton/tests/index.test.js +237 -0
- package/v2Components/FormBuilder/constants.js +8 -0
- package/v2Components/FormBuilder/index.js +2 -2
- package/v2Components/TemplatePreview/_templatePreview.scss +20 -0
- package/v2Components/TemplatePreview/assets/images/empty_image_preview.svg +4 -0
- package/v2Components/TemplatePreview/assets/images/empty_video_preview.svg +4 -0
- package/v2Components/TemplatePreview/index.js +160 -105
- package/v2Components/TemplatePreview/tests/__snapshots__/index.test.js.snap +6 -6
- package/v2Containers/CreativesContainer/SlideBoxContent.js +0 -6
- package/v2Containers/CreativesContainer/index.js +100 -7
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +3 -0
- package/v2Containers/Email/index.js +0 -1
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +192 -0
- package/v2Containers/EmailWrapper/constants.js +11 -1
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +343 -0
- package/v2Containers/EmailWrapper/index.js +116 -300
- package/v2Containers/EmailWrapper/mockdata/mockdata.js +119 -0
- package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +214 -0
- package/v2Containers/EmailWrapper/tests/index.test.js +101 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +601 -0
- package/v2Containers/MobilePush/Edit/index.js +0 -1
- package/v2Containers/MobilepushWrapper/index.js +1 -2
- package/v2Containers/Sms/Create/index.js +0 -1
- package/v2Containers/SmsWrapper/index.js +0 -2
- package/v2Containers/Templates/_templates.scss +47 -0
- package/v2Containers/Templates/index.js +55 -5
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +177 -156
- package/v2Containers/Whatsapp/constants.js +87 -1
- package/v2Containers/Whatsapp/index.js +715 -190
- package/v2Containers/Whatsapp/index.scss +52 -1
- package/v2Containers/Whatsapp/messages.js +38 -2
- package/v2Containers/Whatsapp/styles.scss +5 -0
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +27722 -90751
- package/v2Containers/Whatsapp/tests/__snapshots__/utils.test.js.snap +6 -0
- package/v2Containers/Whatsapp/tests/mockData.js +3 -7
- package/v2Containers/Whatsapp/tests/utils.test.js +178 -1
- package/v2Containers/Whatsapp/utils.js +52 -0
- package/v2Containers/Zalo/index.js +47 -15
- package/v2Containers/Zalo/index.scss +8 -0
- package/v2Containers/Zalo/messages.js +4 -0
- package/v2Containers/mockdata.js +2 -0
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import useEmailWrapper from '../hooks/useEmailWrapper';
|
|
3
|
+
import { EmailWrapperMockData } from '../mockdata/mockdata';
|
|
4
|
+
import _, { isEmpty, find } from 'lodash';
|
|
5
|
+
import { renderHook, waitFor, act as reactAct } from '@testing-library/react';
|
|
6
|
+
import { EMAIL_CREATE_MODES, STEPS } from '../constants';
|
|
7
|
+
import { GA } from '@capillarytech/cap-ui-utils';
|
|
8
|
+
import { gtmPush } from '../../../utils/gtmTrackers';
|
|
9
|
+
|
|
10
|
+
// Mock dependencies
|
|
11
|
+
jest.mock('lodash', () => ({
|
|
12
|
+
...jest.requireActual('lodash'),
|
|
13
|
+
isEmpty: jest.fn().mockImplementation(val => {
|
|
14
|
+
if (val === null || val === undefined) return true;
|
|
15
|
+
if (typeof val === 'object') return Object.keys(val).length === 0;
|
|
16
|
+
if (typeof val === 'string') return val.trim().length === 0;
|
|
17
|
+
return !val;
|
|
18
|
+
}),
|
|
19
|
+
find: jest.fn(),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
let mockCapNotificationError;
|
|
23
|
+
jest.mock('@capillarytech/cap-ui-library/CapNotification', () => {
|
|
24
|
+
mockCapNotificationError = jest.fn();
|
|
25
|
+
return {
|
|
26
|
+
error: mockCapNotificationError,
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
jest.mock('../../../utils/gtmTrackers', () => ({
|
|
31
|
+
gtmPush: jest.fn(),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
const mockStopTimer = jest.fn();
|
|
35
|
+
|
|
36
|
+
jest.mock('@capillarytech/cap-ui-utils', () => ({
|
|
37
|
+
...jest.requireActual('@capillarytech/cap-ui-utils'),
|
|
38
|
+
GA: {
|
|
39
|
+
timeTracker: {
|
|
40
|
+
startTimer: jest.fn(),
|
|
41
|
+
stopTimer: jest.fn(),
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
describe('useEmailWrapper', () => {
|
|
47
|
+
let mockProps;
|
|
48
|
+
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
jest.clearAllMocks();
|
|
51
|
+
isEmpty.mockClear();
|
|
52
|
+
find.mockClear();
|
|
53
|
+
mockCapNotificationError.mockClear();
|
|
54
|
+
gtmPush.mockClear();
|
|
55
|
+
mockStopTimer.mockClear();
|
|
56
|
+
|
|
57
|
+
isEmpty.mockImplementation(val => {
|
|
58
|
+
if (val === null || val === undefined) return true;
|
|
59
|
+
if (typeof val === 'object') return Object.keys(val).length === 0;
|
|
60
|
+
if (typeof val === 'string') return val.trim().length === 0;
|
|
61
|
+
return !val;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
mockProps = {
|
|
65
|
+
...EmailWrapperMockData,
|
|
66
|
+
intl: { formatMessage: jest.fn((msg) => msg.defaultMessage || msg.id || '') },
|
|
67
|
+
onEmailModeChange: jest.fn(),
|
|
68
|
+
showNextStep: jest.fn(),
|
|
69
|
+
onResetStep: jest.fn(),
|
|
70
|
+
onEnterTemplateName: jest.fn(),
|
|
71
|
+
onRemoveTemplateName: jest.fn(),
|
|
72
|
+
templatesActions: {
|
|
73
|
+
resetTemplateData: jest.fn(),
|
|
74
|
+
getDefaultBeeTemplates: jest.fn(),
|
|
75
|
+
handleZipUpload: jest.fn(),
|
|
76
|
+
handleHtmlUpload: jest.fn(),
|
|
77
|
+
setEdmTemplate: jest.fn(),
|
|
78
|
+
setBEETemplate: jest.fn(),
|
|
79
|
+
},
|
|
80
|
+
EmailLayout: null,
|
|
81
|
+
CmsTemplates: null,
|
|
82
|
+
SelectedEdmDefaultTemplate: null,
|
|
83
|
+
isUploading: false,
|
|
84
|
+
getCmsTemplatesInProgress: false,
|
|
85
|
+
step: STEPS.MODE_SELECTION,
|
|
86
|
+
emailCreateMode: '',
|
|
87
|
+
setIsLoadingContent: jest.fn(),
|
|
88
|
+
isGetFormData: false,
|
|
89
|
+
getFormdata: jest.fn(),
|
|
90
|
+
type: 'someType',
|
|
91
|
+
isFullMode: false,
|
|
92
|
+
cap: { currentOrgDetails: { id: 'org1' } },
|
|
93
|
+
showTemplateName: true,
|
|
94
|
+
showLiquidErrorInFooter: false,
|
|
95
|
+
onValidationFail: jest.fn(),
|
|
96
|
+
forwardedTags: [],
|
|
97
|
+
selectedOfferDetails: null,
|
|
98
|
+
onPreviewContentClicked: jest.fn(),
|
|
99
|
+
onTestContentClicked: jest.fn(),
|
|
100
|
+
editor: null,
|
|
101
|
+
moduleType: '',
|
|
102
|
+
eventContextTags: [],
|
|
103
|
+
isLoyaltyModule: false,
|
|
104
|
+
cmsTemplatesLoader: false,
|
|
105
|
+
currentOrgDetails: { id: 'org1' },
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('is a function', () => {
|
|
110
|
+
expect(typeof useEmailWrapper).toBe('function');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('initializes with correct default state', () => {
|
|
114
|
+
const { result } = renderHook(() => useEmailWrapper(mockProps));
|
|
115
|
+
|
|
116
|
+
expect(result.current.templateName).toBeDefined();
|
|
117
|
+
expect(result.current.modes).toBeDefined();
|
|
118
|
+
expect(result.current.onTemplateNameChange).toBeInstanceOf(Function);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('handles template name change correctly', () => {
|
|
122
|
+
const { result } = renderHook(() => useEmailWrapper(mockProps));
|
|
123
|
+
|
|
124
|
+
reactAct(() => {
|
|
125
|
+
result.current.onTemplateNameChange({ target: { value: 'New Template' } });
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(result.current.templateName).toBe('New Template');
|
|
129
|
+
expect(result.current.isTemplateNameEmpty).toBe(false);
|
|
130
|
+
expect(mockProps.onEnterTemplateName).toHaveBeenCalled();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('detects empty template name correctly', () => {
|
|
134
|
+
const { result } = renderHook(() => useEmailWrapper(mockProps));
|
|
135
|
+
|
|
136
|
+
reactAct(() => {
|
|
137
|
+
result.current.onTemplateNameChange({ target: { value: '' } });
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(result.current.templateName).toBe('');
|
|
141
|
+
expect(result.current.isTemplateNameEmpty).toBe(true);
|
|
142
|
+
expect(mockProps.onRemoveTemplateName).toHaveBeenCalled();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('handles mode change correctly', () => {
|
|
146
|
+
const { result } = renderHook(() => useEmailWrapper(mockProps));
|
|
147
|
+
|
|
148
|
+
reactAct(() => {
|
|
149
|
+
result.current.onChange({ target: { value: EMAIL_CREATE_MODES.UPLOAD } });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(mockProps.onEmailModeChange).toHaveBeenCalledWith(EMAIL_CREATE_MODES.UPLOAD);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('handles zip file upload correctly and calls success callbacks', () => {
|
|
156
|
+
const successCallback = mockProps.showNextStep;
|
|
157
|
+
const errorCallback = jest.fn();
|
|
158
|
+
|
|
159
|
+
mockProps.templatesActions.handleZipUpload.mockImplementation((file, onSuccess, onError) => {
|
|
160
|
+
onSuccess();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const { result } = renderHook(() => useEmailWrapper({
|
|
164
|
+
...mockProps,
|
|
165
|
+
isUploading: false,
|
|
166
|
+
}));
|
|
167
|
+
|
|
168
|
+
const mockFile = {
|
|
169
|
+
name: 'test.zip',
|
|
170
|
+
size: 1024 * 1024,
|
|
171
|
+
originFileObj: new Blob(['test data'], { type: 'application/zip' })
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
reactAct(() => {
|
|
175
|
+
result.current.useFileUpload({ file: mockFile });
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(mockProps.templatesActions.handleZipUpload).toHaveBeenCalledWith(
|
|
179
|
+
mockFile.originFileObj,
|
|
180
|
+
expect.any(Function),
|
|
181
|
+
expect.any(Function)
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// expect(mockStopTimer).toHaveBeenCalled();
|
|
185
|
+
expect(gtmPush).toHaveBeenCalled();
|
|
186
|
+
expect(successCallback).toHaveBeenCalled();
|
|
187
|
+
expect(mockCapNotificationError).not.toHaveBeenCalled();
|
|
188
|
+
expect(result.current.modeContent).toEqual({ file: mockFile });
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('handles zip file upload error correctly', () => {
|
|
192
|
+
mockProps.templatesActions.handleZipUpload.mockImplementation((file, onSuccess, onError) => {
|
|
193
|
+
onError();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const { result } = renderHook(() => useEmailWrapper({
|
|
197
|
+
...mockProps,
|
|
198
|
+
isUploading: false,
|
|
199
|
+
}));
|
|
200
|
+
|
|
201
|
+
const mockFile = {
|
|
202
|
+
name: 'test.zip',
|
|
203
|
+
size: 1024 * 1024,
|
|
204
|
+
originFileObj: new Blob(['test data'], { type: 'application/zip' })
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
reactAct(() => {
|
|
208
|
+
result.current.useFileUpload({ file: mockFile });
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(mockProps.templatesActions.handleZipUpload).toHaveBeenCalled();
|
|
212
|
+
expect(mockCapNotificationError).toHaveBeenCalled();
|
|
213
|
+
expect(mockProps.showNextStep).not.toHaveBeenCalled();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('handles HTML file upload correctly', async () => {
|
|
217
|
+
const mockHtmlContent = '<html><body>Test</body></html>';
|
|
218
|
+
const mockFile = {
|
|
219
|
+
name: 'test.html',
|
|
220
|
+
size: 1024,
|
|
221
|
+
originFileObj: new Blob([mockHtmlContent], { type: 'text/html' })
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const mockReader = {
|
|
225
|
+
onload: null,
|
|
226
|
+
onerror: null,
|
|
227
|
+
readAsText: jest.fn(function() {
|
|
228
|
+
this.result = mockHtmlContent;
|
|
229
|
+
if (this.onload) {
|
|
230
|
+
this.onload();
|
|
231
|
+
}
|
|
232
|
+
}),
|
|
233
|
+
result: '',
|
|
234
|
+
};
|
|
235
|
+
const fileReaderSpy = jest.spyOn(global, 'FileReader').mockImplementation(() => mockReader);
|
|
236
|
+
|
|
237
|
+
const { result } = renderHook(() => useEmailWrapper({
|
|
238
|
+
...mockProps,
|
|
239
|
+
isUploading: false,
|
|
240
|
+
}));
|
|
241
|
+
|
|
242
|
+
reactAct(() => {
|
|
243
|
+
result.current.useFileUpload({ file: mockFile });
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
await waitFor(() => {
|
|
247
|
+
expect(mockReader.readAsText).toHaveBeenCalledWith(mockFile.originFileObj);
|
|
248
|
+
expect(mockProps.templatesActions.handleHtmlUpload).toHaveBeenCalledWith(mockHtmlContent);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
expect(result.current.modeContent).toEqual({ file: mockFile });
|
|
252
|
+
expect(mockCapNotificationError).not.toHaveBeenCalled();
|
|
253
|
+
|
|
254
|
+
fileReaderSpy.mockRestore();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('shows error for invalid file type during upload', () => {
|
|
258
|
+
const { result } = renderHook(() => useEmailWrapper({
|
|
259
|
+
...mockProps,
|
|
260
|
+
isUploading: false
|
|
261
|
+
}));
|
|
262
|
+
|
|
263
|
+
const mockFile = {
|
|
264
|
+
name: 'test.txt',
|
|
265
|
+
size: 1024,
|
|
266
|
+
originFileObj: new Blob(['test data'], { type: 'text/plain' })
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
reactAct(() => {
|
|
270
|
+
result.current.useFileUpload({ file: mockFile });
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
expect(mockCapNotificationError).toHaveBeenCalledWith(expect.objectContaining({
|
|
274
|
+
key: "email-upload-error",
|
|
275
|
+
}));
|
|
276
|
+
expect(mockProps.templatesActions.handleZipUpload).not.toHaveBeenCalled();
|
|
277
|
+
expect(mockProps.templatesActions.handleHtmlUpload).not.toHaveBeenCalled();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('shows error for oversized file during upload', () => {
|
|
281
|
+
const { result } = renderHook(() => useEmailWrapper({
|
|
282
|
+
...mockProps,
|
|
283
|
+
isUploading: false
|
|
284
|
+
}));
|
|
285
|
+
|
|
286
|
+
const mockFile = {
|
|
287
|
+
name: 'large_file.zip',
|
|
288
|
+
size: 6 * 1024 * 1024,
|
|
289
|
+
originFileObj: new Blob(['large data'], { type: 'application/zip' })
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
reactAct(() => {
|
|
293
|
+
result.current.useFileUpload({ file: mockFile });
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
expect(mockCapNotificationError).toHaveBeenCalledWith(expect.objectContaining({
|
|
297
|
+
key: "email-upload-error",
|
|
298
|
+
}));
|
|
299
|
+
expect(mockProps.templatesActions.handleZipUpload).not.toHaveBeenCalled();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('shows error if no file or originFileObj is provided for upload', () => {
|
|
303
|
+
const { result } = renderHook(() => useEmailWrapper({
|
|
304
|
+
...mockProps,
|
|
305
|
+
isUploading: false
|
|
306
|
+
}));
|
|
307
|
+
|
|
308
|
+
reactAct(() => {
|
|
309
|
+
result.current.useFileUpload({ file: null });
|
|
310
|
+
});
|
|
311
|
+
expect(mockCapNotificationError).toHaveBeenCalledTimes(1);
|
|
312
|
+
expect(mockCapNotificationError).toHaveBeenCalledWith(expect.objectContaining({ key: "email-upload-error"}));
|
|
313
|
+
|
|
314
|
+
mockCapNotificationError.mockClear();
|
|
315
|
+
reactAct(() => {
|
|
316
|
+
result.current.useFileUpload({ file: { name: 'test.zip', size: 100 } });
|
|
317
|
+
});
|
|
318
|
+
expect(mockCapNotificationError).toHaveBeenCalledTimes(1);
|
|
319
|
+
expect(mockCapNotificationError).toHaveBeenCalledWith(expect.objectContaining({ key: "email-upload-error"}));
|
|
320
|
+
|
|
321
|
+
expect(mockProps.templatesActions.handleZipUpload).not.toHaveBeenCalled();
|
|
322
|
+
expect(mockProps.templatesActions.handleHtmlUpload).not.toHaveBeenCalled();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('calls showNextStep when useEditor is invoked', () => {
|
|
326
|
+
const { result } = renderHook(() => useEmailWrapper(mockProps));
|
|
327
|
+
const templateId = 'template123';
|
|
328
|
+
|
|
329
|
+
reactAct(() => {
|
|
330
|
+
result.current.cmsTemplatesProps.handleEdmDefaultTemplateSelection(templateId);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
expect(result.current.modeContent).toEqual({ id: templateId });
|
|
334
|
+
expect(mockProps.showNextStep).toHaveBeenCalled();
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('calls getDefaultBeeTemplates in useEffect when in EDITOR mode, TEMPLATE_SELECTION step, and no templates', () => {
|
|
338
|
+
renderHook(() => useEmailWrapper({
|
|
339
|
+
...mockProps,
|
|
340
|
+
step: STEPS.TEMPLATE_SELECTION,
|
|
341
|
+
emailCreateMode: EMAIL_CREATE_MODES.EDITOR,
|
|
342
|
+
CmsTemplates: null,
|
|
343
|
+
getCmsTemplatesInProgress: false,
|
|
344
|
+
}));
|
|
345
|
+
|
|
346
|
+
expect(mockProps.templatesActions.getDefaultBeeTemplates).toHaveBeenCalledTimes(1);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('does NOT call getDefaultBeeTemplates if templates already exist or are loading', () => {
|
|
350
|
+
renderHook(() => useEmailWrapper({
|
|
351
|
+
...mockProps,
|
|
352
|
+
step: STEPS.TEMPLATE_SELECTION,
|
|
353
|
+
emailCreateMode: EMAIL_CREATE_MODES.EDITOR,
|
|
354
|
+
CmsTemplates: [{ _id: '1', name: 'Test Template' }],
|
|
355
|
+
getCmsTemplatesInProgress: false,
|
|
356
|
+
}));
|
|
357
|
+
expect(mockProps.templatesActions.getDefaultBeeTemplates).not.toHaveBeenCalled();
|
|
358
|
+
|
|
359
|
+
renderHook(() => useEmailWrapper({
|
|
360
|
+
...mockProps,
|
|
361
|
+
step: STEPS.TEMPLATE_SELECTION,
|
|
362
|
+
emailCreateMode: EMAIL_CREATE_MODES.EDITOR,
|
|
363
|
+
CmsTemplates: null,
|
|
364
|
+
getCmsTemplatesInProgress: true,
|
|
365
|
+
}));
|
|
366
|
+
expect(mockProps.templatesActions.getDefaultBeeTemplates).not.toHaveBeenCalled();
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('calls setEdmTemplate/setBEETemplate in useEffect when in EDITOR mode and CREATE_TEMPLATE_CONTENT step', async () => {
|
|
370
|
+
// 1. Setup
|
|
371
|
+
const templateId = 'editorTemplate456';
|
|
372
|
+
const mockTemplateData = { _id: templateId, name: 'Selected Editor Template' };
|
|
373
|
+
|
|
374
|
+
// Ensure mocks are clean from previous tests
|
|
375
|
+
find.mockClear();
|
|
376
|
+
isEmpty.mockClear();
|
|
377
|
+
mockProps.templatesActions.setEdmTemplate.mockClear();
|
|
378
|
+
mockProps.templatesActions.setBEETemplate.mockClear();
|
|
379
|
+
mockProps.showNextStep.mockClear();
|
|
380
|
+
|
|
381
|
+
// 2. Mock find (this will be called by the *internal* handleEdmDefaultTemplateSelection)
|
|
382
|
+
find.mockImplementation((array, criteria) => {
|
|
383
|
+
// console.log('DEBUG: find called with', JSON.stringify(array), JSON.stringify(criteria)); // Optional debug
|
|
384
|
+
if (array && criteria && criteria._id === templateId && array[0]?._id === templateId) {
|
|
385
|
+
return mockTemplateData;
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// 3. Initial Render (Template Selection Step)
|
|
391
|
+
const initialProps = {
|
|
392
|
+
...mockProps,
|
|
393
|
+
step: STEPS.TEMPLATE_SELECTION,
|
|
394
|
+
emailCreateMode: EMAIL_CREATE_MODES.EDITOR,
|
|
395
|
+
CmsTemplates: [mockTemplateData],
|
|
396
|
+
SelectedEdmDefaultTemplate: null,
|
|
397
|
+
// selectedCreateMode is initially '' from hook's useState
|
|
398
|
+
};
|
|
399
|
+
const { result, rerender } = renderHook((props) => useEmailWrapper(props), {
|
|
400
|
+
initialProps: initialProps
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// 4. Simulate user selecting a template ID via the exposed callback (useEditor)
|
|
404
|
+
reactAct(() => {
|
|
405
|
+
// This maps to useEditor which calls setModeContent and showNextStep
|
|
406
|
+
result.current.cmsTemplatesProps.handleEdmDefaultTemplateSelection(templateId);
|
|
407
|
+
});
|
|
408
|
+
// Verify internal state updated and mock called
|
|
409
|
+
expect(result.current.modeContent).toEqual({ id: templateId }); // State updated
|
|
410
|
+
expect(mockProps.showNextStep).toHaveBeenCalledTimes(1); // User action effect
|
|
411
|
+
|
|
412
|
+
// 5. Rerender with step changed to CREATE_TEMPLATE_CONTENT
|
|
413
|
+
const createContentProps = {
|
|
414
|
+
...initialProps, // Base props
|
|
415
|
+
step: STEPS.CREATE_TEMPLATE_CONTENT, // *** Change step ***
|
|
416
|
+
SelectedEdmDefaultTemplate: null // *** Ensure this is null ***
|
|
417
|
+
// The internal state `modeContent = { id: templateId }` persists
|
|
418
|
+
// The internal state `selectedCreateMode = ''` also persists (not changed yet)
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
// Mock isEmpty just before rerender for the specific check inside the effect
|
|
422
|
+
isEmpty.mockImplementation(val => {
|
|
423
|
+
// console.log('DEBUG: isEmpty called with', JSON.stringify(val)); // Optional debug
|
|
424
|
+
// Return true ONLY for the 'SelectedEdmDefaultTemplate' check (which is null)
|
|
425
|
+
if (val === null) return true;
|
|
426
|
+
// Provide a fallback for other potential isEmpty calls if needed
|
|
427
|
+
if (typeof val === 'object' && !val) return true;
|
|
428
|
+
if (typeof val === 'object' && Object.keys(val).length === 0) return true;
|
|
429
|
+
if (typeof val === 'string' && val.trim().length === 0) return true;
|
|
430
|
+
return !val;
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
rerender(createContentProps);
|
|
434
|
+
|
|
435
|
+
// 6. Wait for effects triggered by the rerender
|
|
436
|
+
await reactAct(async () => {
|
|
437
|
+
// Short pause allows React's scheduler to run effects
|
|
438
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
// Assert that the template setting actions were called as a result of the effect
|
|
443
|
+
expect(mockProps.templatesActions.setEdmTemplate).toHaveBeenCalledTimes(1);
|
|
444
|
+
expect(mockProps.templatesActions.setEdmTemplate).toHaveBeenCalledWith(mockTemplateData);
|
|
445
|
+
expect(mockProps.templatesActions.setBEETemplate).toHaveBeenCalledTimes(1);
|
|
446
|
+
expect(mockProps.templatesActions.setBEETemplate).toHaveBeenCalledWith(mockTemplateData);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('sets selectedCreateMode in useEffect when in UPLOAD mode and CREATE_TEMPLATE_CONTENT step with EmailLayout', async () => {
|
|
450
|
+
const initialRenderProps = {
|
|
451
|
+
...mockProps,
|
|
452
|
+
step: STEPS.MODE_SELECTION,
|
|
453
|
+
emailCreateMode: EMAIL_CREATE_MODES.UPLOAD,
|
|
454
|
+
EmailLayout: null,
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
const { result, rerender } = renderHook((props) => useEmailWrapper(props), {
|
|
458
|
+
initialProps: initialRenderProps
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const rerenderProps = {
|
|
462
|
+
...mockProps,
|
|
463
|
+
step: STEPS.CREATE_TEMPLATE_CONTENT,
|
|
464
|
+
emailCreateMode: EMAIL_CREATE_MODES.UPLOAD,
|
|
465
|
+
EmailLayout: { html: '<p>Test Layout</p>' },
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// Reset isEmpty to default before setting the specific mock for the effect
|
|
469
|
+
isEmpty.mockImplementation((val) => {
|
|
470
|
+
if (val === null || val === undefined) return true;
|
|
471
|
+
if (typeof val === 'object') return Object.keys(val).length === 0;
|
|
472
|
+
if (typeof val === 'string') return val.trim().length === 0;
|
|
473
|
+
return !val;
|
|
474
|
+
});
|
|
475
|
+
// Mock isEmpty specifically for the EmailLayout check inside the useEffect
|
|
476
|
+
isEmpty.mockImplementationOnce(() => false); // Mock !isEmpty(EmailLayout) to be true
|
|
477
|
+
|
|
478
|
+
rerender(rerenderProps);
|
|
479
|
+
|
|
480
|
+
// Wait for the effect to run, set selectedCreateMode, and isShowEmailCreate to update
|
|
481
|
+
await waitFor(() => {
|
|
482
|
+
expect(result.current.isShowEmailCreate).toBe(true);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// Restore default isEmpty behavior after the specific mock is used
|
|
486
|
+
isEmpty.mockImplementation((val) => {
|
|
487
|
+
if (val === null || val === undefined) return true;
|
|
488
|
+
if (typeof val === 'object') return Object.keys(val).length === 0;
|
|
489
|
+
if (typeof val === 'string') return val.trim().length === 0;
|
|
490
|
+
return !val;
|
|
491
|
+
});
|
|
492
|
+
isEmpty.mockClear(); // Clear any remaining mock state if needed
|
|
493
|
+
|
|
494
|
+
expect(mockProps.templatesActions.setEdmTemplate).not.toHaveBeenCalled();
|
|
495
|
+
expect(mockProps.templatesActions.setBEETemplate).not.toHaveBeenCalled();
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('calls cleanup functions on unmount', () => {
|
|
499
|
+
const { unmount } = renderHook(() => useEmailWrapper(mockProps));
|
|
500
|
+
|
|
501
|
+
unmount();
|
|
502
|
+
|
|
503
|
+
expect(mockProps.onResetStep).toHaveBeenCalled();
|
|
504
|
+
expect(mockProps.templatesActions.resetTemplateData).toHaveBeenCalled();
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('correctly computes isShowEmailCreate based on state transitions', async () => {
|
|
508
|
+
// --- Case 1: Initial state -> false ---
|
|
509
|
+
const initialPropsCase1 = {
|
|
510
|
+
...mockProps,
|
|
511
|
+
step: STEPS.MODE_SELECTION,
|
|
512
|
+
emailCreateMode: '',
|
|
513
|
+
EmailLayout: null,
|
|
514
|
+
SelectedEdmDefaultTemplate: null,
|
|
515
|
+
};
|
|
516
|
+
// Mock isEmpty for initial render checks if needed (though selectedCreateMode is the main driver)
|
|
517
|
+
isEmpty.mockReturnValue(true); // General mock for empty things
|
|
518
|
+
|
|
519
|
+
const { result, rerender } = renderHook((props) => useEmailWrapper(props), {
|
|
520
|
+
initialProps: initialPropsCase1,
|
|
521
|
+
});
|
|
522
|
+
expect(result.current.isShowEmailCreate).toBe(false);
|
|
523
|
+
isEmpty.mockClear(); // Clear general mock
|
|
524
|
+
|
|
525
|
+
// --- Case 2: UPLOAD mode selected, Layout available -> true ---
|
|
526
|
+
const uploadProps = {
|
|
527
|
+
...mockProps,
|
|
528
|
+
step: STEPS.CREATE_TEMPLATE_CONTENT,
|
|
529
|
+
emailCreateMode: EMAIL_CREATE_MODES.UPLOAD,
|
|
530
|
+
EmailLayout: { html: '<p>Test</p>' }, // Layout is present
|
|
531
|
+
SelectedEdmDefaultTemplate: null,
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// Reset isEmpty to default before setting the specific mock for the effect
|
|
535
|
+
isEmpty.mockImplementation((val) => {
|
|
536
|
+
if (val === null || val === undefined) return true;
|
|
537
|
+
if (typeof val === 'object') return Object.keys(val).length === 0;
|
|
538
|
+
if (typeof val === 'string') return val.trim().length === 0;
|
|
539
|
+
return !val;
|
|
540
|
+
});
|
|
541
|
+
// Mock isEmpty specifically for the EmailLayout check inside the useEffect
|
|
542
|
+
isEmpty.mockImplementationOnce(() => false); // Mock !isEmpty(EmailLayout) to be true
|
|
543
|
+
|
|
544
|
+
rerender(uploadProps);
|
|
545
|
+
|
|
546
|
+
// Wait for the effect to run, set selectedCreateMode, and isShowEmailCreate to update
|
|
547
|
+
await waitFor(() => {
|
|
548
|
+
expect(result.current.isShowEmailCreate).toBe(true);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// Restore default isEmpty behavior after the specific mock is used
|
|
552
|
+
isEmpty.mockImplementation((val) => {
|
|
553
|
+
if (val === null || val === undefined) return true;
|
|
554
|
+
if (typeof val === 'object') return Object.keys(val).length === 0;
|
|
555
|
+
if (typeof val === 'string') return val.trim().length === 0;
|
|
556
|
+
return !val;
|
|
557
|
+
});
|
|
558
|
+
isEmpty.mockClear(); // Clear any remaining mock state if needed
|
|
559
|
+
|
|
560
|
+
// --- Case 3: EDITOR mode selected, Template becomes available -> true (via effect) ---
|
|
561
|
+
const templateId = 't1';
|
|
562
|
+
const mockTemplate = { _id: templateId };
|
|
563
|
+
find.mockReturnValue(mockTemplate); // Ensure find is mocked for this case
|
|
564
|
+
|
|
565
|
+
const editorPropsInitial = {
|
|
566
|
+
...mockProps,
|
|
567
|
+
step: STEPS.TEMPLATE_SELECTION,
|
|
568
|
+
emailCreateMode: EMAIL_CREATE_MODES.EDITOR,
|
|
569
|
+
CmsTemplates: [mockTemplate],
|
|
570
|
+
SelectedEdmDefaultTemplate: null,
|
|
571
|
+
};
|
|
572
|
+
rerender(editorPropsInitial);
|
|
573
|
+
|
|
574
|
+
// Simulate selecting the template via the exposed handler
|
|
575
|
+
reactAct(() => {
|
|
576
|
+
result.current.cmsTemplatesProps.handleEdmDefaultTemplateSelection(templateId);
|
|
577
|
+
});
|
|
578
|
+
// Verify modeContent was updated internally by the act above
|
|
579
|
+
expect(result.current.modeContent).toEqual({ id: templateId });
|
|
580
|
+
|
|
581
|
+
const editorPropsFinal = {
|
|
582
|
+
...mockProps, // Use fresh props base
|
|
583
|
+
step: STEPS.CREATE_TEMPLATE_CONTENT, // Move to the create step
|
|
584
|
+
emailCreateMode: EMAIL_CREATE_MODES.EDITOR,
|
|
585
|
+
CmsTemplates: [mockTemplate],
|
|
586
|
+
SelectedEdmDefaultTemplate: null, // Template not yet selected in props
|
|
587
|
+
// modeContent state ({id: templateId}) should persist internally from the reactAct call
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
// Mock isEmpty specifically for the check within the useEffect triggered by rerender
|
|
591
|
+
// It should return true for SelectedEdmDefaultTemplate to proceed
|
|
592
|
+
isEmpty.mockImplementation((arg) => arg === null || arg === undefined || Object.keys(arg).length === 0); // General implementation first
|
|
593
|
+
isEmpty.mockImplementationOnce(() => true); // Mock the specific check for SelectedEdmDefaultTemplate
|
|
594
|
+
|
|
595
|
+
rerender(editorPropsFinal);
|
|
596
|
+
|
|
597
|
+
// Cleanup mocks if necessary for subsequent tests (though beforeEach should handle)
|
|
598
|
+
find.mockClear();
|
|
599
|
+
isEmpty.mockClear();
|
|
600
|
+
});
|
|
601
|
+
});
|
|
@@ -1991,7 +1991,6 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1991
1991
|
hideTestAndPreviewBtn={this.props.hideTestAndPreviewBtn}
|
|
1992
1992
|
isFullMode={this.props.isFullMode}
|
|
1993
1993
|
eventContextTags={this.props?.eventContextTags}
|
|
1994
|
-
messageDetails={this.props?.messageDetails}
|
|
1995
1994
|
/>}
|
|
1996
1995
|
</CapColumn>
|
|
1997
1996
|
{this.props.iosCtasData && this.state.showIosCtaTable &&
|
|
@@ -72,7 +72,7 @@ export class MobilepushWrapper extends React.Component { // eslint-disable-line
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
render() {
|
|
75
|
-
const {mobilePushCreateMode, step, getFormData, setIsLoadingContent, isGetFormData, query, isFullMode, showTemplateName, type, onValidationFail, onPreviewContentClicked, onTestContentClicked, templateData, eventContextTags = []
|
|
75
|
+
const {mobilePushCreateMode, step, getFormData, setIsLoadingContent, isGetFormData, query, isFullMode, showTemplateName, type, onValidationFail, onPreviewContentClicked, onTestContentClicked, templateData, eventContextTags = []} = this.props;
|
|
76
76
|
const {templateName} = this.state;
|
|
77
77
|
const isShowMobilepushCreate = !isEmpty(mobilePushCreateMode);
|
|
78
78
|
return (
|
|
@@ -120,7 +120,6 @@ export class MobilepushWrapper extends React.Component { // eslint-disable-line
|
|
|
120
120
|
templateData={templateData}
|
|
121
121
|
hideTestAndPreviewBtn={this.props.hideTestAndPreviewBtn}
|
|
122
122
|
eventContextTags={eventContextTags}
|
|
123
|
-
messageDetails={messageDetails}
|
|
124
123
|
/>
|
|
125
124
|
|
|
126
125
|
|
|
@@ -994,7 +994,6 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
|
|
|
994
994
|
onTestContentClicked={this.props.onTestContentClicked}
|
|
995
995
|
onPreviewContentClicked={this.props.onPreviewContentClicked}
|
|
996
996
|
eventContextTags={this.props?.eventContextTags}
|
|
997
|
-
messageDetails={this.props?.messageDetails}
|
|
998
997
|
/>
|
|
999
998
|
</CapColumn>
|
|
1000
999
|
</CapRow>
|
|
@@ -30,7 +30,6 @@ const SmsWrapper = (props) => {
|
|
|
30
30
|
smsRegister,
|
|
31
31
|
onShowTemplates,
|
|
32
32
|
eventContextTags,
|
|
33
|
-
messageDetails = {},
|
|
34
33
|
} = props;
|
|
35
34
|
|
|
36
35
|
const smsProps = {
|
|
@@ -47,7 +46,6 @@ const SmsWrapper = (props) => {
|
|
|
47
46
|
onPreviewContentClicked,
|
|
48
47
|
onTestContentClicked,
|
|
49
48
|
eventContextTags,
|
|
50
|
-
messageDetails,
|
|
51
49
|
};
|
|
52
50
|
const isTraiDlt = isTraiDLTEnable(isFullMode, smsRegister);
|
|
53
51
|
return <>
|