@capillarytech/creatives-library 8.0.345-alpha.14 → 8.0.345-alpha.15

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 (129) hide show
  1. package/constants/unified.js +29 -0
  2. package/package.json +1 -1
  3. package/services/tests/api.test.js +13 -0
  4. package/utils/commonUtils.js +19 -1
  5. package/utils/rcsPayloadUtils.js +92 -0
  6. package/utils/templateVarUtils.js +201 -0
  7. package/utils/tests/templateVarUtils.test.js +204 -0
  8. package/v2Components/CapActionButton/constants.js +7 -0
  9. package/v2Components/CapActionButton/index.js +167 -109
  10. package/v2Components/CapActionButton/index.scss +157 -6
  11. package/v2Components/CapActionButton/messages.js +19 -3
  12. package/v2Components/CapActionButton/tests/index.test.js +41 -17
  13. package/v2Components/CapTagList/index.js +10 -0
  14. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
  15. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +207 -21
  17. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  18. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
  21. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  22. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +160 -15
  23. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +18 -0
  24. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +341 -76
  25. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
  26. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  27. package/v2Components/CommonTestAndPreview/constants.js +38 -2
  28. package/v2Components/CommonTestAndPreview/index.js +676 -186
  29. package/v2Components/CommonTestAndPreview/messages.js +49 -3
  30. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  31. package/v2Components/CommonTestAndPreview/sagas.js +15 -6
  32. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +308 -284
  33. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
  34. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  35. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  36. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  37. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
  38. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  39. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  40. package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -4
  41. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  42. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  43. package/v2Components/FormBuilder/index.js +8 -10
  44. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
  45. package/v2Components/SmsFallback/constants.js +73 -0
  46. package/v2Components/SmsFallback/index.js +955 -0
  47. package/v2Components/SmsFallback/index.scss +265 -0
  48. package/v2Components/SmsFallback/messages.js +78 -0
  49. package/v2Components/SmsFallback/smsFallbackUtils.js +118 -0
  50. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  51. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  52. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  53. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
  54. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +277 -0
  55. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  56. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  57. package/v2Components/TemplatePreview/_templatePreview.scss +33 -23
  58. package/v2Components/TemplatePreview/constants.js +2 -0
  59. package/v2Components/TemplatePreview/index.js +143 -28
  60. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  61. package/v2Components/TestAndPreviewSlidebox/index.js +13 -1
  62. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  63. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  64. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  65. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  66. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  67. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  68. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  69. package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
  70. package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  71. package/v2Containers/CreativesContainer/constants.js +9 -0
  72. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
  73. package/v2Containers/CreativesContainer/index.js +300 -103
  74. package/v2Containers/CreativesContainer/index.scss +51 -1
  75. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  76. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +78 -34
  77. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
  78. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  79. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +357 -98
  80. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
  81. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  82. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  83. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  84. package/v2Containers/Email/reducer.js +3 -11
  85. package/v2Containers/Email/sagas.js +5 -9
  86. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +0 -4
  87. package/v2Containers/Email/tests/sagas.test.js +3 -21
  88. package/v2Containers/Rcs/constants.js +119 -8
  89. package/v2Containers/Rcs/index.js +2379 -807
  90. package/v2Containers/Rcs/index.js.rej +1336 -0
  91. package/v2Containers/Rcs/index.scss +276 -6
  92. package/v2Containers/Rcs/index.scss.rej +74 -0
  93. package/v2Containers/Rcs/messages.js +38 -3
  94. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +225 -0
  95. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +98018 -70073
  96. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  97. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +128 -0
  98. package/v2Containers/Rcs/tests/index.test.js +152 -121
  99. package/v2Containers/Rcs/tests/mockData.js +38 -0
  100. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
  101. package/v2Containers/Rcs/tests/utils.test.js +646 -30
  102. package/v2Containers/Rcs/utils.js +478 -11
  103. package/v2Containers/Sms/Create/index.js +100 -40
  104. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  105. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  106. package/v2Containers/SmsTrai/Create/index.js +9 -4
  107. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  108. package/v2Containers/SmsTrai/Edit/index.js +636 -130
  109. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  110. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  111. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4328 -2375
  112. package/v2Containers/SmsWrapper/index.js +37 -8
  113. package/v2Containers/TagList/index.js +6 -0
  114. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  115. package/v2Containers/Templates/_templates.scss +163 -2
  116. package/v2Containers/Templates/actions.js +11 -0
  117. package/v2Containers/Templates/constants.js +2 -0
  118. package/v2Containers/Templates/index.js +119 -54
  119. package/v2Containers/Templates/sagas.js +57 -12
  120. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  121. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
  122. package/v2Containers/Templates/tests/sagas.test.js +193 -123
  123. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  124. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  125. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  126. package/v2Containers/TemplatesV2/index.js +86 -23
  127. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  128. package/v2Containers/Whatsapp/index.js +3 -20
  129. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
@@ -1,5 +1,7 @@
1
1
  import { expectSaga } from 'redux-saga-test-plan';
2
- import { take, call, takeLatest, takeEvery, put } from 'redux-saga/effects';
2
+ import {
3
+ take, call, takeLatest, takeEvery, put,
4
+ } from 'redux-saga/effects';
3
5
  import * as matchers from 'redux-saga-test-plan/matchers';
4
6
  import * as api from '../../../services/api';
5
7
  import * as types from '../constants';
@@ -8,13 +10,13 @@ import * as cdnUtils from '../../../utils/cdnTransformation';
8
10
  import {
9
11
  getCdnTransformationConfig,
10
12
  getAllTemplates,
13
+ getLocalSmsTemplates,
11
14
  watchGetCdnTransformationConfig,
12
15
  watchGetAllTemplates,
13
16
  getSenderDetails,
14
17
  fetchWeCrmAccounts,
15
18
  sendZippedFile,
16
19
  fetchUserList,
17
- watchGetUserList,
18
20
  deleteRcsTemplate,
19
21
  watchDeleteRcsTemplate,
20
22
  watchForGetTemplateInfoById,
@@ -31,6 +33,7 @@ import {
31
33
  } from '../sagas';
32
34
 
33
35
  import * as mockData from './mockData';
36
+ import { fetchSmsTemplatesFromQuery } from '../utils/smsTemplatesListApi';
34
37
  import { ZALO } from '../../CreativesContainer/constants';
35
38
  import { VIET_GUYS, ZALO_TEMPLATE_INFO_REQUEST } from '../../Zalo/constants';
36
39
  import { throwError } from 'redux-saga-test-plan/providers';
@@ -133,8 +136,8 @@ describe('templateList saga', () => {
133
136
  ],
134
137
  ]).put({
135
138
  type: types.GET_ALL_TEMPLATES_SUCCESS,
136
- data: mockData.getAllTemplatesListSuccess,
137
- weCRMTemplate: mockData.getAllTemplatesListSuccess,
139
+ data: mockData.getAllTemplatesListSuccess.response,
140
+ weCRMTemplate: mockData.getAllTemplatesListSuccess.response?.unMapped,
138
141
  isReset: mockData.getAllTemplatesListSuccess?.queryParams?.page === 1,
139
142
  })
140
143
  .run();
@@ -150,8 +153,8 @@ describe('templateList saga', () => {
150
153
  ],
151
154
  ]).put({
152
155
  type: types.GET_ALL_TEMPLATES_SUCCESS,
153
- data: mockData.getAllTemplatesListSuccess,
154
- weCRMTemplate: mockData.getAllTemplatesListSuccess,
156
+ data: mockData.getAllTemplatesListSuccess.response,
157
+ weCRMTemplate: mockData.getAllTemplatesListSuccess.response?.unMapped,
155
158
  isReset: mockData.getAllTemplatesListSuccess?.queryParams?.page === 1,
156
159
  })
157
160
  .run();
@@ -170,6 +173,163 @@ describe('templateList saga', () => {
170
173
  })
171
174
  .run();
172
175
  });
176
+
177
+ it('SMS channel uses fetchSmsTemplatesFromQuery', () => {
178
+ const smsAction = {
179
+ type: types.GET_ALL_TEMPLATES_REQUEST,
180
+ channel: 'Sms',
181
+ queryParams: { page: 1, perPage: 25, name: '', sortBy: 'Most Recent' },
182
+ intlCopyOf: 'Kopie',
183
+ };
184
+ const fetched = {
185
+ channelTemplates: { templates: [{ name: 'Kopie Foo' }], totalCount: 1 },
186
+ weCRMTemplate: undefined,
187
+ raw: {},
188
+ };
189
+ return expectSaga(getAllTemplates, smsAction)
190
+ .provide([
191
+ [call(fetchSmsTemplatesFromQuery, smsAction.queryParams, smsAction.intlCopyOf), fetched],
192
+ ])
193
+ .put({
194
+ type: types.GET_ALL_TEMPLATES_SUCCESS,
195
+ data: fetched.channelTemplates,
196
+ weCRMTemplate: fetched.weCRMTemplate,
197
+ isReset: true,
198
+ })
199
+ .run();
200
+ });
201
+
202
+ it('SMS channel propagates fetchSmsTemplatesFromQuery errors', () => {
203
+ const smsAction = {
204
+ type: types.GET_ALL_TEMPLATES_REQUEST,
205
+ channel: 'SMS',
206
+ queryParams: { page: 2, perPage: 25 },
207
+ intlCopyOf: '',
208
+ };
209
+ const error = new Error('sms list fail');
210
+ return expectSaga(getAllTemplates, smsAction)
211
+ .provide([
212
+ [call(fetchSmsTemplatesFromQuery, smsAction.queryParams, smsAction.intlCopyOf), throwError(error)],
213
+ ])
214
+ .put({
215
+ type: types.GET_ALL_TEMPLATES_FAILURE,
216
+ error,
217
+ })
218
+ .run();
219
+ });
220
+
221
+ it('wechat channel merges mapped and richmedia and applies Most Recent sort', () => {
222
+ const wechatApiResponse = {
223
+ response: {
224
+ mapped: [
225
+ { name: 'B', updatedAt: '2024-01-02T00:00:00Z' },
226
+ { name: 'A', updatedAt: '2024-01-01T00:00:00Z' },
227
+ ],
228
+ richmedia: [{ name: 'C', updatedAt: '2024-01-03T00:00:00Z' }],
229
+ unMapped: [],
230
+ },
231
+ };
232
+ const action = {
233
+ channel: 'wechat',
234
+ queryParams: { sortBy: 'Most Recent', page: 1 },
235
+ intlCopyOf: '',
236
+ };
237
+ return expectSaga(getAllTemplates, action)
238
+ .provide([[matchers.call.fn(api.getAllTemplates), wechatApiResponse]])
239
+ .put.actionType(types.GET_ALL_TEMPLATES_SUCCESS)
240
+ .run();
241
+ });
242
+
243
+ it('wechat channel applies Alphabetically sort', () => {
244
+ const wechatApiResponse = {
245
+ response: {
246
+ mapped: [{ name: 'Zebra', updatedAt: '2024-01-02T00:00:00Z' }],
247
+ richmedia: [{ name: 'Apple', updatedAt: '2024-01-01T00:00:00Z' }],
248
+ unMapped: [],
249
+ },
250
+ };
251
+ const action = {
252
+ channel: 'wechat',
253
+ queryParams: { sortBy: 'Alphabetically', page: 1 },
254
+ intlCopyOf: '',
255
+ };
256
+ return expectSaga(getAllTemplates, action)
257
+ .provide([[matchers.call.fn(api.getAllTemplates), wechatApiResponse]])
258
+ .put.actionType(types.GET_ALL_TEMPLATES_SUCCESS)
259
+ .run();
260
+ });
261
+
262
+ it('wechat channel replaces Copy-of prefix when intlCopyOf is set', () => {
263
+ const wechatApiResponse = {
264
+ response: {
265
+ mapped: [{ name: 'Copy of Promo', updatedAt: '2024-01-01T00:00:00Z' }],
266
+ richmedia: [],
267
+ unMapped: [],
268
+ },
269
+ };
270
+ const action = {
271
+ channel: 'wechat',
272
+ queryParams: { page: 1 },
273
+ intlCopyOf: 'Kopia av ',
274
+ };
275
+ return expectSaga(getAllTemplates, action)
276
+ .provide([[matchers.call.fn(api.getAllTemplates), wechatApiResponse]])
277
+ .put.actionType(types.GET_ALL_TEMPLATES_SUCCESS)
278
+ .run();
279
+ });
280
+ });
281
+
282
+ describe('getLocalSmsTemplates saga', () => {
283
+ it('invokes onSuccess with fetched data', () => {
284
+ const onSuccess = jest.fn();
285
+ const action = {
286
+ queryParams: { page: 1 },
287
+ intlCopyOf: '',
288
+ onSuccess,
289
+ };
290
+ const fetched = { channelTemplates: { templates: [] }, raw: {} };
291
+ return expectSaga(getLocalSmsTemplates, action)
292
+ .provide([[call(fetchSmsTemplatesFromQuery, action.queryParams, action.intlCopyOf), fetched]])
293
+ .call(onSuccess, fetched)
294
+ .run();
295
+ });
296
+
297
+ it('invokes onFailure when fetch throws', () => {
298
+ const onFailure = jest.fn();
299
+ const error = new Error('local list fail');
300
+ const action = {
301
+ queryParams: { page: 1 },
302
+ intlCopyOf: 'Kopie',
303
+ onFailure,
304
+ };
305
+ return expectSaga(getLocalSmsTemplates, action)
306
+ .provide([
307
+ [call(fetchSmsTemplatesFromQuery, action.queryParams, action.intlCopyOf), throwError(error)],
308
+ ])
309
+ .call(onFailure, error)
310
+ .run();
311
+ });
312
+
313
+ it('does not throw when callbacks are omitted', () => {
314
+ const action = { queryParams: { page: 1 }, intlCopyOf: '' };
315
+ return expectSaga(getLocalSmsTemplates, action)
316
+ .provide([
317
+ [call(fetchSmsTemplatesFromQuery, action.queryParams, action.intlCopyOf), throwError(new Error('x'))],
318
+ ])
319
+ .run();
320
+ });
321
+
322
+ it('does not call onSuccess when it is not a function', () => {
323
+ const action = {
324
+ queryParams: { page: 1 },
325
+ intlCopyOf: '',
326
+ onSuccess: 'not-a-fn',
327
+ };
328
+ const fetched = { channelTemplates: { templates: [] } };
329
+ return expectSaga(getLocalSmsTemplates, action)
330
+ .provide([[call(fetchSmsTemplatesFromQuery, action.queryParams, action.intlCopyOf), fetched]])
331
+ .run();
332
+ });
173
333
  });
174
334
 
175
335
  describe('watchForTemplates saga', () => {
@@ -363,7 +523,7 @@ describe('getSenderDetails Saga', () => {
363
523
 
364
524
  describe('fetchWeCrmAccounts Saga', () => {
365
525
  const action = { source: 'CRM' };
366
- test.concurrent('handles successful fetching of WeCRM accounts', () => {
526
+ it('handles successful fetching of WeCRM accounts', () => {
367
527
  const fakeResponse = {
368
528
  response: [{ id: 1, name: 'Account One' }]
369
529
  };
@@ -376,14 +536,11 @@ describe('fetchWeCrmAccounts Saga', () => {
376
536
  })
377
537
  );
378
538
  });
379
- test.concurrent('handles error thrown from api', () => {
539
+ it('handles error thrown from api', () => {
380
540
  const error = new Error('Fetch failed');
381
- expectSaga(fetchWeCrmAccounts, action.source)
541
+ expectSaga(fetchWeCrmAccounts, action)
382
542
  .provide([
383
- [
384
- call(api.fetchWeCrmAccounts),
385
- throwError(error)
386
- ],
543
+ [call(api.fetchWeCrmAccounts, action.source), throwError(error)],
387
544
  ])
388
545
  .put({
389
546
  type: types.GET_WECRM_ACCOUNTS_FAILURE,
@@ -425,6 +582,29 @@ describe('sendZippedFile Saga', () => {
425
582
  expect(generator.next().value).toEqual(call(api.sendZippedFile, action.selectedFile));
426
583
  });
427
584
 
585
+ it('calls errorHandler with message and returns early when isError is true', () => {
586
+ const errorHandler = jest.fn();
587
+ const successHandler = jest.fn();
588
+ const errorAction = {
589
+ selectedFile: 'upload.zip',
590
+ errorHandler,
591
+ successHandler,
592
+ };
593
+ const fakeErrorResponse = {
594
+ status: { isError: true },
595
+ message: 'Upload failed',
596
+ };
597
+ const generator = sendZippedFile(errorAction);
598
+ expect(generator.next().value).toEqual(call(api.sendZippedFile, errorAction.selectedFile));
599
+ // Advancing with isError response causes the saga to call errorHandler and yield
600
+ const yieldStep = generator.next(fakeErrorResponse);
601
+ expect(errorHandler).toHaveBeenCalledWith('Upload failed');
602
+ expect(yieldStep.done).toBe(false);
603
+ // After resuming from the yield, the return statement completes the generator
604
+ expect(generator.next().done).toBe(true);
605
+ expect(successHandler).not.toHaveBeenCalled();
606
+ });
607
+
428
608
  it('handles error thrown from api', () => {
429
609
  const error = new Error('Fetch failed');
430
610
  expectSaga(sendZippedFile, action)
@@ -729,114 +909,4 @@ describe('watchForGetTemplateInfoById saga', () => {
729
909
  .not.call(getTemplateInfoById, action)
730
910
  .run();
731
911
  });
732
- });
733
-
734
- describe('watchGetOrgLevelCampaignSettings saga', () => {
735
- it('should take latest GET_ORG_LEVEL_CAMPAIGN_SETTINGS_REQUEST', () => {
736
- const generator = watchGetOrgLevelCampaignSettings();
737
- expect(generator.next().value).toEqual(
738
- takeLatest(types.GET_ORG_LEVEL_CAMPAIGN_SETTINGS_REQUEST, getOrgLevelCampaignSettings),
739
- );
740
- });
741
- });
742
-
743
- describe('watchGetUserList saga', () => {
744
- it('should take latest GET_USER_LIST_REQUEST', () => {
745
- const generator = watchGetUserList();
746
- expect(generator.next().value).toEqual(
747
- takeLatest(types.GET_USER_LIST_REQUEST, fetchUserList),
748
- );
749
- });
750
- });
751
-
752
- describe('getAllTemplates wechat channel', () => {
753
- it('should combine mapped and richmedia and sort by Most Recent (desc updatedAt)', () => {
754
- const wechatChannel = {
755
- channel: 'wechat',
756
- queryParams: { page: 1, sortBy: 'Most Recent' },
757
- };
758
- const apiResult = {
759
- response: {
760
- mapped: [{ name: 'Template B', updatedAt: '2020-01-01' }],
761
- richmedia: [{ name: 'Template A', updatedAt: '2023-06-01' }],
762
- unMapped: {},
763
- },
764
- };
765
- const generator = getAllTemplates(wechatChannel);
766
- generator.next(); // yield call(Api.getAllTemplates, ...)
767
- const putEffect = generator.next(apiResult).value;
768
- // Most recent first: Template A (2023) before Template B (2020)
769
- expect(putEffect).toEqual(
770
- put({
771
- type: types.GET_ALL_TEMPLATES_SUCCESS,
772
- data: {
773
- templates: [
774
- { name: 'Template A', updatedAt: '2023-06-01' },
775
- { name: 'Template B', updatedAt: '2020-01-01' },
776
- ],
777
- },
778
- weCRMTemplate: {},
779
- isReset: true,
780
- }),
781
- );
782
- expect(generator.next().done).toBe(true);
783
- });
784
-
785
- it('should combine mapped and richmedia and sort Alphabetically for wechat channel', () => {
786
- const wechatChannel = {
787
- channel: 'wechat',
788
- queryParams: { page: 1, sortBy: 'Alphabetically' },
789
- };
790
- const apiResult = {
791
- response: {
792
- mapped: [{ name: 'Z Template' }, { name: 'A Template' }],
793
- richmedia: [],
794
- unMapped: {},
795
- },
796
- };
797
- const generator = getAllTemplates(wechatChannel);
798
- generator.next(); // yield call(Api.getAllTemplates, ...)
799
- // b.name - a.name on strings gives NaN; sort order is unchanged from original
800
- expect(generator.next(apiResult).value).toEqual(
801
- put({
802
- type: types.GET_ALL_TEMPLATES_SUCCESS,
803
- data: { templates: [{ name: 'Z Template' }, { name: 'A Template' }] },
804
- weCRMTemplate: {},
805
- isReset: true,
806
- }),
807
- );
808
- expect(generator.next().done).toBe(true);
809
- });
810
-
811
- it('should replace COPY_OF string in template names when intlCopyOf is set', () => {
812
- const channelWithCopy = {
813
- channel: 'email',
814
- queryParams: { page: 1 },
815
- intlCopyOf: 'Copia de',
816
- };
817
- const apiResult = {
818
- response: {
819
- templates: [{ name: 'Copy of Template 1' }, { name: 'Regular Template' }],
820
- unMapped: {},
821
- },
822
- };
823
- const generator = getAllTemplates(channelWithCopy);
824
- generator.next();
825
- const putEffect = generator.next(apiResult).value;
826
- expect(putEffect).toEqual(
827
- put({
828
- type: types.GET_ALL_TEMPLATES_SUCCESS,
829
- data: {
830
- templates: [
831
- { name: 'Copia de Template 1' },
832
- { name: 'Regular Template' },
833
- ],
834
- unMapped: {},
835
- },
836
- weCRMTemplate: {},
837
- isReset: true,
838
- }),
839
- );
840
- expect(generator.next().done).toBe(true);
841
- });
842
912
  });
@@ -0,0 +1,180 @@
1
+ import * as Api from '../../../services/api';
2
+ import { isTraiDLTEnable } from '../../../utils/common';
3
+
4
+ jest.mock('../../../services/api', () => ({
5
+ getAllTemplates: jest.fn(),
6
+ }));
7
+
8
+ jest.mock('../../../utils/common', () => ({
9
+ isTraiDLTEnable: jest.fn(),
10
+ }));
11
+
12
+ import {
13
+ buildSmsTemplatesListQueryParams,
14
+ fetchSmsTemplatesFromQuery,
15
+ fetchSmsTemplatesListPage,
16
+ SMS_TEMPLATES_LIST_SORT_MOST_RECENT,
17
+ } from '../utils/smsTemplatesListApi';
18
+
19
+ describe('smsTemplatesListApi', () => {
20
+ beforeEach(() => {
21
+ jest.clearAllMocks();
22
+ });
23
+
24
+ describe('buildSmsTemplatesListQueryParams', () => {
25
+ it('includes traiEnable when TRAI DLT is enabled', () => {
26
+ isTraiDLTEnable.mockReturnValue(true);
27
+ const q = buildSmsTemplatesListQueryParams({
28
+ page: 1,
29
+ perPage: 25,
30
+ name: 'x',
31
+ sortBy: SMS_TEMPLATES_LIST_SORT_MOST_RECENT,
32
+ isFullMode: true,
33
+ smsRegister: {},
34
+ });
35
+ expect(q).toEqual({
36
+ page: 1,
37
+ perPage: 25,
38
+ sortBy: SMS_TEMPLATES_LIST_SORT_MOST_RECENT,
39
+ name: 'x',
40
+ traiEnable: true,
41
+ });
42
+ });
43
+
44
+ it('omits traiEnable when TRAI DLT is disabled', () => {
45
+ isTraiDLTEnable.mockReturnValue(false);
46
+ const q = buildSmsTemplatesListQueryParams({
47
+ page: 2,
48
+ perPage: 10,
49
+ isFullMode: false,
50
+ smsRegister: null,
51
+ });
52
+ expect(q.traiEnable).toBeUndefined();
53
+ expect(q.name).toBe('');
54
+ expect(q.sortBy).toBe(SMS_TEMPLATES_LIST_SORT_MOST_RECENT);
55
+ });
56
+ });
57
+
58
+ describe('fetchSmsTemplatesFromQuery', () => {
59
+ it('returns channelTemplates, weCRMTemplate, and raw; maps intl copy on names', async () => {
60
+ Api.getAllTemplates.mockResolvedValue({
61
+ response: {
62
+ templates: [{ name: 'Copy of A', _id: '1' }],
63
+ unMapped: { u: 1 },
64
+ totalCount: 5,
65
+ },
66
+ });
67
+
68
+ const out = await fetchSmsTemplatesFromQuery(
69
+ { page: 1, perPage: 25 },
70
+ 'Kopie',
71
+ );
72
+
73
+ expect(Api.getAllTemplates).toHaveBeenCalledWith({
74
+ channel: 'Sms',
75
+ queryParams: { page: 1, perPage: 25 },
76
+ });
77
+ expect(out.weCRMTemplate).toEqual({ u: 1 });
78
+ expect(out.raw.response.templates[0].name).toBe('Copy of A');
79
+ expect(out.channelTemplates.templates[0].name).toBe('Kopie A');
80
+ expect(out.channelTemplates.totalCount).toBe(5);
81
+ });
82
+
83
+ it('skips name mapping when intlCopyOf is empty', async () => {
84
+ Api.getAllTemplates.mockResolvedValue({
85
+ response: {
86
+ templates: [{ name: 'Copy of A' }],
87
+ },
88
+ });
89
+ const out = await fetchSmsTemplatesFromQuery({ page: 1 }, '');
90
+ expect(out.channelTemplates.templates[0].name).toBe('Copy of A');
91
+ });
92
+
93
+ it('handles missing response.templates', async () => {
94
+ Api.getAllTemplates.mockResolvedValue({ response: {} });
95
+ const out = await fetchSmsTemplatesFromQuery({}, '');
96
+ expect(out.channelTemplates.templates).toEqual([]);
97
+ });
98
+
99
+ it('uses empty response when raw.response is missing', async () => {
100
+ Api.getAllTemplates.mockResolvedValue({});
101
+ const out = await fetchSmsTemplatesFromQuery({}, '');
102
+ expect(out.channelTemplates.templates).toEqual([]);
103
+ expect(out.weCRMTemplate).toBeUndefined();
104
+ });
105
+
106
+ it('does not map names when intlCopyOf is set but templates list is empty', async () => {
107
+ Api.getAllTemplates.mockResolvedValue({
108
+ response: { templates: [] },
109
+ });
110
+ const out = await fetchSmsTemplatesFromQuery({ page: 1 }, 'X');
111
+ expect(out.channelTemplates.templates).toEqual([]);
112
+ });
113
+
114
+ it('uses empty string when template name is missing during intl mapping', async () => {
115
+ Api.getAllTemplates.mockResolvedValue({
116
+ response: {
117
+ templates: [{ _id: 'n', name: undefined }],
118
+ },
119
+ });
120
+ const out = await fetchSmsTemplatesFromQuery({ page: 1 }, 'Lbl');
121
+ expect(out.channelTemplates.templates[0].name).toBe('');
122
+ });
123
+ });
124
+
125
+ describe('fetchSmsTemplatesListPage', () => {
126
+ it('falls back to total when totalCount is absent', async () => {
127
+ isTraiDLTEnable.mockReturnValue(false);
128
+ Api.getAllTemplates.mockResolvedValue({
129
+ response: {
130
+ templates: [{ _id: 'a' }],
131
+ total: 7,
132
+ },
133
+ });
134
+
135
+ const page = await fetchSmsTemplatesListPage({
136
+ page: 1,
137
+ perPage: 25,
138
+ name: '',
139
+ sortBy: SMS_TEMPLATES_LIST_SORT_MOST_RECENT,
140
+ isFullMode: true,
141
+ smsRegister: {},
142
+ intlCopyOf: '',
143
+ });
144
+
145
+ expect(page.templates).toEqual([{ _id: 'a' }]);
146
+ expect(page.totalCount).toBe(7);
147
+ });
148
+
149
+ it('uses totalCount when set', async () => {
150
+ isTraiDLTEnable.mockReturnValue(false);
151
+ Api.getAllTemplates.mockResolvedValue({
152
+ response: {
153
+ templates: [],
154
+ totalCount: 12,
155
+ },
156
+ });
157
+ const page = await fetchSmsTemplatesListPage({
158
+ page: 1,
159
+ perPage: 25,
160
+ isFullMode: false,
161
+ smsRegister: {},
162
+ });
163
+ expect(page.totalCount).toBe(12);
164
+ });
165
+
166
+ it('normalizes total to 0 when missing counts', async () => {
167
+ isTraiDLTEnable.mockReturnValue(false);
168
+ Api.getAllTemplates.mockResolvedValue({
169
+ response: { templates: [] },
170
+ });
171
+ const page = await fetchSmsTemplatesListPage({
172
+ page: 1,
173
+ perPage: 25,
174
+ isFullMode: false,
175
+ smsRegister: {},
176
+ });
177
+ expect(page.totalCount).toBe(0);
178
+ });
179
+ });
180
+ });
@@ -0,0 +1,79 @@
1
+ import get from 'lodash/get';
2
+ import * as Api from '../../../services/api';
3
+ import { COPY_OF } from '../../../constants/unified';
4
+ import { isTraiDLTEnable } from '../../../utils/common';
5
+
6
+ /** Matches Templates `getAllTemplates` default for SMS. */
7
+ export const SMS_TEMPLATES_LIST_SORT_MOST_RECENT = 'Most Recent';
8
+
9
+ /**
10
+ * Same query shape as Redux `GET_ALL_TEMPLATES` for channel Sms (DLT vs non-DLT via traiEnable).
11
+ */
12
+ export function buildSmsTemplatesListQueryParams({
13
+ page,
14
+ perPage,
15
+ name = '',
16
+ sortBy = SMS_TEMPLATES_LIST_SORT_MOST_RECENT,
17
+ isFullMode,
18
+ smsRegister,
19
+ }) {
20
+ const traiDlt = isTraiDLTEnable(isFullMode, smsRegister);
21
+ return {
22
+ page,
23
+ perPage,
24
+ sortBy,
25
+ name: name || '',
26
+ ...(traiDlt ? { traiEnable: true } : {}),
27
+ };
28
+ }
29
+
30
+ /**
31
+ * SMS list for Redux saga: uses queryParams already built by Templates (incl. traiEnable).
32
+ * Applies the same "Copy of" → intl label as the former inline saga logic.
33
+ */
34
+ export async function fetchSmsTemplatesFromQuery(queryParams, intlCopyOf = '') {
35
+ const raw = await Api.getAllTemplates({ channel: 'Sms', queryParams });
36
+ const response = raw.response || {};
37
+ let templates = get(response, 'templates', []) || [];
38
+ if (intlCopyOf && templates.length) {
39
+ templates = templates.map((template) => ({
40
+ ...template,
41
+ name: (template.name || '').replace(new RegExp(COPY_OF, 'g'), intlCopyOf),
42
+ }));
43
+ }
44
+ const channelTemplates = { ...response, templates };
45
+ return {
46
+ channelTemplates,
47
+ weCRMTemplate: response.unMapped,
48
+ raw,
49
+ };
50
+ }
51
+
52
+ /**
53
+ * SMS list for the RCS SMS fallback picker only.
54
+ * Called from `useLocalTemplateList` in SmsFallback — keeps data in component state and pairs with
55
+ * `localTemplatesConfig` / `useLocalTemplates` in TemplatesV2. Does **not** use GET_ALL_TEMPLATES saga.
56
+ * Same HTTP call shape as `fetchSmsTemplatesFromQuery` (used by the main SMS GET_ALL_TEMPLATES saga).
57
+ */
58
+ export async function fetchSmsTemplatesListPage({
59
+ page,
60
+ perPage,
61
+ name,
62
+ sortBy,
63
+ isFullMode,
64
+ smsRegister,
65
+ intlCopyOf = '',
66
+ }) {
67
+ const queryParams = buildSmsTemplatesListQueryParams({
68
+ page,
69
+ perPage,
70
+ name,
71
+ sortBy,
72
+ isFullMode,
73
+ smsRegister,
74
+ });
75
+ const { channelTemplates } = await fetchSmsTemplatesFromQuery(queryParams, intlCopyOf);
76
+ const templates = channelTemplates.templates || [];
77
+ const totalCount = get(channelTemplates, 'totalCount', get(channelTemplates, 'total', 0)) || 0;
78
+ return { templates, totalCount };
79
+ }