@capillarytech/creatives-library 8.0.345-alpha.9 → 8.0.345

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 (27) hide show
  1. package/package.json +1 -1
  2. package/services/api.js +20 -0
  3. package/services/tests/api.test.js +59 -0
  4. package/v2Components/CapCustomSkeleton/index.js +1 -1
  5. package/v2Components/CapCustomSkeleton/tests/__snapshots__/index.test.js.snap +12 -12
  6. package/v2Containers/Assets/images/archive_Empty_Illustration.svg +9 -0
  7. package/v2Containers/CreativesContainer/SlideBoxContent.js +1 -20
  8. package/v2Containers/CreativesContainer/SlideBoxFooter.js +3 -1
  9. package/v2Containers/CreativesContainer/index.js +6 -4
  10. package/v2Containers/CreativesContainer/messages.js +4 -0
  11. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +3 -0
  12. package/v2Containers/Email/index.js +0 -21
  13. package/v2Containers/Templates/ChannelTypeIllustration.js +23 -6
  14. package/v2Containers/Templates/_templates.scss +130 -24
  15. package/v2Containers/Templates/actions.js +36 -0
  16. package/v2Containers/Templates/constants.js +23 -0
  17. package/v2Containers/Templates/index.js +286 -30
  18. package/v2Containers/Templates/messages.js +68 -0
  19. package/v2Containers/Templates/reducer.js +68 -0
  20. package/v2Containers/Templates/sagas.js +89 -1
  21. package/v2Containers/Templates/selectors.js +12 -0
  22. package/v2Containers/Templates/tests/ChannelTypeIllustration.test.js +12 -0
  23. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1300 -1122
  24. package/v2Containers/Templates/tests/index.test.js +6 -0
  25. package/v2Containers/Templates/tests/reducer.test.js +178 -0
  26. package/v2Containers/Templates/tests/sagas.test.js +314 -8
  27. package/v2Containers/Templates/tests/selector.test.js +32 -0
@@ -23,6 +23,9 @@ describe('Test Templates container', () => {
23
23
  const getUserList = jest.fn();
24
24
  const getSenderDetails = jest.fn();
25
25
  const resetTemplate = jest.fn();
26
+ const setArchivedMode = jest.fn();
27
+ const clearTemplateSelection = jest.fn();
28
+ const toggleTemplateSelection = jest.fn();
26
29
  let renderedComponent;
27
30
 
28
31
  beforeEach(() => {
@@ -56,6 +59,9 @@ describe('Test Templates container', () => {
56
59
  getUserList,
57
60
  getSenderDetails,
58
61
  resetTemplate,
62
+ setArchivedMode,
63
+ clearTemplateSelection,
64
+ toggleTemplateSelection,
59
65
  }}
60
66
  location={{
61
67
  pathname: `/${channel}`,
@@ -353,3 +353,181 @@ describe("test reducer - SET account actions clear templates", () => {
353
353
  expect(result.templates).toEqual([]);
354
354
  });
355
355
  });
356
+
357
+ describe("test reducer - archive actions", () => {
358
+ afterEach(() => {
359
+ jest.clearAllMocks();
360
+ });
361
+
362
+ it("should handle SET_ARCHIVE_FILTER", () => {
363
+ const action = { type: types.SET_ARCHIVE_FILTER, filter: 'archived' };
364
+ const result = reducer(initialState, action).toJS();
365
+ expect(result.archiveFilter).toBe('archived');
366
+ expect(result.templates).toEqual([]);
367
+ expect(result.selectedTemplateIds).toEqual([]);
368
+ });
369
+
370
+ it("should handle SET_ARCHIVED_MODE with isArchived true", () => {
371
+ const action = { type: types.SET_ARCHIVED_MODE, isArchived: true };
372
+ const result = reducer(initialState, action).toJS();
373
+ expect(result.isArchivedMode).toBe(true);
374
+ expect(result.archiveFilter).toBe('archived');
375
+ expect(result.templates).toEqual([]);
376
+ expect(result.selectedTemplateIds).toEqual([]);
377
+ });
378
+
379
+ it("should handle SET_ARCHIVED_MODE with isArchived false", () => {
380
+ const action = { type: types.SET_ARCHIVED_MODE, isArchived: false };
381
+ const result = reducer(initialState, action).toJS();
382
+ expect(result.isArchivedMode).toBe(false);
383
+ expect(result.archiveFilter).toBe('active');
384
+ });
385
+
386
+ it("should handle TOGGLE_TEMPLATE_SELECTION - add id", () => {
387
+ const action = { type: types.TOGGLE_TEMPLATE_SELECTION, id: 'id1' };
388
+ const result = reducer(initialState, action).toJS();
389
+ expect(result.selectedTemplateIds).toEqual(['id1']);
390
+ });
391
+
392
+ it("should handle TOGGLE_TEMPLATE_SELECTION - remove id if already selected", () => {
393
+ const { fromJS } = require('immutable');
394
+ const stateWithSelected = initialState.set('selectedTemplateIds', fromJS(['id1', 'id2']));
395
+ const action = { type: types.TOGGLE_TEMPLATE_SELECTION, id: 'id1' };
396
+ const result = reducer(stateWithSelected, action).toJS();
397
+ expect(result.selectedTemplateIds).toEqual(['id2']);
398
+ });
399
+
400
+ it("should handle TOGGLE_TEMPLATE_SELECTION - add id when selectedTemplateIds is a plain array (legacy state)", () => {
401
+ // Covers the Array.isArray branch of the defensive fallback
402
+ const stateWithPlainArray = initialState.set('selectedTemplateIds', ['id1']);
403
+ const action = { type: types.TOGGLE_TEMPLATE_SELECTION, id: 'id2' };
404
+ const result = reducer(stateWithPlainArray, action).toJS();
405
+ expect(result.selectedTemplateIds).toEqual(['id1', 'id2']);
406
+ });
407
+
408
+ it("should handle TOGGLE_TEMPLATE_SELECTION - remove id when selectedTemplateIds is a plain array (legacy state)", () => {
409
+ // Covers the Array.isArray branch of the defensive fallback
410
+ const stateWithPlainArray = initialState.set('selectedTemplateIds', ['id1', 'id2']);
411
+ const action = { type: types.TOGGLE_TEMPLATE_SELECTION, id: 'id1' };
412
+ const result = reducer(stateWithPlainArray, action).toJS();
413
+ expect(result.selectedTemplateIds).toEqual(['id2']);
414
+ });
415
+
416
+ it("should handle TOGGLE_TEMPLATE_SELECTION - add id when selectedTemplateIds is undefined (stale persisted state)", () => {
417
+ // Covers the final fallback to [] when rawSelected is neither Immutable nor Array
418
+ const stateWithUndefined = initialState.set('selectedTemplateIds', undefined);
419
+ const action = { type: types.TOGGLE_TEMPLATE_SELECTION, id: 'id1' };
420
+ const result = reducer(stateWithUndefined, action).toJS();
421
+ expect(result.selectedTemplateIds).toEqual(['id1']);
422
+ });
423
+
424
+ it("should handle SELECT_ALL_TEMPLATES", () => {
425
+ const action = { type: types.SELECT_ALL_TEMPLATES, ids: ['id1', 'id2', 'id3'] };
426
+ const result = reducer(initialState, action).toJS();
427
+ expect(result.selectedTemplateIds).toEqual(['id1', 'id2', 'id3']);
428
+ });
429
+
430
+ it("should handle CLEAR_TEMPLATE_SELECTION", () => {
431
+ const { fromJS } = require('immutable');
432
+ const stateWithSelected = initialState.set('selectedTemplateIds', fromJS(['id1', 'id2']));
433
+ const action = { type: types.CLEAR_TEMPLATE_SELECTION };
434
+ const result = reducer(stateWithSelected, action).toJS();
435
+ expect(result.selectedTemplateIds).toEqual([]);
436
+ });
437
+
438
+ it("should handle ARCHIVE_TEMPLATE_REQUEST", () => {
439
+ const action = { type: types.ARCHIVE_TEMPLATE_REQUEST };
440
+ const result = reducer(initialState, action).toJS();
441
+ expect(result.archiveInProgress).toBe(true);
442
+ });
443
+
444
+ it("should handle ARCHIVE_TEMPLATE_SUCCESS", () => {
445
+ const action = { type: types.ARCHIVE_TEMPLATE_SUCCESS };
446
+ const result = reducer(initialState, action).toJS();
447
+ expect(result.archiveInProgress).toBe(false);
448
+ });
449
+
450
+ it("should remove archived template id from selectedTemplateIds on ARCHIVE_TEMPLATE_SUCCESS", () => {
451
+ const { fromJS } = require('immutable');
452
+ const stateWithSelected = initialState.set('selectedTemplateIds', fromJS(['id1', 'id2', 'id3']));
453
+ const action = { type: types.ARCHIVE_TEMPLATE_SUCCESS, id: 'id2' };
454
+ const result = reducer(stateWithSelected, action).toJS();
455
+ expect(result.archiveInProgress).toBe(false);
456
+ expect(result.selectedTemplateIds).toEqual(['id1', 'id3']);
457
+ });
458
+
459
+ it("should handle ARCHIVE_TEMPLATE_FAILURE", () => {
460
+ const action = { type: types.ARCHIVE_TEMPLATE_FAILURE };
461
+ const result = reducer(initialState, action).toJS();
462
+ expect(result.archiveInProgress).toBe(false);
463
+ });
464
+
465
+ it("should handle UNARCHIVE_TEMPLATE_REQUEST", () => {
466
+ const action = { type: types.UNARCHIVE_TEMPLATE_REQUEST };
467
+ const result = reducer(initialState, action).toJS();
468
+ expect(result.unarchiveInProgress).toBe(true);
469
+ });
470
+
471
+ it("should handle UNARCHIVE_TEMPLATE_SUCCESS", () => {
472
+ const action = { type: types.UNARCHIVE_TEMPLATE_SUCCESS };
473
+ const result = reducer(initialState, action).toJS();
474
+ expect(result.unarchiveInProgress).toBe(false);
475
+ });
476
+
477
+ it("should remove unarchived template id from selectedTemplateIds on UNARCHIVE_TEMPLATE_SUCCESS", () => {
478
+ const { fromJS } = require('immutable');
479
+ const stateWithSelected = initialState.set('selectedTemplateIds', fromJS(['id1', 'id2', 'id3']));
480
+ const action = { type: types.UNARCHIVE_TEMPLATE_SUCCESS, id: 'id1' };
481
+ const result = reducer(stateWithSelected, action).toJS();
482
+ expect(result.unarchiveInProgress).toBe(false);
483
+ expect(result.selectedTemplateIds).toEqual(['id2', 'id3']);
484
+ });
485
+
486
+ it("should handle UNARCHIVE_TEMPLATE_FAILURE", () => {
487
+ const action = { type: types.UNARCHIVE_TEMPLATE_FAILURE };
488
+ const result = reducer(initialState, action).toJS();
489
+ expect(result.unarchiveInProgress).toBe(false);
490
+ });
491
+
492
+ it("should handle BULK_ARCHIVE_REQUEST", () => {
493
+ const action = { type: types.BULK_ARCHIVE_REQUEST };
494
+ const result = reducer(initialState, action).toJS();
495
+ expect(result.bulkArchiveInProgress).toBe(true);
496
+ });
497
+
498
+ it("should handle BULK_ARCHIVE_SUCCESS", () => {
499
+ const { fromJS } = require('immutable');
500
+ const stateWithSelected = initialState.set('selectedTemplateIds', fromJS(['id1']));
501
+ const action = { type: types.BULK_ARCHIVE_SUCCESS };
502
+ const result = reducer(stateWithSelected, action).toJS();
503
+ expect(result.bulkArchiveInProgress).toBe(false);
504
+ expect(result.selectedTemplateIds).toEqual([]);
505
+ });
506
+
507
+ it("should handle BULK_ARCHIVE_FAILURE", () => {
508
+ const action = { type: types.BULK_ARCHIVE_FAILURE };
509
+ const result = reducer(initialState, action).toJS();
510
+ expect(result.bulkArchiveInProgress).toBe(false);
511
+ });
512
+
513
+ it("should handle BULK_UNARCHIVE_REQUEST", () => {
514
+ const action = { type: types.BULK_UNARCHIVE_REQUEST };
515
+ const result = reducer(initialState, action).toJS();
516
+ expect(result.bulkUnarchiveInProgress).toBe(true);
517
+ });
518
+
519
+ it("should handle BULK_UNARCHIVE_SUCCESS", () => {
520
+ const { fromJS } = require('immutable');
521
+ const stateWithSelected = initialState.set('selectedTemplateIds', fromJS(['id1']));
522
+ const action = { type: types.BULK_UNARCHIVE_SUCCESS };
523
+ const result = reducer(stateWithSelected, action).toJS();
524
+ expect(result.bulkUnarchiveInProgress).toBe(false);
525
+ expect(result.selectedTemplateIds).toEqual([]);
526
+ });
527
+
528
+ it("should handle BULK_UNARCHIVE_FAILURE", () => {
529
+ const action = { type: types.BULK_UNARCHIVE_FAILURE };
530
+ const result = reducer(initialState, action).toJS();
531
+ expect(result.bulkUnarchiveInProgress).toBe(false);
532
+ });
533
+ });
@@ -1,10 +1,10 @@
1
1
  import { expectSaga } from 'redux-saga-test-plan';
2
- import { take, call, takeLatest, takeEvery, put } from 'redux-saga/effects';
3
2
  import * as matchers from 'redux-saga-test-plan/matchers';
3
+ import { throwError } from 'redux-saga-test-plan/providers';
4
+ import { call, takeLatest, put } from 'redux-saga/effects';
4
5
  import * as api from '../../../services/api';
5
6
  import * as types from '../constants';
6
7
  import * as cdnUtils from '../../../utils/cdnTransformation';
7
-
8
8
  import {
9
9
  getCdnTransformationConfig,
10
10
  getAllTemplates,
@@ -28,13 +28,30 @@ import {
28
28
  watchGetOrgLevelCampaignSettings,
29
29
  watchSendingFile,
30
30
  watchFetchWeCrmAccounts,
31
+ watchGetSenderDetails,
32
+ v2TemplateSaga,
33
+ v2TemplateSagaWatchGetDefaultBeeTemplates,
34
+ archiveTemplateSaga,
35
+ unarchiveTemplateSaga,
36
+ bulkArchiveTemplatesSaga,
37
+ bulkUnarchiveTemplatesSaga,
38
+ watchArchiveTemplate,
39
+ watchUnarchiveTemplate,
40
+ watchBulkArchive,
41
+ watchBulkUnarchive,
31
42
  } from '../sagas';
32
-
33
- import * as mockData from './mockData';
34
- import { ZALO } from '../../CreativesContainer/constants';
35
- import { VIET_GUYS, ZALO_TEMPLATE_INFO_REQUEST } from '../../Zalo/constants';
36
- import { throwError } from 'redux-saga-test-plan/providers';
37
43
  import { getTemplateInfoById } from '../../Zalo/saga';
44
+ import { ZALO_TEMPLATE_INFO_REQUEST } from '../../Zalo/constants';
45
+ import * as mockData from './mockData';
46
+
47
+ jest.mock('@capillarytech/cap-ui-library', () => ({
48
+ CapNotification: {
49
+ success: jest.fn(),
50
+ error: jest.fn(),
51
+ warning: jest.fn(),
52
+ info: jest.fn(),
53
+ },
54
+ }));
38
55
 
39
56
  describe('getCdnTransformationConfig saga', () => {
40
57
  it("handle valid response from api", () => {
@@ -839,4 +856,293 @@ describe('getAllTemplates wechat channel', () => {
839
856
  );
840
857
  expect(generator.next().done).toBe(true);
841
858
  });
842
- });
859
+ });
860
+ describe('archiveTemplateSaga', () => {
861
+ it('should dispatch ARCHIVE_TEMPLATE_SUCCESS, refresh listing, then show success notification', () => {
862
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
863
+ const action = { channel: 'EMAIL', id: 'id1', templateName: 'Test' };
864
+ const gen = archiveTemplateSaga(action);
865
+ // call archiveTemplate
866
+ gen.next();
867
+ // put ARCHIVE_TEMPLATE_SUCCESS with id
868
+ expect(gen.next().value).toEqual(put({ type: types.ARCHIVE_TEMPLATE_SUCCESS, id: 'id1' }));
869
+ // yield select archiveFilter
870
+ const selectStep = gen.next();
871
+ expect(selectStep.value).toBeDefined();
872
+ // call getAllTemplates (listing refresh) — notification fires AFTER this resolves
873
+ const callStep = gen.next('active');
874
+ expect(callStep.value).toHaveProperty('@@redux-saga/IO');
875
+ // CapNotification.success runs (not yielded) then generator is done
876
+ expect(gen.next().done).toBe(true);
877
+ expect(CapNotification.success).toHaveBeenCalledWith(expect.objectContaining({ message: 'Template archived successfully' }));
878
+ });
879
+
880
+ it('should dispatch ARCHIVE_TEMPLATE_FAILURE and call CapNotification.error on error', () => {
881
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
882
+ const action = { channel: 'EMAIL', id: 'id1', templateName: 'Test' };
883
+ const gen = archiveTemplateSaga(action);
884
+ gen.next(); // call archiveTemplate
885
+ const error = new Error('archive failed');
886
+ expect(gen.throw(error).value).toEqual(put({ type: types.ARCHIVE_TEMPLATE_FAILURE, error }));
887
+ // advance past put — CapNotification.error executes and generator finishes
888
+ const done = gen.next();
889
+ expect(done.done).toBe(true);
890
+ expect(CapNotification.error).toHaveBeenCalledWith({ message: 'Failed to archive template' });
891
+ });
892
+ });
893
+
894
+ describe('unarchiveTemplateSaga', () => {
895
+ it('should dispatch UNARCHIVE_TEMPLATE_SUCCESS, refresh listing, then show success notification', () => {
896
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
897
+ const action = { channel: 'EMAIL', id: 'id1', templateName: 'Test' };
898
+ const gen = unarchiveTemplateSaga(action);
899
+ // call unarchiveTemplate
900
+ gen.next();
901
+ // put UNARCHIVE_TEMPLATE_SUCCESS with id
902
+ expect(gen.next().value).toEqual(put({ type: types.UNARCHIVE_TEMPLATE_SUCCESS, id: 'id1' }));
903
+ // yield select archiveFilter
904
+ const selectStep = gen.next();
905
+ expect(selectStep.value).toBeDefined();
906
+ // call getAllTemplates (listing refresh) — notification fires AFTER this resolves
907
+ const callStep = gen.next('archived');
908
+ expect(callStep.value).toHaveProperty('@@redux-saga/IO');
909
+ // CapNotification.success runs (not yielded) then generator is done
910
+ expect(gen.next().done).toBe(true);
911
+ expect(CapNotification.success).toHaveBeenCalledWith(expect.objectContaining({ message: 'Template unarchived successfully' }));
912
+ });
913
+
914
+ it('should dispatch UNARCHIVE_TEMPLATE_FAILURE and call CapNotification.error on error', () => {
915
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
916
+ const action = { channel: 'EMAIL', id: 'id1', templateName: 'Test' };
917
+ const gen = unarchiveTemplateSaga(action);
918
+ gen.next(); // call unarchiveTemplate
919
+ const error = new Error('unarchive failed');
920
+ expect(gen.throw(error).value).toEqual(put({ type: types.UNARCHIVE_TEMPLATE_FAILURE, error }));
921
+ // advance past put — CapNotification.error executes and generator finishes
922
+ const done = gen.next();
923
+ expect(done.done).toBe(true);
924
+ expect(CapNotification.error).toHaveBeenCalledWith({ message: 'Failed to unarchive template' });
925
+ });
926
+ });
927
+
928
+ describe('bulkArchiveTemplatesSaga', () => {
929
+ it('should dispatch BULK_ARCHIVE_SUCCESS, refresh listing, then show success notification', () => {
930
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
931
+ const action = { channel: 'EMAIL', ids: ['id1', 'id2'] };
932
+ const gen = bulkArchiveTemplatesSaga(action);
933
+ // call bulkArchiveTemplates
934
+ gen.next();
935
+ // put BULK_ARCHIVE_SUCCESS
936
+ expect(gen.next({ response: { modifiedCount: 2 } }).value).toEqual(put({ type: types.BULK_ARCHIVE_SUCCESS }));
937
+ // yield select archiveFilter
938
+ const selectStep = gen.next();
939
+ expect(selectStep.value).toBeDefined();
940
+ // call getAllTemplates (listing refresh) — notification fires AFTER this resolves
941
+ const callStep = gen.next('active');
942
+ expect(callStep.value).toHaveProperty('@@redux-saga/IO');
943
+ // CapNotification.success runs (not yielded) then generator is done
944
+ expect(gen.next().done).toBe(true);
945
+ expect(CapNotification.success).toHaveBeenCalledWith({ message: '2 templates archived successfully' });
946
+ });
947
+
948
+ it('should dispatch BULK_ARCHIVE_FAILURE and call CapNotification.error on error', () => {
949
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
950
+ const action = { channel: 'EMAIL', ids: ['id1'] };
951
+ const gen = bulkArchiveTemplatesSaga(action);
952
+ gen.next(); // call bulkArchiveTemplates
953
+ const error = new Error('bulk archive failed');
954
+ expect(gen.throw(error).value).toEqual(put({ type: types.BULK_ARCHIVE_FAILURE, error }));
955
+ // advance past put — CapNotification.error executes and generator finishes
956
+ const done = gen.next();
957
+ expect(done.done).toBe(true);
958
+ expect(CapNotification.error).toHaveBeenCalledWith({ message: 'Failed to archive templates' });
959
+ });
960
+ });
961
+
962
+ describe('bulkUnarchiveTemplatesSaga', () => {
963
+ it('should dispatch BULK_UNARCHIVE_SUCCESS, refresh listing, then show success notification', () => {
964
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
965
+ const action = { channel: 'EMAIL', ids: ['id1', 'id2'] };
966
+ const gen = bulkUnarchiveTemplatesSaga(action);
967
+ // call bulkUnarchiveTemplates
968
+ gen.next();
969
+ // put BULK_UNARCHIVE_SUCCESS
970
+ expect(gen.next({ response: { modifiedCount: 2 } }).value).toEqual(put({ type: types.BULK_UNARCHIVE_SUCCESS }));
971
+ // yield select archiveFilter
972
+ const selectStep = gen.next();
973
+ expect(selectStep.value).toBeDefined();
974
+ // call getAllTemplates (listing refresh) — notification fires AFTER this resolves
975
+ const callStep = gen.next('archived');
976
+ expect(callStep.value).toHaveProperty('@@redux-saga/IO');
977
+ // CapNotification.success runs (not yielded) then generator is done
978
+ expect(gen.next().done).toBe(true);
979
+ expect(CapNotification.success).toHaveBeenCalledWith({ message: '2 templates unarchived successfully' });
980
+ });
981
+
982
+ it('should dispatch BULK_UNARCHIVE_FAILURE and call CapNotification.error on error', () => {
983
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
984
+ const action = { channel: 'EMAIL', ids: ['id1'] };
985
+ const gen = bulkUnarchiveTemplatesSaga(action);
986
+ gen.next(); // call bulkUnarchiveTemplates
987
+ const error = new Error('bulk unarchive failed');
988
+ expect(gen.throw(error).value).toEqual(put({ type: types.BULK_UNARCHIVE_FAILURE, error }));
989
+ // advance past put — CapNotification.error executes and generator finishes
990
+ const done = gen.next();
991
+ expect(done.done).toBe(true);
992
+ expect(CapNotification.error).toHaveBeenCalledWith({ message: 'Failed to unarchive templates' });
993
+ });
994
+ });
995
+
996
+ describe('archive watcher sagas', () => {
997
+ it('watchArchiveTemplate should take latest ARCHIVE_TEMPLATE_REQUEST', () => {
998
+ const gen = watchArchiveTemplate();
999
+ expect(gen.next().value).toEqual(takeLatest(types.ARCHIVE_TEMPLATE_REQUEST, archiveTemplateSaga));
1000
+ });
1001
+
1002
+ it('watchUnarchiveTemplate should take latest UNARCHIVE_TEMPLATE_REQUEST', () => {
1003
+ const gen = watchUnarchiveTemplate();
1004
+ expect(gen.next().value).toEqual(takeLatest(types.UNARCHIVE_TEMPLATE_REQUEST, unarchiveTemplateSaga));
1005
+ });
1006
+
1007
+ it('watchBulkArchive should take latest BULK_ARCHIVE_REQUEST', () => {
1008
+ const gen = watchBulkArchive();
1009
+ expect(gen.next().value).toEqual(takeLatest(types.BULK_ARCHIVE_REQUEST, bulkArchiveTemplatesSaga));
1010
+ });
1011
+
1012
+ it('watchBulkUnarchive should take latest BULK_UNARCHIVE_REQUEST', () => {
1013
+ const gen = watchBulkUnarchive();
1014
+ expect(gen.next().value).toEqual(takeLatest(types.BULK_UNARCHIVE_REQUEST, bulkUnarchiveTemplatesSaga));
1015
+ });
1016
+ });
1017
+
1018
+ describe('fetchWeCrmAccounts failure', () => {
1019
+ it('should dispatch GET_WECRM_ACCOUNTS_FAILURE on error', () => {
1020
+ const action = { source: 'test-source' };
1021
+ const gen = fetchWeCrmAccounts(action);
1022
+ gen.next(); // call fetchWeCrmAccounts
1023
+ const error = new Error('fetch failed');
1024
+ expect(gen.throw(error).value).toEqual(
1025
+ put({ type: types.GET_WECRM_ACCOUNTS_FAILURE, data: error })
1026
+ );
1027
+ });
1028
+ });
1029
+
1030
+ describe('sendZippedFile saga', () => {
1031
+ it('should call errorHandler and return when result.status.isError is true', () => {
1032
+ const mockErrorHandler = jest.fn();
1033
+ const mockSuccessHandler = jest.fn();
1034
+ const action = { selectedFile: 'file.zip', errorHandler: mockErrorHandler, successHandler: mockSuccessHandler };
1035
+ const gen = sendZippedFile(action);
1036
+ gen.next(); // call Api.sendZippedFile
1037
+ const result = { status: { isError: true }, message: 'upload error' };
1038
+ // advance past yield call — enters isError branch
1039
+ const step = gen.next(result);
1040
+ // errorMessage = result.message; yield errorHandler(errorMessage)
1041
+ // errorHandler returns undefined so yielded value is undefined
1042
+ expect(step.value).toBeUndefined(); // yield errorHandler('upload error')
1043
+ const done = gen.next(); // return
1044
+ expect(done.done).toBe(true);
1045
+ });
1046
+
1047
+ it('should call successHandler after successful upload', () => {
1048
+ const mockErrorHandler = jest.fn();
1049
+ const mockSuccessHandler = jest.fn();
1050
+ const action = { selectedFile: 'file.zip', errorHandler: mockErrorHandler, successHandler: mockSuccessHandler };
1051
+ const gen = sendZippedFile(action);
1052
+ gen.next(); // call Api.sendZippedFile
1053
+ const result = {
1054
+ status: { isError: false },
1055
+ response: { metaEntity: { htmlContent: encodeURIComponent('<html></html>') } },
1056
+ };
1057
+ gen.next(result); // advance past isError check, put SEND_ZIPPED_FILE_SUCCESS
1058
+ gen.next(); // advance to successHandler yield
1059
+ const done = gen.next();
1060
+ expect(done.done).toBe(true);
1061
+ });
1062
+
1063
+ it('should call errorHandler and put SEND_ZIPPED_FILE_FAILURE on exception', () => {
1064
+ const mockErrorHandler = jest.fn();
1065
+ const mockSuccessHandler = jest.fn();
1066
+ const action = { selectedFile: 'file.zip', errorHandler: mockErrorHandler, successHandler: mockSuccessHandler };
1067
+ const gen = sendZippedFile(action);
1068
+ gen.next(); // call Api.sendZippedFile
1069
+ const error = new Error('network error');
1070
+ // throw error — enters catch: yield errorHandler()
1071
+ const step1 = gen.throw(error);
1072
+ // errorHandler returns undefined so yielded value is undefined
1073
+ expect(step1.value).toBeUndefined(); // yield errorHandler()
1074
+ // yield put SEND_ZIPPED_FILE_FAILURE
1075
+ const step2 = gen.next();
1076
+ expect(step2.value).toEqual(
1077
+ put({ type: types.SEND_ZIPPED_FILE_FAILURE, data: '' })
1078
+ );
1079
+ });
1080
+ });
1081
+
1082
+ describe('archive sagas - CapNotification.error coverage', () => {
1083
+ it('archiveTemplateSaga should call CapNotification.error on failure', () => {
1084
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
1085
+ const gen = archiveTemplateSaga({ channel: 'EMAIL', id: 'id1', templateName: 'Test' });
1086
+ gen.next(); // call archiveTemplate
1087
+ const error = new Error('archive failed');
1088
+ gen.throw(error); // put ARCHIVE_TEMPLATE_FAILURE
1089
+ expect(CapNotification.error).toBeDefined();
1090
+ });
1091
+
1092
+ it('unarchiveTemplateSaga should call CapNotification.error on failure', () => {
1093
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
1094
+ const gen = unarchiveTemplateSaga({ channel: 'EMAIL', id: 'id1', templateName: 'Test' });
1095
+ gen.next(); // call unarchiveTemplate
1096
+ const error = new Error('unarchive failed');
1097
+ gen.throw(error); // put UNARCHIVE_TEMPLATE_FAILURE
1098
+ expect(CapNotification.error).toBeDefined();
1099
+ });
1100
+
1101
+ it('bulkArchiveTemplatesSaga should call CapNotification.error on failure', () => {
1102
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
1103
+ const gen = bulkArchiveTemplatesSaga({ channel: 'EMAIL', ids: ['id1'] });
1104
+ gen.next(); // call bulkArchiveTemplates
1105
+ const error = new Error('bulk archive failed');
1106
+ gen.throw(error); // put BULK_ARCHIVE_FAILURE
1107
+ expect(CapNotification.error).toBeDefined();
1108
+ });
1109
+
1110
+ it('bulkUnarchiveTemplatesSaga should call CapNotification.error on failure', () => {
1111
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
1112
+ const gen = bulkUnarchiveTemplatesSaga({ channel: 'EMAIL', ids: ['id1'] });
1113
+ gen.next(); // call bulkUnarchiveTemplates
1114
+ const error = new Error('bulk unarchive failed');
1115
+ gen.throw(error); // put BULK_UNARCHIVE_FAILURE
1116
+ expect(CapNotification.error).toBeDefined();
1117
+ });
1118
+ });
1119
+
1120
+ describe('watchGetSenderDetails', () => {
1121
+ it('should take latest GET_SENDER_DETAILS_REQUEST', () => {
1122
+ const gen = watchGetSenderDetails();
1123
+ expect(gen.next().value).toEqual(takeLatest(types.GET_SENDER_DETAILS_REQUEST, getSenderDetails));
1124
+ });
1125
+ });
1126
+
1127
+ describe('v2TemplateSaga', () => {
1128
+ it('should yield all watchers including archive watchers', () => {
1129
+ const gen = v2TemplateSaga();
1130
+ const step = gen.next();
1131
+ // all() returns an IO object with an ALL key containing the array of effects
1132
+ expect(step.value).toHaveProperty('@@redux-saga/IO', true);
1133
+ expect(step.value).toHaveProperty('ALL');
1134
+ expect(step.value.ALL).toHaveLength(14);
1135
+ expect(step.done).toBe(false);
1136
+ });
1137
+ });
1138
+
1139
+ describe('v2TemplateSagaWatchGetDefaultBeeTemplates', () => {
1140
+ it('should yield all with watchSendingFile and watchGetDefaultBeeTemplates', () => {
1141
+ const gen = v2TemplateSagaWatchGetDefaultBeeTemplates();
1142
+ const step = gen.next();
1143
+ expect(step.value).toHaveProperty('@@redux-saga/IO', true);
1144
+ expect(step.value).toHaveProperty('ALL');
1145
+ expect(step.value.ALL).toHaveLength(2);
1146
+ expect(step.done).toBe(false);
1147
+ });
1148
+ });
@@ -1,5 +1,6 @@
1
1
  import { fromJS } from "immutable";
2
2
  import { makeSelectTemplatesResponse } from "../selectors";
3
+ import { selectArchiveFilter, selectSelectedTemplateIds } from "../selectors";
3
4
 
4
5
  describe("Template selectors", () => {
5
6
  const mockState = fromJS({
@@ -15,3 +16,34 @@ describe("Template selectors", () => {
15
16
  });
16
17
  });
17
18
  });
19
+
20
+ describe("selectArchiveFilter selector", () => {
21
+ it("should return archiveFilter from state", () => {
22
+ const state = fromJS({
23
+ templates: { archiveFilter: 'archived' },
24
+ });
25
+ const result = selectArchiveFilter().resultFunc(state.get('templates').toJS());
26
+ expect(result).toBe('archived');
27
+ });
28
+
29
+ it("should default to 'active' when archiveFilter is not set", () => {
30
+ const result = selectArchiveFilter().resultFunc({});
31
+ expect(result).toBe('active');
32
+ });
33
+ });
34
+
35
+ describe("selectSelectedTemplateIds selector", () => {
36
+ it("should return selectedTemplateIds from state", () => {
37
+ const ids = ['id1', 'id2'];
38
+ const state = fromJS({
39
+ templates: { selectedTemplateIds: ids },
40
+ });
41
+ const result = selectSelectedTemplateIds().resultFunc(state.get('templates').toJS());
42
+ expect(result).toEqual(ids);
43
+ });
44
+
45
+ it("should default to [] when selectedTemplateIds is not set", () => {
46
+ const result = selectSelectedTemplateIds().resultFunc({});
47
+ expect(result).toEqual([]);
48
+ });
49
+ });