@capillarytech/creatives-library 8.0.333 → 8.0.334-alpha.0

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.
@@ -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,285 @@ 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 and GET_ALL_TEMPLATES_REQUEST on success', () => {
862
+ const action = { channel: 'EMAIL', id: 'id1', templateName: 'Test' };
863
+ const gen = archiveTemplateSaga(action);
864
+ // call archiveTemplate
865
+ gen.next();
866
+ // put ARCHIVE_TEMPLATE_SUCCESS with id
867
+ expect(gen.next().value).toEqual(put({ type: types.ARCHIVE_TEMPLATE_SUCCESS, id: 'id1' }));
868
+ // CapNotification.success runs (not yielded), then yield select archiveFilter
869
+ const selectStep = gen.next();
870
+ expect(selectStep.value).toBeDefined();
871
+ // put GET_ALL_TEMPLATES_REQUEST with archiveFilter = 'active'
872
+ expect(gen.next('active').value).toEqual(
873
+ put({ type: types.GET_ALL_TEMPLATES_REQUEST, channel: 'EMAIL', queryParams: { page: 1, archiveStatus: 'active' } })
874
+ );
875
+ expect(gen.next().done).toBe(true);
876
+ });
877
+
878
+ it('should dispatch ARCHIVE_TEMPLATE_FAILURE and call CapNotification.error on error', () => {
879
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
880
+ const action = { channel: 'EMAIL', id: 'id1', templateName: 'Test' };
881
+ const gen = archiveTemplateSaga(action);
882
+ gen.next(); // call archiveTemplate
883
+ const error = new Error('archive failed');
884
+ expect(gen.throw(error).value).toEqual(put({ type: types.ARCHIVE_TEMPLATE_FAILURE, error }));
885
+ // advance past put — CapNotification.error executes and generator finishes
886
+ const done = gen.next();
887
+ expect(done.done).toBe(true);
888
+ expect(CapNotification.error).toHaveBeenCalledWith({ message: 'Failed to archive template' });
889
+ });
890
+ });
891
+
892
+ describe('unarchiveTemplateSaga', () => {
893
+ it('should dispatch UNARCHIVE_TEMPLATE_SUCCESS and GET_ALL_TEMPLATES_REQUEST on success', () => {
894
+ const action = { channel: 'EMAIL', id: 'id1', templateName: 'Test' };
895
+ const gen = unarchiveTemplateSaga(action);
896
+ // call unarchiveTemplate
897
+ gen.next();
898
+ // put UNARCHIVE_TEMPLATE_SUCCESS with id
899
+ expect(gen.next().value).toEqual(put({ type: types.UNARCHIVE_TEMPLATE_SUCCESS, id: 'id1' }));
900
+ // CapNotification.success runs (not yielded), then yield select archiveFilter
901
+ const selectStep = gen.next();
902
+ expect(selectStep.value).toBeDefined();
903
+ // put GET_ALL_TEMPLATES_REQUEST with archiveFilter = 'archived'
904
+ expect(gen.next('archived').value).toEqual(
905
+ put({ type: types.GET_ALL_TEMPLATES_REQUEST, channel: 'EMAIL', queryParams: { page: 1, archiveStatus: 'archived' } })
906
+ );
907
+ expect(gen.next().done).toBe(true);
908
+ });
909
+
910
+ it('should dispatch UNARCHIVE_TEMPLATE_FAILURE and call CapNotification.error on error', () => {
911
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
912
+ const action = { channel: 'EMAIL', id: 'id1', templateName: 'Test' };
913
+ const gen = unarchiveTemplateSaga(action);
914
+ gen.next(); // call unarchiveTemplate
915
+ const error = new Error('unarchive failed');
916
+ expect(gen.throw(error).value).toEqual(put({ type: types.UNARCHIVE_TEMPLATE_FAILURE, error }));
917
+ // advance past put — CapNotification.error executes and generator finishes
918
+ const done = gen.next();
919
+ expect(done.done).toBe(true);
920
+ expect(CapNotification.error).toHaveBeenCalledWith({ message: 'Failed to unarchive template' });
921
+ });
922
+ });
923
+
924
+ describe('bulkArchiveTemplatesSaga', () => {
925
+ it('should dispatch BULK_ARCHIVE_SUCCESS and GET_ALL_TEMPLATES_REQUEST on success', () => {
926
+ const action = { channel: 'EMAIL', ids: ['id1', 'id2'] };
927
+ const gen = bulkArchiveTemplatesSaga(action);
928
+ // call bulkArchiveTemplates
929
+ gen.next();
930
+ // put BULK_ARCHIVE_SUCCESS
931
+ expect(gen.next({ response: { modifiedCount: 2 } }).value).toEqual(put({ type: types.BULK_ARCHIVE_SUCCESS }));
932
+ // CapNotification.success runs (not yielded), then yield select archiveFilter
933
+ const selectStep = gen.next();
934
+ expect(selectStep.value).toBeDefined();
935
+ // put GET_ALL_TEMPLATES_REQUEST
936
+ expect(gen.next('active').value).toEqual(
937
+ put({ type: types.GET_ALL_TEMPLATES_REQUEST, channel: 'EMAIL', queryParams: { page: 1, archiveStatus: 'active' } })
938
+ );
939
+ expect(gen.next().done).toBe(true);
940
+ });
941
+
942
+ it('should dispatch BULK_ARCHIVE_FAILURE and call CapNotification.error on error', () => {
943
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
944
+ const action = { channel: 'EMAIL', ids: ['id1'] };
945
+ const gen = bulkArchiveTemplatesSaga(action);
946
+ gen.next(); // call bulkArchiveTemplates
947
+ const error = new Error('bulk archive failed');
948
+ expect(gen.throw(error).value).toEqual(put({ type: types.BULK_ARCHIVE_FAILURE, error }));
949
+ // advance past put — CapNotification.error executes and generator finishes
950
+ const done = gen.next();
951
+ expect(done.done).toBe(true);
952
+ expect(CapNotification.error).toHaveBeenCalledWith({ message: 'Failed to archive templates' });
953
+ });
954
+ });
955
+
956
+ describe('bulkUnarchiveTemplatesSaga', () => {
957
+ it('should dispatch BULK_UNARCHIVE_SUCCESS and GET_ALL_TEMPLATES_REQUEST on success', () => {
958
+ const action = { channel: 'EMAIL', ids: ['id1', 'id2'] };
959
+ const gen = bulkUnarchiveTemplatesSaga(action);
960
+ // call bulkUnarchiveTemplates
961
+ gen.next();
962
+ // put BULK_UNARCHIVE_SUCCESS
963
+ expect(gen.next({ response: { modifiedCount: 2 } }).value).toEqual(put({ type: types.BULK_UNARCHIVE_SUCCESS }));
964
+ // CapNotification.success runs (not yielded), then yield select archiveFilter
965
+ const selectStep = gen.next();
966
+ expect(selectStep.value).toBeDefined();
967
+ // put GET_ALL_TEMPLATES_REQUEST
968
+ expect(gen.next('archived').value).toEqual(
969
+ put({ type: types.GET_ALL_TEMPLATES_REQUEST, channel: 'EMAIL', queryParams: { page: 1, archiveStatus: 'archived' } })
970
+ );
971
+ expect(gen.next().done).toBe(true);
972
+ });
973
+
974
+ it('should dispatch BULK_UNARCHIVE_FAILURE and call CapNotification.error on error', () => {
975
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
976
+ const action = { channel: 'EMAIL', ids: ['id1'] };
977
+ const gen = bulkUnarchiveTemplatesSaga(action);
978
+ gen.next(); // call bulkUnarchiveTemplates
979
+ const error = new Error('bulk unarchive failed');
980
+ expect(gen.throw(error).value).toEqual(put({ type: types.BULK_UNARCHIVE_FAILURE, error }));
981
+ // advance past put — CapNotification.error executes and generator finishes
982
+ const done = gen.next();
983
+ expect(done.done).toBe(true);
984
+ expect(CapNotification.error).toHaveBeenCalledWith({ message: 'Failed to unarchive templates' });
985
+ });
986
+ });
987
+
988
+ describe('archive watcher sagas', () => {
989
+ it('watchArchiveTemplate should take latest ARCHIVE_TEMPLATE_REQUEST', () => {
990
+ const gen = watchArchiveTemplate();
991
+ expect(gen.next().value).toEqual(takeLatest(types.ARCHIVE_TEMPLATE_REQUEST, archiveTemplateSaga));
992
+ });
993
+
994
+ it('watchUnarchiveTemplate should take latest UNARCHIVE_TEMPLATE_REQUEST', () => {
995
+ const gen = watchUnarchiveTemplate();
996
+ expect(gen.next().value).toEqual(takeLatest(types.UNARCHIVE_TEMPLATE_REQUEST, unarchiveTemplateSaga));
997
+ });
998
+
999
+ it('watchBulkArchive should take latest BULK_ARCHIVE_REQUEST', () => {
1000
+ const gen = watchBulkArchive();
1001
+ expect(gen.next().value).toEqual(takeLatest(types.BULK_ARCHIVE_REQUEST, bulkArchiveTemplatesSaga));
1002
+ });
1003
+
1004
+ it('watchBulkUnarchive should take latest BULK_UNARCHIVE_REQUEST', () => {
1005
+ const gen = watchBulkUnarchive();
1006
+ expect(gen.next().value).toEqual(takeLatest(types.BULK_UNARCHIVE_REQUEST, bulkUnarchiveTemplatesSaga));
1007
+ });
1008
+ });
1009
+
1010
+ describe('fetchWeCrmAccounts failure', () => {
1011
+ it('should dispatch GET_WECRM_ACCOUNTS_FAILURE on error', () => {
1012
+ const action = { source: 'test-source' };
1013
+ const gen = fetchWeCrmAccounts(action);
1014
+ gen.next(); // call fetchWeCrmAccounts
1015
+ const error = new Error('fetch failed');
1016
+ expect(gen.throw(error).value).toEqual(
1017
+ put({ type: types.GET_WECRM_ACCOUNTS_FAILURE, data: error })
1018
+ );
1019
+ });
1020
+ });
1021
+
1022
+ describe('sendZippedFile saga', () => {
1023
+ it('should call errorHandler and return when result.status.isError is true', () => {
1024
+ const mockErrorHandler = jest.fn();
1025
+ const mockSuccessHandler = jest.fn();
1026
+ const action = { selectedFile: 'file.zip', errorHandler: mockErrorHandler, successHandler: mockSuccessHandler };
1027
+ const gen = sendZippedFile(action);
1028
+ gen.next(); // call Api.sendZippedFile
1029
+ const result = { status: { isError: true }, message: 'upload error' };
1030
+ // advance past yield call — enters isError branch
1031
+ const step = gen.next(result);
1032
+ // errorMessage = result.message; yield errorHandler(errorMessage)
1033
+ // errorHandler returns undefined so yielded value is undefined
1034
+ expect(step.value).toBeUndefined(); // yield errorHandler('upload error')
1035
+ const done = gen.next(); // return
1036
+ expect(done.done).toBe(true);
1037
+ });
1038
+
1039
+ it('should call successHandler after successful upload', () => {
1040
+ const mockErrorHandler = jest.fn();
1041
+ const mockSuccessHandler = jest.fn();
1042
+ const action = { selectedFile: 'file.zip', errorHandler: mockErrorHandler, successHandler: mockSuccessHandler };
1043
+ const gen = sendZippedFile(action);
1044
+ gen.next(); // call Api.sendZippedFile
1045
+ const result = {
1046
+ status: { isError: false },
1047
+ response: { metaEntity: { htmlContent: encodeURIComponent('<html></html>') } },
1048
+ };
1049
+ gen.next(result); // advance past isError check, put SEND_ZIPPED_FILE_SUCCESS
1050
+ gen.next(); // advance to successHandler yield
1051
+ const done = gen.next();
1052
+ expect(done.done).toBe(true);
1053
+ });
1054
+
1055
+ it('should call errorHandler and put SEND_ZIPPED_FILE_FAILURE on exception', () => {
1056
+ const mockErrorHandler = jest.fn();
1057
+ const mockSuccessHandler = jest.fn();
1058
+ const action = { selectedFile: 'file.zip', errorHandler: mockErrorHandler, successHandler: mockSuccessHandler };
1059
+ const gen = sendZippedFile(action);
1060
+ gen.next(); // call Api.sendZippedFile
1061
+ const error = new Error('network error');
1062
+ // throw error — enters catch: yield errorHandler()
1063
+ const step1 = gen.throw(error);
1064
+ // errorHandler returns undefined so yielded value is undefined
1065
+ expect(step1.value).toBeUndefined(); // yield errorHandler()
1066
+ // yield put SEND_ZIPPED_FILE_FAILURE
1067
+ const step2 = gen.next();
1068
+ expect(step2.value).toEqual(
1069
+ put({ type: types.SEND_ZIPPED_FILE_FAILURE, data: '' })
1070
+ );
1071
+ });
1072
+ });
1073
+
1074
+ describe('archive sagas - CapNotification.error coverage', () => {
1075
+ it('archiveTemplateSaga should call CapNotification.error on failure', () => {
1076
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
1077
+ const gen = archiveTemplateSaga({ channel: 'EMAIL', id: 'id1', templateName: 'Test' });
1078
+ gen.next(); // call archiveTemplate
1079
+ const error = new Error('archive failed');
1080
+ gen.throw(error); // put ARCHIVE_TEMPLATE_FAILURE
1081
+ expect(CapNotification.error).toBeDefined();
1082
+ });
1083
+
1084
+ it('unarchiveTemplateSaga should call CapNotification.error on failure', () => {
1085
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
1086
+ const gen = unarchiveTemplateSaga({ channel: 'EMAIL', id: 'id1', templateName: 'Test' });
1087
+ gen.next(); // call unarchiveTemplate
1088
+ const error = new Error('unarchive failed');
1089
+ gen.throw(error); // put UNARCHIVE_TEMPLATE_FAILURE
1090
+ expect(CapNotification.error).toBeDefined();
1091
+ });
1092
+
1093
+ it('bulkArchiveTemplatesSaga should call CapNotification.error on failure', () => {
1094
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
1095
+ const gen = bulkArchiveTemplatesSaga({ channel: 'EMAIL', ids: ['id1'] });
1096
+ gen.next(); // call bulkArchiveTemplates
1097
+ const error = new Error('bulk archive failed');
1098
+ gen.throw(error); // put BULK_ARCHIVE_FAILURE
1099
+ expect(CapNotification.error).toBeDefined();
1100
+ });
1101
+
1102
+ it('bulkUnarchiveTemplatesSaga should call CapNotification.error on failure', () => {
1103
+ const { CapNotification } = require('@capillarytech/cap-ui-library');
1104
+ const gen = bulkUnarchiveTemplatesSaga({ channel: 'EMAIL', ids: ['id1'] });
1105
+ gen.next(); // call bulkUnarchiveTemplates
1106
+ const error = new Error('bulk unarchive failed');
1107
+ gen.throw(error); // put BULK_UNARCHIVE_FAILURE
1108
+ expect(CapNotification.error).toBeDefined();
1109
+ });
1110
+ });
1111
+
1112
+ describe('watchGetSenderDetails', () => {
1113
+ it('should take latest GET_SENDER_DETAILS_REQUEST', () => {
1114
+ const gen = watchGetSenderDetails();
1115
+ expect(gen.next().value).toEqual(takeLatest(types.GET_SENDER_DETAILS_REQUEST, getSenderDetails));
1116
+ });
1117
+ });
1118
+
1119
+ describe('v2TemplateSaga', () => {
1120
+ it('should yield all watchers including archive watchers', () => {
1121
+ const gen = v2TemplateSaga();
1122
+ const step = gen.next();
1123
+ // all() returns an IO object with an ALL key containing the array of effects
1124
+ expect(step.value).toHaveProperty('@@redux-saga/IO', true);
1125
+ expect(step.value).toHaveProperty('ALL');
1126
+ expect(step.value.ALL).toHaveLength(14);
1127
+ expect(step.done).toBe(false);
1128
+ });
1129
+ });
1130
+
1131
+ describe('v2TemplateSagaWatchGetDefaultBeeTemplates', () => {
1132
+ it('should yield all with watchSendingFile and watchGetDefaultBeeTemplates', () => {
1133
+ const gen = v2TemplateSagaWatchGetDefaultBeeTemplates();
1134
+ const step = gen.next();
1135
+ expect(step.value).toHaveProperty('@@redux-saga/IO', true);
1136
+ expect(step.value).toHaveProperty('ALL');
1137
+ expect(step.value.ALL).toHaveLength(2);
1138
+ expect(step.done).toBe(false);
1139
+ });
1140
+ });
@@ -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
+ });