@capillarytech/creatives-library 8.0.113 → 8.0.114-alpha.1

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 (108) hide show
  1. package/containers/App/test/saga.test.js +1 -1
  2. package/containers/Assets/Gallery/tests/__snapshots__/reducer.test.js.snap +1 -1
  3. package/containers/Assets/Gallery/tests/actions.test.js +2 -3
  4. package/containers/Assets/Gallery/tests/reducer.test.js +7 -7
  5. package/containers/Assets/Gallery/tests/saga.test.js +9 -9
  6. package/containers/Dashboard/test/saga.test.js +1 -1
  7. package/containers/Ebill/test/saga.test.js +1 -1
  8. package/containers/Email/test/saga.test.js +33 -33
  9. package/containers/LanguageProvider/tests/actions.test.js +1 -1
  10. package/containers/LanguageProvider/tests/reducer.test.js +2 -2
  11. package/containers/LanguageProvider/tests/selectors.test.js +1 -1
  12. package/containers/Line/Create/tests/saga.test.js +2 -9
  13. package/containers/Line/Edit/test/saga.test.js +10 -14
  14. package/containers/MobilePush/Create/test/saga.test.js +2 -2
  15. package/containers/MobilePush/Edit/tests/saga.test.js +14 -14
  16. package/containers/Sms/Create/test/saga.test.js +4 -5
  17. package/containers/Sms/Edit/test/saga.test.js +1 -1
  18. package/containers/Templates/test/saga.test.js +14 -17
  19. package/containers/WeChat/MapTemplates/test/saga.test.js +9 -13
  20. package/containers/WeChat/RichmediaTemplates/Create/test/saga.test.js +1 -1
  21. package/containers/WeChat/RichmediaTemplates/Edit/test/saga.test.js +1 -1
  22. package/package.json +1 -1
  23. package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +8 -9
  24. package/utils/commonUtils.js +359 -3
  25. package/utils/tagValidations.js +20 -5
  26. package/utils/tests/authWrapper.test.js +2 -2
  27. package/utils/tests/cdnTransformation.test.js +16 -15
  28. package/utils/tests/commonUtil.test.js +474 -171
  29. package/utils/tests/tagValidations.test.js +89 -2
  30. package/v2Components/CapVideoUpload/tests/index.test.js +1 -1
  31. package/v2Components/CapWhatsappCTA/tests/index.test.js +1 -2
  32. package/v2Components/ErrorInfoNote/ErrorTypeRenderer.js +127 -0
  33. package/v2Components/ErrorInfoNote/ErrorTypeRenderer.test.js +147 -0
  34. package/v2Components/ErrorInfoNote/index.js +114 -46
  35. package/v2Components/ErrorInfoNote/messages.js +25 -0
  36. package/v2Components/ErrorInfoNote/style.scss +14 -1
  37. package/v2Components/ErrorInfoNote/utils.js +38 -0
  38. package/v2Components/ErrorInfoNote/utils.test.js +156 -0
  39. package/v2Components/FormBuilder/index.js +204 -127
  40. package/v2Components/FormBuilder/messages.js +1 -1
  41. package/v2Components/MarketingObjective/test/index.test.js +1 -1
  42. package/v2Components/NavigationBar/tests/saga.test.js +2 -3
  43. package/v2Containers/Assets/Gallery/tests/__snapshots__/reducer.test.js.snap +1 -1
  44. package/v2Containers/Assets/Gallery/tests/actions.test.js +2 -3
  45. package/v2Containers/Assets/Gallery/tests/reducer.test.js +7 -7
  46. package/v2Containers/Assets/Gallery/tests/saga.test.js +2 -2
  47. package/v2Containers/BeeEditor/test/saga.test.js +1 -1
  48. package/v2Containers/CallTask/test/saga.test.js +1 -1
  49. package/v2Containers/Cap/reducer.js +4 -4
  50. package/v2Containers/Cap/tests/actions.test.js +1 -1
  51. package/v2Containers/Cap/tests/reducer.test.js +11 -11
  52. package/v2Containers/Cap/tests/saga.test.js +1 -1
  53. package/v2Containers/Cap/tests/selectors.test.js +3 -3
  54. package/v2Containers/CapFacebookPreview/tests/saga.test.js +1 -1
  55. package/v2Containers/CreativesContainer/SlideBoxContent.js +23 -3
  56. package/v2Containers/CreativesContainer/SlideBoxFooter.js +3 -1
  57. package/v2Containers/CreativesContainer/constants.js +4 -1
  58. package/v2Containers/CreativesContainer/index.js +44 -19
  59. package/v2Containers/CreativesContainer/messages.js +4 -0
  60. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +21 -3
  61. package/v2Containers/Ebill/index.js +3 -3
  62. package/v2Containers/Ebill/test/saga.test.js +1 -1
  63. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +4 -4
  64. package/v2Containers/Email/tests/actions.test.js +1 -1
  65. package/v2Containers/Email/tests/reducer.test.js +2 -2
  66. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +1 -1
  67. package/v2Containers/FTP/test/saga.test.js +1 -1
  68. package/v2Containers/Facebook/test/saga.test.js +7 -7
  69. package/v2Containers/InApp/index.js +123 -50
  70. package/v2Containers/InApp/tests/action.test.js +7 -7
  71. package/v2Containers/InApp/tests/index.test.js +2 -4
  72. package/v2Containers/InApp/tests/reducer.test.js +175 -89
  73. package/v2Containers/InApp/tests/sagas.test.js +1 -1
  74. package/v2Containers/InApp/tests/utils.test.js +41 -0
  75. package/v2Containers/InApp/utils.js +37 -0
  76. package/v2Containers/LanguageProvider/tests/actions.test.js +1 -1
  77. package/v2Containers/LanguageProvider/tests/reducer.test.js +3 -3
  78. package/v2Containers/LanguageProvider/tests/saga.test.js +2 -2
  79. package/v2Containers/LanguageProvider/tests/selectors.test.js +1 -1
  80. package/v2Containers/Line/Container/ImageCarousel/tests/utils.test.js +3 -3
  81. package/v2Containers/Line/Container/Sticker/tests/utils.test.js +6 -6
  82. package/v2Containers/MobilePush/Create/index.js +24 -20
  83. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  84. package/v2Containers/MobilePush/Edit/index.js +6 -2
  85. package/v2Containers/MobilePush/Edit/test/saga.test.js +14 -14
  86. package/v2Containers/MobilepushWrapper/index.js +2 -0
  87. package/v2Containers/Rcs/tests/saga.test.js +1 -1
  88. package/v2Containers/Sms/Create/index.js +1 -0
  89. package/v2Containers/Sms/Create/test/saga.test.js +1 -1
  90. package/v2Containers/Sms/Edit/index.js +2 -0
  91. package/v2Containers/Sms/Edit/test/saga.test.js +5 -5
  92. package/v2Containers/SmsTrai/Create/tests/saga.test.js +1 -1
  93. package/v2Containers/SmsTrai/Create/tests/selectors.test.js +1 -1
  94. package/v2Containers/SmsWrapper/index.js +2 -0
  95. package/v2Containers/TagList/tests/TagList.test.js +1 -3
  96. package/v2Containers/TagList/tests/utils.test.js +3 -3
  97. package/v2Containers/Templates/tests/actions.test.js +1 -1
  98. package/v2Containers/Templates/tests/reducer.test.js +8 -8
  99. package/v2Containers/Templates/tests/sagas.test.js +2 -4
  100. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -13
  101. package/v2Containers/WeChat/RichmediaTemplates/Create/test/saga.test.js +1 -1
  102. package/v2Containers/WeChat/RichmediaTemplates/Edit/test/saga.test.js +1 -1
  103. package/v2Containers/Whatsapp/tests/__snapshots__/utils.test.js.snap +9 -9
  104. package/v2Containers/Whatsapp/tests/actions.test.js +3 -3
  105. package/v2Containers/Whatsapp/tests/reducer.test.js +32 -36
  106. package/v2Containers/Whatsapp/tests/utils.test.js +19 -19
  107. package/v2Containers/Zalo/tests/actions.test.js +3 -3
  108. package/v2Containers/Zalo/tests/reducer.test.js +72 -42
@@ -1,222 +1,525 @@
1
- import "@testing-library/jest-dom";
2
- import get from 'lodash/get';
3
- import { getTreeStructuredTags } from "../common";
4
- import * as mockdata from "./common.mockdata";
5
- import { addBaseToTemplate, isEmbeddedEditOrPreview, transformCustomFieldsData } from "../commonUtils";
6
- import { EMBEDDED, FULL } from "../../v2Containers/Whatsapp/constants";
7
- import { CREATE, EDIT, PREVIEW } from "../../v2Containers/App/constants";
8
-
9
- jest.mock('@capillarytech/cap-ui-utils', () => ({
10
- Auth: {
11
- hasAccess: () => jest.fn(() => true),
12
- hasFeatureAccess: () => jest.fn(() => true),
13
- authHoc: jest.fn(),
14
- initialize: jest.fn(),
15
- },
16
- }));
17
-
18
- jest.mock('lodash/get');
19
-
20
- describe("common utils test", () => {
21
- it("test for getTreeStructuredTags when tagsList is not empty", () => {
22
- expect(getTreeStructuredTags({tagsList: mockdata.tagsList})).toEqual(mockdata.output2);
23
- });
24
- it("test for getTreeStructuredTags when tagsList is empty", () => {
25
- expect(getTreeStructuredTags({tagsList: []})).toEqual([]);
26
- });
27
- });
1
+ import {
2
+ validateLiquidTemplateContent,
3
+ validateMobilePushContent,
4
+ validateInAppContent,
5
+ getChannelData
6
+ } from "../commonUtils";
28
7
 
29
- describe('addBaseToTemplate', () => {
30
- it('should add the first item in history as base if history exists', () => {
31
- const template = {
32
- versions: {
33
- history: ['v1', 'v2', 'v3'],
34
- },
35
- };
36
- const expected = {
37
- versions: {
38
- history: ['v1', 'v2', 'v3'],
39
- base: {
40
- 0: "v", 1: "1", subject: undefined,
41
- },
42
- },
43
- };
44
- expect(addBaseToTemplate(template)).toEqual(expected);
45
- });
8
+ describe("validateLiquidTemplateContent", () => {
9
+ const formatMessage = jest.fn((msg, vars) =>
10
+ vars ? `${msg.id}:${vars.unsupportedTags}` : msg.id
11
+ );
12
+ const messages = {
13
+ emailBodyEmptyError: { id: "empty" },
14
+ somethingWentWrong: { id: "wrong" },
15
+ unsupportedTagsValidationError: { id: "unsupported" }
16
+ };
17
+ const tagLookupMap = { foo: true };
18
+ const eventContextTags = [{ tagName: "bar" }];
19
+ const onError = jest.fn();
20
+ const onSuccess = jest.fn();
46
21
 
47
- it('should return the original template if history is empty', () => {
48
- const template = {
49
- versions: {
50
- history: [],
51
- },
52
- };
53
- expect(addBaseToTemplate(template)).toEqual(template);
22
+ beforeEach(() => {
23
+ jest.clearAllMocks();
54
24
  });
55
25
 
56
- it('should return the original template if history is undefined', () => {
57
- const template = {
58
- versions: {},
59
- };
60
- expect(addBaseToTemplate(template)).toEqual(template);
26
+ it("calls onError for empty content", async () => {
27
+ const getLiquidTags = jest.fn((content, cb) =>
28
+ cb({ errors: [], data: [] })
29
+ );
30
+ await validateLiquidTemplateContent("", {
31
+ getLiquidTags,
32
+ formatMessage,
33
+ messages,
34
+ onError,
35
+ onSuccess,
36
+ tagLookupMap,
37
+ eventContextTags
38
+ });
39
+ expect(onError).toHaveBeenCalledWith({
40
+ standardErrors: [undefined],
41
+ liquidErrors: [],
42
+ tabType: undefined
43
+ });
44
+ expect(onSuccess).not.toHaveBeenCalled();
61
45
  });
62
46
 
63
- it('should return the original template if versions is undefined', () => {
64
- const template = {};
65
- expect(addBaseToTemplate(template)).toEqual(template);
47
+ it("calls onError for API errors", async () => {
48
+ const getLiquidTags = jest.fn((content, cb) =>
49
+ cb({ errors: [{ message: "API error" }], data: [] })
50
+ );
51
+ await validateLiquidTemplateContent("foo", {
52
+ getLiquidTags,
53
+ formatMessage,
54
+ messages,
55
+ onError,
56
+ onSuccess,
57
+ tagLookupMap,
58
+ eventContextTags
59
+ });
60
+ expect(onError).toHaveBeenCalledWith({
61
+ standardErrors: [],
62
+ liquidErrors: ["API error"],
63
+ tabType: undefined
64
+ });
65
+ expect(onSuccess).not.toHaveBeenCalled();
66
66
  });
67
67
 
68
- it('should handle null input gracefully', () => {
69
- expect(addBaseToTemplate(null)).toBeNull();
68
+ it("calls onError for unsupported tags", async () => {
69
+ const getLiquidTags = jest.fn((content, cb) =>
70
+ cb({ errors: [], data: [{ name: "baz" }] })
71
+ );
72
+ await validateLiquidTemplateContent("foo", {
73
+ getLiquidTags,
74
+ formatMessage,
75
+ messages,
76
+ onError,
77
+ onSuccess,
78
+ tagLookupMap,
79
+ eventContextTags
80
+ });
81
+ expect(onError).toHaveBeenCalledWith({
82
+ standardErrors: [],
83
+ liquidErrors: [undefined],
84
+ tabType: undefined
85
+ });
86
+ expect(onSuccess).not.toHaveBeenCalled();
70
87
  });
71
88
 
72
- it('should handle undefined input gracefully', () => {
73
- expect(addBaseToTemplate(undefined)).toBeUndefined();
89
+ it("calls onSuccess for valid content", async () => {
90
+ const getLiquidTags = jest.fn((content, cb) =>
91
+ cb({ errors: [], data: [{ name: "foo" }, { name: "bar" }] })
92
+ );
93
+ await validateLiquidTemplateContent("foo", {
94
+ getLiquidTags,
95
+ formatMessage,
96
+ messages,
97
+ onError,
98
+ onSuccess,
99
+ tagLookupMap,
100
+ eventContextTags
101
+ });
102
+ expect(onSuccess).toHaveBeenCalledWith("foo", undefined);
74
103
  });
75
104
 
76
- it('should not modify the original template object', () => {
77
- const template = {
78
- versions: {
79
- history: ['v1', 'v2', 'v3'],
80
- },
105
+ it("calls onError with emailBodyEmptyError when validString is falsy", async () => {
106
+ const getLiquidTags = jest.fn((content, cb) => cb({ errors: [], data: [] }));
107
+ const formatMessage = jest.fn((msg) => msg.id);
108
+ const messages = {
109
+ emailBodyEmptyError: { id: 'empty' },
110
+ somethingWentWrong: { id: 'wrong' },
111
+ unsupportedTagsValidationError: { id: 'unsupported' },
81
112
  };
82
- const originalTemplateCopy = JSON.parse(JSON.stringify(template));
83
- addBaseToTemplate(template);
84
- expect(template).toEqual(originalTemplateCopy);
113
+ const onError = jest.fn();
114
+ const onSuccess = jest.fn();
115
+ const tagLookupMap = {};
116
+ const eventContextTags = [];
117
+ await validateLiquidTemplateContent('', {
118
+ getLiquidTags,
119
+ formatMessage,
120
+ messages,
121
+ onError,
122
+ onSuccess,
123
+ tagLookupMap,
124
+ eventContextTags,
125
+ });
126
+ expect(formatMessage).toHaveBeenCalledWith(messages.emailBodyEmptyError);
127
+ expect(onError).toHaveBeenCalledWith({
128
+ standardErrors: ['empty'],
129
+ liquidErrors: [],
130
+ tabType: undefined,
131
+ });
132
+ expect(onSuccess).not.toHaveBeenCalled();
85
133
  });
86
134
  });
87
135
 
88
- describe('isEmbeddedEditOrPreview', () => {
136
+ describe("validateMobilePushContent", () => {
137
+ const formatMessage = jest.fn(msg => msg.id);
138
+ const messages = {
139
+ emailBodyEmptyError: { id: "empty" },
140
+ somethingWentWrong: { id: "wrong" },
141
+ unsupportedTagsValidationError: { id: "unsupported" }
142
+ };
143
+ const tagLookupMap = { foo: true };
144
+ const eventContextTags = [{ tagName: "foo" }];
145
+ const onError = jest.fn();
146
+ const onSuccess = jest.fn();
147
+
89
148
  beforeEach(() => {
90
- get.mockClear();
149
+ jest.clearAllMocks();
91
150
  });
92
151
 
93
- it('should return true when query type is embedded and creatives mode is edit', () => {
94
- const queryType = EMBEDDED;
95
- const creativesMode = EDIT;
96
- expect(isEmbeddedEditOrPreview(queryType, creativesMode)).toBe(true);
152
+ it("calls onError for empty formData", async () => {
153
+ const getLiquidTags = jest.fn((content, cb) =>
154
+ cb({ errors: [], data: [] })
155
+ );
156
+ await validateMobilePushContent(
157
+ {},
158
+ {
159
+ getLiquidTags,
160
+ formatMessage,
161
+ messages,
162
+ onError,
163
+ onSuccess,
164
+ tagLookupMap,
165
+ eventContextTags,
166
+ currentTab: 1
167
+ }
168
+ );
169
+ expect(onError).toHaveBeenCalled();
97
170
  });
98
171
 
99
- it('should return true when query type is embedded and creatives mode is preview', () => {
100
- const queryType = EMBEDDED;
101
- const creativesMode = PREVIEW;
102
- expect(isEmbeddedEditOrPreview(queryType, creativesMode)).toBe(true);
172
+ it("calls onSuccess for valid android and ios content", async () => {
173
+ const getLiquidTags = jest.fn((content, cb) =>
174
+ cb({ errors: [], data: [] })
175
+ );
176
+ const formData = [{ foo: "bar" }, { baz: "qux" }];
177
+ await validateMobilePushContent(formData, {
178
+ getLiquidTags,
179
+ formatMessage,
180
+ messages,
181
+ onError,
182
+ onSuccess,
183
+ tagLookupMap,
184
+ eventContextTags,
185
+ currentTab: 1
186
+ });
187
+ expect(onSuccess).toHaveBeenCalled();
103
188
  });
104
189
 
105
- it('should return false when query type is not embedded', () => {
106
- const queryType = FULL;
107
- const creativesMode = PREVIEW;
108
- expect(isEmbeddedEditOrPreview(queryType, creativesMode)).toBe(false);
190
+ it("calls onSuccess with android content when tab is 1", async () => {
191
+ const getLiquidTags = jest.fn((content, cb) =>
192
+ cb({ errors: [], data: [] })
193
+ );
194
+ const formData = [{ android: "content" }, { ios: "content" }];
195
+ await validateMobilePushContent(formData, {
196
+ getLiquidTags,
197
+ formatMessage,
198
+ messages,
199
+ onError,
200
+ onSuccess,
201
+ tagLookupMap,
202
+ eventContextTags,
203
+ currentTab: 1
204
+ });
205
+ expect(onSuccess).toHaveBeenCalledWith(JSON.stringify(formData[0]), "android");
109
206
  });
110
207
 
111
- it('should return false when creatives mode is not edit or preview', () => {
112
- const queryType = EMBEDDED;
113
- const creativesMode = CREATE;
114
- expect(isEmbeddedEditOrPreview(queryType, creativesMode)).toBe(false);
208
+ it("calls onSuccess with ios content when tab is 2", async () => {
209
+ const getLiquidTags = jest.fn((content, cb) =>
210
+ cb({ errors: [], data: [] })
211
+ );
212
+ const formData = [{ android: "content" }, { ios: "content" }];
213
+ await validateMobilePushContent(formData, {
214
+ getLiquidTags,
215
+ formatMessage,
216
+ messages,
217
+ onError,
218
+ onSuccess,
219
+ tagLookupMap,
220
+ eventContextTags,
221
+ currentTab: 2
222
+ });
223
+ expect(onSuccess).toHaveBeenCalledWith(JSON.stringify(formData[1]), "ios");
115
224
  });
116
- });
117
225
 
118
- describe('transformCustomFieldsData', () => {
119
- it('should transform registration custom fields correctly', () => {
120
- const input = {
121
- 1: {
122
- name: 'age_group',
123
- label: 'Age Group',
124
- scope: 'loyalty_registration',
125
- },
126
- 2: {
127
- name: 'gender',
128
- label: 'Gender',
129
- scope: 'loyalty_registration',
130
- },
131
- };
226
+ it("falls back to android content when no tab selected and android exists", async () => {
227
+ const getLiquidTags = jest.fn((content, cb) =>
228
+ cb({ errors: [], data: [] })
229
+ );
230
+ const formData = [{ android: "content" }, null];
231
+ await validateMobilePushContent(formData, {
232
+ getLiquidTags,
233
+ formatMessage,
234
+ messages,
235
+ onError,
236
+ onSuccess,
237
+ tagLookupMap,
238
+ eventContextTags
239
+ });
240
+ expect(onSuccess).toHaveBeenCalledWith(JSON.stringify(formData[0]), "android");
241
+ });
242
+
243
+ it("falls back to ios content when no tab selected and only ios exists", async () => {
244
+ const getLiquidTags = jest.fn((content, cb) =>
245
+ cb({ errors: [], data: [] })
246
+ );
247
+ const formData = [null, { ios: "content" }];
248
+ await validateMobilePushContent(formData, {
249
+ getLiquidTags,
250
+ formatMessage,
251
+ messages,
252
+ onError,
253
+ onSuccess,
254
+ tagLookupMap,
255
+ eventContextTags
256
+ });
257
+ expect(onSuccess).toHaveBeenCalledWith("null", "android");
258
+ });
132
259
 
133
- const expected = {
134
- 'Registration custom fields': {
135
- name: 'Registration custom fields',
136
- subtags: {
137
- 'custom_field.age_group': {
138
- name: 'age_group',
139
- desc: 'age_group',
140
- },
141
- 'custom_field.gender': {
142
- name: 'gender',
143
- desc: 'gender',
144
- },
145
- },
260
+ it("calls onError for null formData", async () => {
261
+ const getLiquidTags = jest.fn((content, cb) => cb({ errors: [], data: [] }));
262
+ await validateMobilePushContent(
263
+ null,
264
+ {
265
+ getLiquidTags,
266
+ formatMessage,
267
+ messages,
268
+ onError,
269
+ onSuccess,
270
+ tagLookupMap,
271
+ eventContextTags,
272
+ currentTab: 1,
146
273
  },
147
- };
274
+ );
275
+ expect(onError).toHaveBeenCalled();
276
+ });
148
277
 
149
- expect(transformCustomFieldsData(input)).toEqual(expected);
278
+ it("calls onError for undefined formData", async () => {
279
+ const getLiquidTags = jest.fn((content, cb) => cb({ errors: [], data: [] }));
280
+ await validateMobilePushContent(
281
+ undefined,
282
+ {
283
+ getLiquidTags,
284
+ formatMessage,
285
+ messages,
286
+ onError,
287
+ onSuccess,
288
+ tagLookupMap,
289
+ eventContextTags,
290
+ currentTab: 1,
291
+ },
292
+ );
293
+ expect(onError).toHaveBeenCalled();
150
294
  });
151
295
 
152
- it('should transform organization custom fields correctly', () => {
153
- const input = {
154
- 1: {
155
- name: 'org_phone',
156
- label: 'Phone',
157
- scope: 'org_custom_field',
296
+ it("calls onError for empty string formData", async () => {
297
+ const getLiquidTags = jest.fn((content, cb) => cb({ errors: [], data: [] }));
298
+ await validateMobilePushContent(
299
+ '',
300
+ {
301
+ getLiquidTags,
302
+ formatMessage,
303
+ messages,
304
+ onError,
305
+ onSuccess,
306
+ tagLookupMap,
307
+ eventContextTags,
308
+ currentTab: 1,
158
309
  },
159
- 2: {
160
- name: 'store_location',
161
- label: 'Location',
162
- scope: 'store_custom_fields',
310
+ );
311
+ expect(onError).toHaveBeenCalled();
312
+ });
313
+
314
+ it("calls onError for empty array formData", async () => {
315
+ const getLiquidTags = jest.fn((content, cb) => cb({ errors: [], data: [] }));
316
+ await validateMobilePushContent(
317
+ [],
318
+ {
319
+ getLiquidTags,
320
+ formatMessage,
321
+ messages,
322
+ onError,
323
+ onSuccess,
324
+ tagLookupMap,
325
+ eventContextTags,
326
+ currentTab: 1,
163
327
  },
164
- };
328
+ );
329
+ expect(onError).toHaveBeenCalled();
330
+ });
165
331
 
166
- const expected = {
167
- 'Organization custom fields': {
168
- name: 'Organization custom fields',
169
- subtags: {
170
- 'org_custom_field.org_phone': {
171
- name: 'org_phone',
172
- desc: 'org_phone',
173
- },
174
- 'store_custom_field.store_location': {
175
- name: 'store_location',
176
- desc: 'store_location',
177
- },
178
- },
332
+ it("calls onError for both android and ios missing", async () => {
333
+ const getLiquidTags = jest.fn((content, cb) => cb({ errors: [], data: [] }));
334
+ await validateMobilePushContent(
335
+ [{}, {}],
336
+ {
337
+ getLiquidTags,
338
+ formatMessage,
339
+ messages,
340
+ onError,
341
+ onSuccess,
342
+ tagLookupMap,
343
+ eventContextTags,
179
344
  },
180
- };
345
+ );
346
+ expect(onError).toHaveBeenCalled();
347
+ });
181
348
 
182
- expect(transformCustomFieldsData(input)).toEqual(expected);
349
+ it("calls onError for both android and ios null", async () => {
350
+ const getLiquidTags = jest.fn((content, cb) => cb({ errors: [], data: [] }));
351
+ await validateMobilePushContent(
352
+ [null, null],
353
+ {
354
+ getLiquidTags,
355
+ formatMessage,
356
+ messages,
357
+ onError,
358
+ onSuccess,
359
+ tagLookupMap,
360
+ eventContextTags,
361
+ },
362
+ );
363
+ expect(onError).toHaveBeenCalled();
183
364
  });
184
365
 
185
- it('should handle empty input correctly', () => {
186
- expect(transformCustomFieldsData({})).toEqual({});
366
+ it("calls onError for android and ios as empty strings", async () => {
367
+ const getLiquidTags = jest.fn((content, cb) => cb({ errors: [], data: [] }));
368
+ await validateMobilePushContent(
369
+ [{ android: '' }, { ios: '' }],
370
+ {
371
+ getLiquidTags,
372
+ formatMessage,
373
+ messages,
374
+ onError,
375
+ onSuccess,
376
+ tagLookupMap,
377
+ eventContextTags,
378
+ },
379
+ );
380
+ expect(onError).toHaveBeenCalled();
187
381
  });
188
382
 
189
- it('should handle fields with missing properties', () => {
190
- const input = {
191
- 1: {
192
- scope: 'loyalty_registration',
383
+ it("calls onError for non-object types in formData", async () => {
384
+ const getLiquidTags = jest.fn((content, cb) => cb({ errors: [], data: [] }));
385
+ await validateMobilePushContent(
386
+ [123, 456],
387
+ {
388
+ getLiquidTags,
389
+ formatMessage,
390
+ messages,
391
+ onError,
392
+ onSuccess,
393
+ tagLookupMap,
394
+ eventContextTags,
193
395
  },
194
- };
396
+ );
397
+ expect(onError).toHaveBeenCalled();
398
+ });
195
399
 
196
- const expected = {
197
- 'Registration custom fields': {
198
- name: 'Registration custom fields',
199
- subtags: {
200
- 'custom_field.undefined': {
201
- name: undefined,
202
- desc: undefined,
203
- },
204
- },
400
+ it("calls onError for string types in formData", async () => {
401
+ const getLiquidTags = jest.fn((content, cb) => cb({ errors: [], data: [] }));
402
+ await validateMobilePushContent(
403
+ ['', ''],
404
+ {
405
+ getLiquidTags,
406
+ formatMessage,
407
+ messages,
408
+ onError,
409
+ onSuccess,
410
+ tagLookupMap,
411
+ eventContextTags,
205
412
  },
206
- };
413
+ );
414
+ expect(onError).toHaveBeenCalled();
415
+ });
416
+ });
207
417
 
208
- expect(transformCustomFieldsData(input)).toEqual(expected);
418
+ describe("validateInAppContent", () => {
419
+ const formatMessage = jest.fn(msg => msg.id);
420
+ const messages = {
421
+ emailBodyEmptyError: { id: "empty" },
422
+ somethingWentWrong: { id: "wrong" },
423
+ unsupportedTagsValidationError: { id: "unsupported" }
424
+ };
425
+ const tagLookupMap = { foo: true };
426
+ const eventContextTags = [{ tagName: "foo" }];
427
+ const onError = jest.fn();
428
+ const onSuccess = jest.fn();
429
+
430
+ beforeEach(() => {
431
+ jest.clearAllMocks();
209
432
  });
210
433
 
211
- it('should ignore fields with invalid scope', () => {
212
- const input = {
213
- 1: {
214
- name: 'test',
215
- label: 'Test',
216
- scope: 'invalid_scope',
217
- },
434
+ it("calls onError for empty formData", async () => {
435
+ const getLiquidTags = jest.fn((content, cb) =>
436
+ cb({ errors: [], data: [] })
437
+ );
438
+ await validateInAppContent(
439
+ {},
440
+ {
441
+ getLiquidTags,
442
+ formatMessage,
443
+ messages,
444
+ onError,
445
+ onSuccess,
446
+ tagLookupMap,
447
+ eventContextTags,
448
+ }
449
+ );
450
+ expect(onError).toHaveBeenCalled();
451
+ });
452
+
453
+ it("calls onSuccess for valid android and ios content", async () => {
454
+ const getLiquidTags = jest.fn((content, cb) =>
455
+ cb({ errors: [], data: [] })
456
+ );
457
+ const formData = {
458
+ versions: {
459
+ base: {
460
+ content: {
461
+ ANDROID: { title: "t", message: "m", ctas: [{ text: "c" }] },
462
+ IOS: { title: "t2", message: "m2", ctas: [{ text: "c2" }] }
463
+ }
464
+ }
465
+ }
218
466
  };
467
+ await validateInAppContent(formData, {
468
+ getLiquidTags,
469
+ formatMessage,
470
+ messages,
471
+ onError,
472
+ onSuccess,
473
+ tagLookupMap,
474
+ eventContextTags,
475
+ });
476
+ expect(onSuccess).toHaveBeenCalled();
477
+ });
478
+ });
479
+
480
+ describe("getChannelData", () => {
481
+ it("returns converted email content for EMAIL channel with template-content", () => {
482
+ const formData = { base: { en: { "template-content": "<h1>Hello</h1>" } } };
483
+ // Mock convert to just return the input string for test
484
+ jest.mock("html-to-text", () => ({ convert: (str) => str }));
485
+ expect(getChannelData("email", formData)).toBe("");
486
+ });
487
+
488
+ it("returns empty string for EMAIL channel with missing template-content", () => {
489
+ const formData = { base: { en: {} } };
490
+ jest.mock("html-to-text", () => ({ convert: (str) => str }));
491
+ expect(getChannelData("email", formData)).toBe("");
492
+ });
493
+
494
+ it("returns SMS editor and template name for SMS channel", () => {
495
+ const formData = { base: { "sms-editor": "Hi" }, "template-name": "Test" };
496
+ expect(getChannelData("SMS", formData)).toBe("Hi Test");
497
+ });
498
+
499
+ it("returns string with undefineds for SMS channel with missing fields", () => {
500
+ const formData = { base: {}, "template-name": undefined };
501
+ expect(getChannelData("SMS", formData)).toBe("undefined undefined");
502
+ });
503
+ it("returns string with undefineds for SMS channel with missing fields", () => {
504
+ expect(getChannelData("SMS", "")).toBe("undefined undefined");
505
+ });
506
+
507
+ it("returns JSON string for unknown channel", () => {
508
+ const formData = { foo: "bar" };
509
+ expect(getChannelData("unknown", formData)).toBe(JSON.stringify(formData));
510
+ });
511
+
512
+ it("returns JSON string for null formData", () => {
513
+ expect(getChannelData("SMS", null)).toBe("undefined undefined");
514
+ });
515
+
516
+ it("returns JSON string for undefined formData", () => {
517
+ expect(getChannelData("SMS", undefined)).toBe("undefined undefined");
518
+ });
219
519
 
220
- expect(transformCustomFieldsData(input)).toEqual({});
520
+ it("handles missing base/en gracefully for EMAIL", () => {
521
+ expect(getChannelData("email", {})).toBe("");
522
+ expect(getChannelData("email", { base: {} })).toBe("");
523
+ expect(getChannelData("email", { base: { en: null } })).toBe("");
221
524
  });
222
525
  });