@capillarytech/creatives-library 8.0.340 → 8.0.341

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.340",
4
+ "version": "8.0.341",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -3,6 +3,7 @@ import React from 'react';
3
3
  import CapHeading from "@capillarytech/cap-ui-library/CapHeading";
4
4
  import CapLabel from "@capillarytech/cap-ui-library/CapLabel";
5
5
  import messages from "./messages";
6
+ import { createAsyncAssetUploadConstants } from '../../utils/asyncAssetUpload';
6
7
 
7
8
  export const DEFAULT_ACTION = 'app/v2Containers/Viber/DEFAULT_ACTION';
8
9
  export const ALLOWED_EXTENSIONS_REGEX = (/\.(jpe?g|png)$/i);
@@ -16,6 +17,13 @@ export const UPLOAD_VIBER_ASSET_REQUEST = 'app/v2Containers/Viber/UPLOAD_ASSET_R
16
17
  export const UPLOAD_VIBER_ASSET_SUCCESS = 'app/v2Containers/Viber/UPLOAD_ASSET_SUCCESS';
17
18
  export const UPLOAD_VIBER_ASSET_FAILURE = 'app/v2Containers/Viber/UPLOAD_ASSET_FAILURE';
18
19
 
20
+ const viberAsyncPrefix = 'app/v2Containers/Viber';
21
+ const asyncUploadConstants = createAsyncAssetUploadConstants('VIBER', viberAsyncPrefix);
22
+ export const UPLOAD_VIBER_ASSET_PROCESSING = asyncUploadConstants.UPLOAD_VIBER_ASSET_PROCESSING;
23
+ export const UPLOAD_VIBER_ASSET_COMPLETED = asyncUploadConstants.UPLOAD_VIBER_ASSET_COMPLETED;
24
+ export const UPLOAD_VIBER_ASSET_FAILED = asyncUploadConstants.UPLOAD_VIBER_ASSET_FAILED;
25
+ export const UPLOAD_VIBER_ASSET_TIMEOUT = asyncUploadConstants.UPLOAD_VIBER_ASSET_TIMEOUT;
26
+
19
27
 
20
28
  export const CREATE_VIBER_TEMPLATE_REQUEST = 'app/v2Containers/Viber/CREATE_VIBER_TEMPLATE_REQUEST';
21
29
  export const CREATE_VIBER_TEMPLATE_SUCCESS = 'app/v2Containers/Viber/CREATE_VIBER_TEMPLATE_SUCCESS';
@@ -419,6 +419,7 @@ export const Viber = (props) => {
419
419
  channel={VIBER}
420
420
  errorMessage={formatMessage(messages.videoErrorMessage)}
421
421
  showVideoNameAndDuration
422
+ assetUploading={viber?.assetUploading}
422
423
  />
423
424
  );
424
425
 
@@ -795,6 +796,10 @@ export const Viber = (props) => {
795
796
  ) {
796
797
  return true;
797
798
  }
799
+ // block submit while a media asset upload is still in progress
800
+ if ((isMediaTypeImage || isMediaTypeVideo) && viber?.assetUploading) {
801
+ return true;
802
+ }
798
803
  if (isBtnTypeCta && !isCtaSaved) {
799
804
  return true;
800
805
  }
@@ -6,51 +6,71 @@
6
6
 
7
7
  import { fromJS } from 'immutable';
8
8
  import * as types from './constants';
9
+ import { createAsyncAssetUploadReducerCases } from '../../utils/asyncAssetUpload';
10
+
11
+ const asyncUploadCases = createAsyncAssetUploadReducerCases({
12
+ PROCESSING: types.UPLOAD_VIBER_ASSET_PROCESSING,
13
+ COMPLETED: types.UPLOAD_VIBER_ASSET_COMPLETED,
14
+ FAILED: types.UPLOAD_VIBER_ASSET_FAILED,
15
+ TIMEOUT: types.UPLOAD_VIBER_ASSET_TIMEOUT,
16
+ });
9
17
 
10
18
  const initialState = fromJS({
11
19
  uploadedAssetData: {},
12
20
  createTemplateInProgress: false,
21
+ assetProcessing: {},
13
22
  });
14
23
 
15
24
  function viberReducer(state = initialState, action) {
16
25
  switch (action.type) {
17
26
  case types.CREATE_VIBER_TEMPLATE_REQUEST:
18
27
  return state
19
- .set('createTemplateInProgress', true)
20
- .set('createTemplateError', false)
21
- .set('createTemplateErrorMessage', fromJS(""));
28
+ .set('createTemplateInProgress', true)
29
+ .set('createTemplateError', false)
30
+ .set('createTemplateErrorMessage', fromJS(""));
22
31
  case types.CREATE_VIBER_TEMPLATE_SUCCESS:
23
32
  return state.set('createTemplateInProgress', false)
24
- .set('response', action.data)
25
- .set('createTemplateError', (action.statusCode !== undefined && action.statusCode > 300))
26
- .set('createTemplateErrorMessage', fromJS(action.errorMsg));
33
+ .set('response', action.data)
34
+ .set('createTemplateError', (action.statusCode !== undefined && action.statusCode > 300))
35
+ .set('createTemplateErrorMessage', fromJS(action.errorMsg));
27
36
  case types.CREATE_VIBER_TEMPLATE_FAILURE:
28
37
  return state
29
- .set('createTemplateInProgress', false)
30
- .set('createTemplateError', true)
31
- .set('createTemplateErrorMessage', fromJS(action.errorMsg));
38
+ .set('createTemplateInProgress', false)
39
+ .set('createTemplateError', true)
40
+ .set('createTemplateErrorMessage', fromJS(action.errorMsg));
32
41
  case types.UPLOAD_VIBER_ASSET_REQUEST:
33
42
  return state
34
- .set('uploadAssetSuccess', false)
35
- .set('assetUploading', true);
43
+ .set('uploadAssetSuccess', false)
44
+ .set('assetUploading', true);
36
45
  case types.UPLOAD_VIBER_ASSET_SUCCESS:
37
46
  return state
38
- .set('uploadAssetSuccess', (action.statusCode !== undefined && action.statusCode !== '' && action.statusCode < 300))
39
- .set('assetUploading', false)
40
- .set(action.templateType !== undefined ? `uploadedAssetData${action.templateType}` : 'uploadedAssetData', action.data);
47
+ .set('uploadAssetSuccess', (action.statusCode !== undefined && action.statusCode !== '' && action.statusCode < 300))
48
+ .set('assetUploading', false)
49
+ .set(action.templateType !== undefined ? `uploadedAssetData${action.templateType}` : 'uploadedAssetData', fromJS(action.data));
41
50
  case types.UPLOAD_VIBER_ASSET_FAILURE:
42
51
  return state
43
- .set('uploadAssetSuccess', false)
44
- .set('assetUploading', false);
52
+ .set('uploadAssetSuccess', false)
53
+ .set('assetUploading', false);
54
+ case asyncUploadCases.PROCESSING:
55
+ return asyncUploadCases.handleProcessing(state, action);
56
+ case asyncUploadCases.COMPLETED:
57
+ return asyncUploadCases.handleCompleted(state, action);
58
+ case asyncUploadCases.FAILED:
59
+ return asyncUploadCases.handleFailed(state, action);
60
+ case asyncUploadCases.TIMEOUT:
61
+ return asyncUploadCases.handleTimeout(state, action);
45
62
  case types.CLEAR_VIBER_ASSET:
46
63
  return state
47
- .delete(action.templateType !== undefined ? `uploadedAssetData${action.templateType}` : 'uploadedAssetData');
64
+ .delete(action.templateType !== undefined ? `uploadedAssetData${action.templateType}` : 'uploadedAssetData')
65
+ .set('assetProcessing', fromJS({}))
66
+ .set('assetUploading', false)
67
+ .set('uploadAssetSuccess', false);
48
68
  // FOR EDIT
49
69
  case types.EDIT_VIBER_TEMPLATE_REQUEST:
50
70
  return state
51
- .set('editTemplateInProgress', true)
52
- .set('editTemplateError', false)
53
- .set('editTemplateErrorMessage', fromJS(""));
71
+ .set('editTemplateInProgress', true)
72
+ .set('editTemplateError', false)
73
+ .set('editTemplateErrorMessage', fromJS(""));
54
74
  case types.EDIT_VIBER_TEMPLATE_SUCCESS:
55
75
  return state.set('editTemplateInProgress', false)
56
76
  .set('editResponse', action.data)
@@ -75,7 +95,10 @@ function viberReducer(state = initialState, action) {
75
95
  return state
76
96
  .delete('uploadedAssetData')
77
97
  .delete('metaEntities')
78
- .delete('templateDetails');
98
+ .delete('templateDetails')
99
+ .set('assetProcessing', fromJS({}))
100
+ .set('assetUploading', false)
101
+ .set('uploadAssetSuccess', false);
79
102
  default:
80
103
  return state;
81
104
  }
@@ -2,17 +2,58 @@ import {
2
2
  call, put, takeLatest, all,
3
3
  } from 'redux-saga/effects';
4
4
  import * as Api from '../../services/api';
5
+ import { pollAssetStatus } from '../../sagas/assetPolling';
6
+ import { createPollingConfig } from '../../utils/asyncAssetUpload';
7
+ import { ASSET_STATUS } from '../../utils/assetStatusConstants';
5
8
  import * as types from './constants';
6
9
 
7
- export function* uploadViberAsset(file, assetType, fileParams ) {
10
+ export function* uploadViberAsset(params) {
8
11
  try {
9
- const result = yield call(Api.uploadFile, file, assetType, fileParams);
10
- yield put({
11
- type: types.UPLOAD_VIBER_ASSET_SUCCESS,
12
- data: result.response.asset,
13
- statusCode: result.status ? result.status.code : '',
14
- templateType: file.templateType,
15
- });
12
+ const result = yield call(Api.uploadFile, params);
13
+ const responseData = result.response || {};
14
+ const statusCode = result?.status?.code || result?.statusCode || 200;
15
+
16
+ if (statusCode === 202 || responseData?.processingStatus === ASSET_STATUS.PROCESSING) {
17
+ const assetId = responseData.assetId || responseData.asset?._id;
18
+ const { templateType } = params;
19
+ const assetType = params.assetType || responseData.asset?.type?.toLowerCase() || 'image';
20
+
21
+ if (assetId) {
22
+ yield put({
23
+ type: types.UPLOAD_VIBER_ASSET_PROCESSING,
24
+ payload: {
25
+ assetId,
26
+ asset: responseData.asset,
27
+ processingStatus: ASSET_STATUS.PROCESSING,
28
+ },
29
+ });
30
+
31
+ const actionTypes = {
32
+ COMPLETED: types.UPLOAD_VIBER_ASSET_COMPLETED,
33
+ FAILED: types.UPLOAD_VIBER_ASSET_FAILED,
34
+ TIMEOUT: types.UPLOAD_VIBER_ASSET_TIMEOUT,
35
+ };
36
+ const pollingConfig = createPollingConfig(assetType, assetId, actionTypes, templateType);
37
+ yield call(pollAssetStatus, pollingConfig);
38
+ } else {
39
+ const errorMessage = 'Asset upload initiated but no asset ID was returned from the server. Unable to track processing status.';
40
+ yield put({
41
+ type: types.UPLOAD_VIBER_ASSET_FAILED,
42
+ payload: {
43
+ assetId: undefined,
44
+ error: errorMessage,
45
+ },
46
+ templateType,
47
+ });
48
+ }
49
+ } else {
50
+ yield put({
51
+ type: types.UPLOAD_VIBER_ASSET_SUCCESS,
52
+ data: responseData.asset,
53
+ statusCode,
54
+ templateType: params.templateType,
55
+ });
56
+ }
16
57
  } catch (error) {
17
58
  yield put({ type: types.UPLOAD_VIBER_ASSET_FAILURE, error });
18
59
  }
@@ -26,7 +67,12 @@ export function* createViberTemplate({ template, callback }) {
26
67
  errorMsg = result.message;
27
68
  throw errorMsg;
28
69
  }
29
- yield put({ type: types.CREATE_VIBER_TEMPLATE_SUCCESS, data: result.response, statusCode: result.status ? result.status.code : '', errorMsg });
70
+ yield put({
71
+ type: types.CREATE_VIBER_TEMPLATE_SUCCESS,
72
+ data: result.response,
73
+ statusCode: result.status ? result.status.code : '',
74
+ errorMsg,
75
+ });
30
76
  if (callback) {
31
77
  callback(result.response);
32
78
  }
@@ -50,7 +96,12 @@ export function* editTemplate({ template, callback }) {
50
96
  if (callback) {
51
97
  callback(result.response);
52
98
  }
53
- yield put({ type: types.EDIT_VIBER_TEMPLATE_SUCCESS, data: result.response, statusCode: result.status ? result.status.code : '', errorMsg });
99
+ yield put({
100
+ type: types.EDIT_VIBER_TEMPLATE_SUCCESS,
101
+ data: result.response,
102
+ statusCode: result.status ? result.status.code : '',
103
+ errorMsg,
104
+ });
54
105
  } catch (error) {
55
106
  yield put({ type: types.EDIT_VIBER_TEMPLATE_FAILURE, error, errorMsg });
56
107
  if (callback) {
@@ -61,7 +112,6 @@ export function* editTemplate({ template, callback }) {
61
112
 
62
113
  export function* getTemplateDetails({id, callback}) {
63
114
  try {
64
-
65
115
  const result = yield call(Api.getTemplateDetails, {id, channel: 'VIBER'});
66
116
  yield put({ type: types.GET_VIBER_TEMPLATE_DETAILS_SUCCESS, data: result.response });
67
117
  if (callback) {
@@ -75,28 +125,19 @@ export function* getTemplateDetails({id, callback}) {
75
125
 
76
126
  function* watchUploadViberAsset() {
77
127
  yield takeLatest(types.UPLOAD_VIBER_ASSET_REQUEST, uploadViberAsset);
78
-
79
-
80
128
  }
81
129
 
82
130
 
83
131
  function* watchCreateTemplate() {
84
-
85
132
  yield takeLatest(types.CREATE_VIBER_TEMPLATE_REQUEST, createViberTemplate);
86
-
87
-
88
133
  }
89
134
 
90
135
  function* watchGetTemplateDetails() {
91
136
  yield takeLatest(types.GET_VIBER_TEMPLATE_DETAILS_REQUEST, getTemplateDetails);
92
-
93
-
94
137
  }
95
138
 
96
139
  function* watchEditTemplate() {
97
140
  yield takeLatest(types.EDIT_VIBER_TEMPLATE_REQUEST, editTemplate);
98
-
99
-
100
141
  }
101
142
 
102
143
  export default [
@@ -113,4 +154,4 @@ export function* v2ViberSagas() {
113
154
  watchGetTemplateDetails(),
114
155
  watchEditTemplate(),
115
156
  ]);
116
- }
157
+ }
@@ -303,4 +303,84 @@ describe('Test Viber container', () => {
303
303
  });
304
304
  expect(doneBtn).toBeDisabled();
305
305
  });
306
+
307
+ it('disables Done while a replacement video asset is uploading (finding 1)', async () => {
308
+ renderComponent({
309
+ actions: mockActions,
310
+ globalActions: mockGlobalActions,
311
+ templateData: { mode: 'create' },
312
+ viber: {
313
+ uploadedAssetData: {},
314
+ createTemplateInProgress: false,
315
+ templateDetails: templateDetailsVideo,
316
+ // a new upload has started after the template was loaded
317
+ assetUploading: true,
318
+ },
319
+ location: {
320
+ pathname: '/sms/edit',
321
+ query: { type: false, module: 'default' },
322
+ search: '',
323
+ },
324
+ isFullMode: true,
325
+ params: { id: 'test' },
326
+ handleClose: jest.fn(),
327
+ metaEntities,
328
+ getDefaultTags,
329
+ injectedTags,
330
+ });
331
+ const doneBtn = screen.getByRole('button', { name: /done/i });
332
+ expect(doneBtn).toBeDisabled();
333
+ });
334
+
335
+ it('disables Done while an image asset is uploading (finding 1)', async () => {
336
+ renderComponent({
337
+ actions: mockActions,
338
+ globalActions: mockGlobalActions,
339
+ templateData: { mode: 'create' },
340
+ viber: {
341
+ uploadedAssetData: {},
342
+ createTemplateInProgress: false,
343
+ templateDetails: templateDetailsImage,
344
+ assetUploading: true,
345
+ },
346
+ location: {
347
+ pathname: '/sms/edit',
348
+ query: { type: false, module: 'default' },
349
+ search: '',
350
+ },
351
+ isFullMode: true,
352
+ params: { id: 'test' },
353
+ handleClose: jest.fn(),
354
+ });
355
+ const doneBtn = screen.getByRole('button', { name: /done/i });
356
+ expect(doneBtn).toBeDisabled();
357
+ });
358
+
359
+ it('keeps Done enabled when assetUploading is true but media type is text', async () => {
360
+ renderComponent({
361
+ actions: mockActions,
362
+ globalActions: mockGlobalActions,
363
+ templateData: { mode: 'create' },
364
+ viber: {
365
+ uploadedAssetData: {},
366
+ createTemplateInProgress: false,
367
+ templateDetails: templateDetailsText,
368
+ // stale upload flag, but the form has no media so submission is safe
369
+ assetUploading: true,
370
+ },
371
+ location: {
372
+ pathname: '/sms/edit',
373
+ query: { type: false, module: 'default' },
374
+ search: '',
375
+ },
376
+ isFullMode: true,
377
+ params: { id: 'test' },
378
+ handleClose: jest.fn(),
379
+ metaEntities,
380
+ getDefaultTags,
381
+ injectedTags,
382
+ });
383
+ const doneBtn = screen.getByRole('button', { name: /done/i });
384
+ expect(doneBtn).toBeEnabled();
385
+ });
306
386
  });
@@ -0,0 +1,297 @@
1
+ import { fromJS } from 'immutable';
2
+ import viberReducer from '../reducer';
3
+ import * as types from '../constants';
4
+ import { ASSET_STATUS } from '../../../utils/assetStatusConstants';
5
+
6
+ const initialState = fromJS({
7
+ uploadedAssetData: {},
8
+ createTemplateInProgress: false,
9
+ assetProcessing: {},
10
+ });
11
+
12
+ describe('viberReducer', () => {
13
+ it('returns initial state for unknown action', () => {
14
+ expect(viberReducer(undefined, { type: '@@INIT' })).toEqual(initialState);
15
+ });
16
+
17
+ describe('CREATE_VIBER_TEMPLATE_*', () => {
18
+ it('handles CREATE_VIBER_TEMPLATE_REQUEST', () => {
19
+ const next = viberReducer(initialState, {
20
+ type: types.CREATE_VIBER_TEMPLATE_REQUEST,
21
+ });
22
+ expect(next.get('createTemplateInProgress')).toBe(true);
23
+ expect(next.get('createTemplateError')).toBe(false);
24
+ expect(next.get('createTemplateErrorMessage')).toEqual(fromJS(''));
25
+ });
26
+
27
+ it('handles CREATE_VIBER_TEMPLATE_SUCCESS with status > 300', () => {
28
+ const next = viberReducer(initialState, {
29
+ type: types.CREATE_VIBER_TEMPLATE_SUCCESS,
30
+ data: { id: 'x' },
31
+ statusCode: 400,
32
+ errorMsg: 'oops',
33
+ });
34
+ expect(next.get('createTemplateInProgress')).toBe(false);
35
+ expect(next.get('response')).toEqual({ id: 'x' });
36
+ expect(next.get('createTemplateError')).toBe(true);
37
+ expect(next.get('createTemplateErrorMessage')).toEqual(fromJS('oops'));
38
+ });
39
+
40
+ it('handles CREATE_VIBER_TEMPLATE_SUCCESS with status <= 300', () => {
41
+ const next = viberReducer(initialState, {
42
+ type: types.CREATE_VIBER_TEMPLATE_SUCCESS,
43
+ data: { id: 'x' },
44
+ statusCode: 200,
45
+ errorMsg: '',
46
+ });
47
+ expect(next.get('createTemplateError')).toBe(false);
48
+ });
49
+
50
+ it('handles CREATE_VIBER_TEMPLATE_FAILURE', () => {
51
+ const next = viberReducer(initialState, {
52
+ type: types.CREATE_VIBER_TEMPLATE_FAILURE,
53
+ errorMsg: 'bad',
54
+ });
55
+ expect(next.get('createTemplateInProgress')).toBe(false);
56
+ expect(next.get('createTemplateError')).toBe(true);
57
+ expect(next.get('createTemplateErrorMessage')).toEqual(fromJS('bad'));
58
+ });
59
+ });
60
+
61
+ describe('UPLOAD_VIBER_ASSET_*', () => {
62
+ it('handles UPLOAD_VIBER_ASSET_REQUEST', () => {
63
+ const next = viberReducer(initialState, {
64
+ type: types.UPLOAD_VIBER_ASSET_REQUEST,
65
+ });
66
+ expect(next.get('uploadAssetSuccess')).toBe(false);
67
+ expect(next.get('assetUploading')).toBe(true);
68
+ });
69
+
70
+ it('handles UPLOAD_VIBER_ASSET_SUCCESS and wraps data with fromJS (finding 2)', () => {
71
+ const payload = { url: 'https://cdn/x.png', meta: { size: 100 } };
72
+ const next = viberReducer(initialState, {
73
+ type: types.UPLOAD_VIBER_ASSET_SUCCESS,
74
+ data: payload,
75
+ statusCode: 200,
76
+ });
77
+ expect(next.get('assetUploading')).toBe(false);
78
+ expect(next.get('uploadAssetSuccess')).toBe(true);
79
+ // must be wrapped in Immutable (same as asyncUploadCases.handleCompleted)
80
+ const stored = next.get('uploadedAssetData');
81
+ expect(stored).toEqual(fromJS(payload));
82
+ // verify it's actually an Immutable Map, not a plain JS object
83
+ expect(stored.toJS).toBeDefined();
84
+ expect(typeof stored.toJS).toBe('function');
85
+ expect(stored.get('url')).toBe(payload.url);
86
+ });
87
+
88
+ it('handles UPLOAD_VIBER_ASSET_SUCCESS with templateType key wrapped in fromJS', () => {
89
+ const payload = { url: 'https://cdn/y.mp4' };
90
+ const next = viberReducer(initialState, {
91
+ type: types.UPLOAD_VIBER_ASSET_SUCCESS,
92
+ data: payload,
93
+ statusCode: 200,
94
+ templateType: 3,
95
+ });
96
+ const stored = next.get('uploadedAssetData3');
97
+ expect(stored).toEqual(fromJS(payload));
98
+ expect(stored.get('url')).toBe(payload.url);
99
+ });
100
+
101
+ it('UPLOAD_VIBER_ASSET_SUCCESS sets uploadAssetSuccess false when status >= 300', () => {
102
+ const next = viberReducer(initialState, {
103
+ type: types.UPLOAD_VIBER_ASSET_SUCCESS,
104
+ data: {},
105
+ statusCode: 500,
106
+ });
107
+ expect(next.get('uploadAssetSuccess')).toBe(false);
108
+ });
109
+
110
+ it('UPLOAD_VIBER_ASSET_SUCCESS sync and async produce same shape', () => {
111
+ const asset = { url: 'https://cdn/same.png' };
112
+ const sync = viberReducer(initialState, {
113
+ type: types.UPLOAD_VIBER_ASSET_SUCCESS,
114
+ data: asset,
115
+ statusCode: 200,
116
+ });
117
+ const async_ = viberReducer(initialState, {
118
+ type: types.UPLOAD_VIBER_ASSET_COMPLETED,
119
+ payload: { assetId: 'a1', asset },
120
+ });
121
+ // Both paths should store uploadedAssetData as Immutable fromJS(asset)
122
+ expect(sync.get('uploadedAssetData')).toEqual(async_.get('uploadedAssetData'));
123
+ });
124
+
125
+ it('handles UPLOAD_VIBER_ASSET_FAILURE', () => {
126
+ const next = viberReducer(initialState, {
127
+ type: types.UPLOAD_VIBER_ASSET_FAILURE,
128
+ });
129
+ expect(next.get('uploadAssetSuccess')).toBe(false);
130
+ expect(next.get('assetUploading')).toBe(false);
131
+ });
132
+ });
133
+
134
+ describe('async polling cases', () => {
135
+ it('handles UPLOAD_VIBER_ASSET_PROCESSING', () => {
136
+ const next = viberReducer(initialState, {
137
+ type: types.UPLOAD_VIBER_ASSET_PROCESSING,
138
+ payload: { assetId: 'a1', asset: { id: 'a1' } },
139
+ });
140
+ expect(next.get('assetUploading')).toBe(true);
141
+ expect(next.get('uploadAssetSuccess')).toBe(false);
142
+ expect(next.getIn(['assetProcessing', 'a1', 'status'])).toBe(ASSET_STATUS.PROCESSING);
143
+ });
144
+
145
+ it('handles UPLOAD_VIBER_ASSET_COMPLETED', () => {
146
+ const next = viberReducer(initialState, {
147
+ type: types.UPLOAD_VIBER_ASSET_COMPLETED,
148
+ payload: { assetId: 'a1', asset: { url: 'https://cdn/x.png' } },
149
+ });
150
+ expect(next.get('assetUploading')).toBe(false);
151
+ expect(next.get('uploadAssetSuccess')).toBe(true);
152
+ expect(next.getIn(['assetProcessing', 'a1', 'status'])).toBe(ASSET_STATUS.COMPLETED);
153
+ expect(next.get('uploadedAssetData')).toEqual(fromJS({ url: 'https://cdn/x.png' }));
154
+ });
155
+
156
+ it('handles UPLOAD_VIBER_ASSET_FAILED', () => {
157
+ const next = viberReducer(initialState, {
158
+ type: types.UPLOAD_VIBER_ASSET_FAILED,
159
+ payload: { assetId: 'a1', error: 'boom' },
160
+ });
161
+ expect(next.get('assetUploading')).toBe(false);
162
+ expect(next.get('uploadAssetSuccess')).toBe(false);
163
+ expect(next.getIn(['assetProcessing', 'a1', 'status'])).toBe(ASSET_STATUS.FAILED);
164
+ expect(next.getIn(['assetProcessing', 'a1', 'error'])).toBe('boom');
165
+ });
166
+
167
+ it('handles UPLOAD_VIBER_ASSET_TIMEOUT', () => {
168
+ const next = viberReducer(initialState, {
169
+ type: types.UPLOAD_VIBER_ASSET_TIMEOUT,
170
+ payload: { assetId: 'a1', message: 'timed out' },
171
+ });
172
+ expect(next.get('assetUploading')).toBe(false);
173
+ expect(next.getIn(['assetProcessing', 'a1', 'status'])).toBe(ASSET_STATUS.TIMEOUT);
174
+ expect(next.getIn(['assetProcessing', 'a1', 'error'])).toBe('timed out');
175
+ });
176
+ });
177
+
178
+ describe('CLEAR_VIBER_ASSET (finding 3)', () => {
179
+ const dirtyState = initialState
180
+ .set('assetUploading', true)
181
+ .set('uploadAssetSuccess', true)
182
+ .set('uploadedAssetData', fromJS({ url: 'x' }))
183
+ .set('uploadedAssetData2', fromJS({ url: 'y' }))
184
+ .setIn(['assetProcessing', 'a1'], fromJS({ status: 'processing' }));
185
+
186
+ it('clears default uploadedAssetData and resets upload flags', () => {
187
+ const next = viberReducer(dirtyState, { type: types.CLEAR_VIBER_ASSET });
188
+ expect(next.has('uploadedAssetData')).toBe(false);
189
+ expect(next.get('assetProcessing')).toEqual(fromJS({}));
190
+ expect(next.get('assetUploading')).toBe(false);
191
+ expect(next.get('uploadAssetSuccess')).toBe(false);
192
+ });
193
+
194
+ it('clears templateType-specific uploadedAssetData and resets upload flags', () => {
195
+ const next = viberReducer(dirtyState, {
196
+ type: types.CLEAR_VIBER_ASSET,
197
+ templateType: 2,
198
+ });
199
+ expect(next.has('uploadedAssetData2')).toBe(false);
200
+ // default key untouched by specific clear
201
+ expect(next.has('uploadedAssetData')).toBe(true);
202
+ expect(next.get('assetProcessing')).toEqual(fromJS({}));
203
+ expect(next.get('assetUploading')).toBe(false);
204
+ expect(next.get('uploadAssetSuccess')).toBe(false);
205
+ });
206
+ });
207
+
208
+ describe('CLEAR_VIBER_DATA (finding 3 mirror)', () => {
209
+ it('clears assetProcessing, assetUploading, uploadAssetSuccess', () => {
210
+ const dirtyState = initialState
211
+ .set('assetUploading', true)
212
+ .set('uploadAssetSuccess', true)
213
+ .set('metaEntities', fromJS({ some: 'data' }))
214
+ .set('templateDetails', fromJS({ id: 'x' }))
215
+ .setIn(['assetProcessing', 'a1'], fromJS({ status: 'processing' }));
216
+ const next = viberReducer(dirtyState, { type: types.CLEAR_VIBER_DATA });
217
+ expect(next.has('uploadedAssetData')).toBe(false);
218
+ expect(next.has('metaEntities')).toBe(false);
219
+ expect(next.has('templateDetails')).toBe(false);
220
+ expect(next.get('assetProcessing')).toEqual(fromJS({}));
221
+ expect(next.get('assetUploading')).toBe(false);
222
+ expect(next.get('uploadAssetSuccess')).toBe(false);
223
+ });
224
+ });
225
+
226
+ describe('EDIT_VIBER_TEMPLATE_*', () => {
227
+ it('handles EDIT_VIBER_TEMPLATE_REQUEST', () => {
228
+ const next = viberReducer(initialState, {
229
+ type: types.EDIT_VIBER_TEMPLATE_REQUEST,
230
+ });
231
+ expect(next.get('editTemplateInProgress')).toBe(true);
232
+ expect(next.get('editTemplateError')).toBe(false);
233
+ });
234
+
235
+ it('handles EDIT_VIBER_TEMPLATE_SUCCESS', () => {
236
+ const next = viberReducer(initialState, {
237
+ type: types.EDIT_VIBER_TEMPLATE_SUCCESS,
238
+ data: { id: 'e' },
239
+ statusCode: 200,
240
+ errorMsg: '',
241
+ });
242
+ expect(next.get('editTemplateInProgress')).toBe(false);
243
+ expect(next.get('editResponse')).toEqual({ id: 'e' });
244
+ expect(next.get('editTemplateError')).toBe(false);
245
+ });
246
+
247
+ it('handles EDIT_VIBER_TEMPLATE_SUCCESS error status', () => {
248
+ const next = viberReducer(initialState, {
249
+ type: types.EDIT_VIBER_TEMPLATE_SUCCESS,
250
+ data: {},
251
+ statusCode: 500,
252
+ errorMsg: 'err',
253
+ });
254
+ expect(next.get('editTemplateError')).toBe(true);
255
+ });
256
+
257
+ it('handles EDIT_VIBER_TEMPLATE_FAILURE', () => {
258
+ const next = viberReducer(initialState, {
259
+ type: types.EDIT_VIBER_TEMPLATE_FAILURE,
260
+ errorMsg: 'nope',
261
+ });
262
+ expect(next.get('editTemplateError')).toBe(true);
263
+ });
264
+ });
265
+
266
+ describe('GET/CLEAR branches', () => {
267
+ it('handles GET_VIBER_TEMPLATE_DETAILS_REQUEST', () => {
268
+ const next = viberReducer(initialState, {
269
+ type: types.GET_VIBER_TEMPLATE_DETAILS_REQUEST,
270
+ });
271
+ expect(next.get('getTemplateDetailsInProgress')).toBe(true);
272
+ });
273
+
274
+ it('handles GET_VIBER_TEMPLATE_DETAILS_SUCCESS', () => {
275
+ const next = viberReducer(initialState, {
276
+ type: types.GET_VIBER_TEMPLATE_DETAILS_SUCCESS,
277
+ data: { id: 'd' },
278
+ });
279
+ expect(next.get('getTemplateDetailsInProgress')).toBe(false);
280
+ expect(next.get('templateDetails')).toEqual({ id: 'd' });
281
+ });
282
+
283
+ it('handles GET_VIBER_TEMPLATE_DETAILS_FAILURE / CLEAR_VIBER_EDIT_RESPONSE_REQUEST', () => {
284
+ const next = viberReducer(initialState, {
285
+ type: types.CLEAR_VIBER_EDIT_RESPONSE_REQUEST,
286
+ });
287
+ expect(next.get('editResponse')).toEqual({});
288
+ });
289
+
290
+ it('handles CLEAR_VIBER_CREATE_RESPONSE_REQUEST', () => {
291
+ const next = viberReducer(initialState, {
292
+ type: types.CLEAR_VIBER_CREATE_RESPONSE_REQUEST,
293
+ });
294
+ expect(next.get('response')).toEqual({});
295
+ });
296
+ });
297
+ });
@@ -1,50 +1,258 @@
1
1
  import { expectSaga } from 'redux-saga-test-plan';
2
- import { call, put } from 'redux-saga/effects';
2
+ import * as matchers from 'redux-saga-test-plan/matchers';
3
+ import { call } from 'redux-saga/effects';
3
4
  import { throwError } from 'redux-saga-test-plan/providers';
4
5
  import * as Api from '../../../services/api';
5
6
  import * as sagas from '../sagas';
6
- import { v2ViberSagas } from '../sagas';
7
7
  import * as types from '../constants';
8
+ import { pollAssetStatus } from '../../../sagas/assetPolling';
9
+ import { ASSET_STATUS } from '../../../utils/assetStatusConstants';
8
10
 
9
11
  describe('Viber Sagas', () => {
10
-
11
12
  describe('uploadViberAsset Saga', () => {
12
- const file = new Blob(['file contents'], { type: 'text/plain' });
13
- const assetType = 'image';
14
- const fileParams = { directory: 'profile', templateType: 'viber' };
13
+ const mockAsset = {
14
+ _id: 'asset-123',
15
+ type: 'IMAGE',
16
+ url: 'https://example.com/image.jpg',
17
+ };
15
18
 
16
- it('handles uploading asset successfully', () => {
17
- const fakeResponse = {
18
- response: { asset: { id: 1, url: 'http://example.com/image.png' } },
19
- status: { code: 200 }
19
+ const mockParams = {
20
+ file: new File(['test'], 'test.jpg', { type: 'image/jpeg' }),
21
+ assetType: 'image',
22
+ fileParams: {},
23
+ templateType: 0,
24
+ };
25
+
26
+ it('handles async upload (202) with polling', () => {
27
+ const mockResponse = {
28
+ status: { code: 202 },
29
+ response: {
30
+ assetId: 'asset-123',
31
+ asset: mockAsset,
32
+ processingStatus: ASSET_STATUS.PROCESSING,
33
+ },
34
+ };
35
+
36
+ return expectSaga(sagas.uploadViberAsset, mockParams)
37
+ .provide([
38
+ [matchers.call.fn(Api.uploadFile), mockResponse],
39
+ [matchers.call.fn(pollAssetStatus), undefined],
40
+ ])
41
+ .put({
42
+ type: types.UPLOAD_VIBER_ASSET_PROCESSING,
43
+ payload: {
44
+ assetId: 'asset-123',
45
+ asset: mockAsset,
46
+ processingStatus: ASSET_STATUS.PROCESSING,
47
+ },
48
+ })
49
+ .call.fn(Api.uploadFile)
50
+ .call.fn(pollAssetStatus)
51
+ .run();
52
+ });
53
+
54
+ it('handles async when processingStatus is processing with 200 status', () => {
55
+ const mockResponse = {
56
+ status: { code: 200 },
57
+ response: {
58
+ assetId: 'asset-123',
59
+ asset: mockAsset,
60
+ processingStatus: ASSET_STATUS.PROCESSING,
61
+ },
20
62
  };
21
63
 
22
- return expectSaga(sagas.uploadViberAsset, file, assetType, fileParams)
64
+ return expectSaga(sagas.uploadViberAsset, mockParams)
23
65
  .provide([
24
- [call(Api.uploadFile, file, assetType, fileParams), fakeResponse]
66
+ [matchers.call.fn(Api.uploadFile), mockResponse],
67
+ [matchers.call.fn(pollAssetStatus), undefined],
68
+ ])
69
+ .put({
70
+ type: types.UPLOAD_VIBER_ASSET_PROCESSING,
71
+ payload: {
72
+ assetId: 'asset-123',
73
+ asset: mockAsset,
74
+ processingStatus: ASSET_STATUS.PROCESSING,
75
+ },
76
+ })
77
+ .call.fn(pollAssetStatus)
78
+ .run();
79
+ });
80
+
81
+ it('handles sync upload (201) without polling', () => {
82
+ const mockResponse = {
83
+ status: { code: 201 },
84
+ response: {
85
+ asset: mockAsset,
86
+ },
87
+ };
88
+
89
+ return expectSaga(sagas.uploadViberAsset, mockParams)
90
+ .provide([
91
+ [matchers.call.fn(Api.uploadFile), mockResponse],
25
92
  ])
26
93
  .put({
27
94
  type: types.UPLOAD_VIBER_ASSET_SUCCESS,
28
- data: fakeResponse.response.asset,
29
- statusCode: fakeResponse.status.code,
30
- templateType: undefined
95
+ data: mockAsset,
96
+ statusCode: 201,
97
+ templateType: 0,
31
98
  })
99
+ .not.call(pollAssetStatus)
32
100
  .run();
33
101
  });
34
102
 
35
- it('handles asset upload failure', () => {
36
- const error = new Error('Upload failed');
103
+ it('extracts assetId from asset._id when assetId omitted', () => {
104
+ const mockResponse = {
105
+ status: { code: 202 },
106
+ response: {
107
+ asset: { ...mockAsset, _id: 'asset-456' },
108
+ processingStatus: ASSET_STATUS.PROCESSING,
109
+ },
110
+ };
111
+
112
+ return expectSaga(sagas.uploadViberAsset, mockParams)
113
+ .provide([
114
+ [matchers.call.fn(Api.uploadFile), mockResponse],
115
+ [matchers.call.fn(pollAssetStatus), undefined],
116
+ ])
117
+ .put({
118
+ type: types.UPLOAD_VIBER_ASSET_PROCESSING,
119
+ payload: {
120
+ assetId: 'asset-456',
121
+ asset: { ...mockAsset, _id: 'asset-456' },
122
+ processingStatus: ASSET_STATUS.PROCESSING,
123
+ },
124
+ })
125
+ .run();
126
+ });
127
+
128
+ it('dispatches FAILED when assetId missing on async response', () => {
129
+ const mockResponse = {
130
+ status: { code: 202 },
131
+ response: {
132
+ processingStatus: ASSET_STATUS.PROCESSING,
133
+ },
134
+ };
37
135
 
38
- return expectSaga(sagas.uploadViberAsset, file, assetType, fileParams)
136
+ return expectSaga(sagas.uploadViberAsset, mockParams)
39
137
  .provide([
40
- [call(Api.uploadFile, file, assetType, fileParams), throwError(error)]
138
+ [matchers.call.fn(Api.uploadFile), mockResponse],
139
+ ])
140
+ .put({
141
+ type: types.UPLOAD_VIBER_ASSET_FAILED,
142
+ payload: {
143
+ assetId: undefined,
144
+ error: 'Asset upload initiated but no asset ID was returned from the server. Unable to track processing status.',
145
+ },
146
+ templateType: 0,
147
+ })
148
+ .not.call(pollAssetStatus)
149
+ .run();
150
+ });
151
+
152
+ it('handles upload error', () => {
153
+ const uploadError = new Error('Upload failed');
154
+
155
+ return expectSaga(sagas.uploadViberAsset, mockParams)
156
+ .provide([
157
+ [matchers.call.fn(Api.uploadFile), throwError(uploadError)],
41
158
  ])
42
159
  .put({
43
160
  type: types.UPLOAD_VIBER_ASSET_FAILURE,
44
- error
161
+ error: uploadError,
162
+ })
163
+ .run();
164
+ });
165
+
166
+ it('falls back to statusCode on response when status object missing', () => {
167
+ const mockResponse = {
168
+ statusCode: 201,
169
+ response: { asset: mockAsset },
170
+ };
171
+ return expectSaga(sagas.uploadViberAsset, mockParams)
172
+ .provide([
173
+ [matchers.call.fn(Api.uploadFile), mockResponse],
174
+ ])
175
+ .put({
176
+ type: types.UPLOAD_VIBER_ASSET_SUCCESS,
177
+ data: mockAsset,
178
+ statusCode: 201,
179
+ templateType: 0,
45
180
  })
46
181
  .run();
47
182
  });
183
+
184
+ it('defaults statusCode to 200 when no status info present', () => {
185
+ const mockResponse = { response: { asset: mockAsset } };
186
+ return expectSaga(sagas.uploadViberAsset, mockParams)
187
+ .provide([
188
+ [matchers.call.fn(Api.uploadFile), mockResponse],
189
+ ])
190
+ .put({
191
+ type: types.UPLOAD_VIBER_ASSET_SUCCESS,
192
+ data: mockAsset,
193
+ statusCode: 200,
194
+ templateType: 0,
195
+ })
196
+ .run();
197
+ });
198
+
199
+ it('handles empty response object', () => {
200
+ const mockResponse = { status: { code: 201 } };
201
+ return expectSaga(sagas.uploadViberAsset, mockParams)
202
+ .provide([
203
+ [matchers.call.fn(Api.uploadFile), mockResponse],
204
+ ])
205
+ .put({
206
+ type: types.UPLOAD_VIBER_ASSET_SUCCESS,
207
+ data: undefined,
208
+ statusCode: 201,
209
+ templateType: 0,
210
+ })
211
+ .run();
212
+ });
213
+
214
+ it('derives assetType from asset.type when params.assetType omitted', () => {
215
+ const paramsWithoutType = {
216
+ file: new File(['v'], 'v.mp4', { type: 'video/mp4' }),
217
+ fileParams: {},
218
+ templateType: 1,
219
+ };
220
+ const mockResponse = {
221
+ status: { code: 202 },
222
+ response: {
223
+ assetId: 'asset-vid',
224
+ asset: { _id: 'asset-vid', type: 'VIDEO' },
225
+ },
226
+ };
227
+ return expectSaga(sagas.uploadViberAsset, paramsWithoutType)
228
+ .provide([
229
+ [matchers.call.fn(Api.uploadFile), mockResponse],
230
+ [matchers.call.fn(pollAssetStatus), undefined],
231
+ ])
232
+ .call.fn(pollAssetStatus)
233
+ .run();
234
+ });
235
+
236
+ it('defaults assetType to image when no type info available', () => {
237
+ const paramsWithoutType = {
238
+ file: new File(['x'], 'x', { type: '' }),
239
+ fileParams: {},
240
+ templateType: 0,
241
+ };
242
+ const mockResponse = {
243
+ status: { code: 202 },
244
+ response: {
245
+ assetId: 'asset-noop',
246
+ },
247
+ };
248
+ return expectSaga(sagas.uploadViberAsset, paramsWithoutType)
249
+ .provide([
250
+ [matchers.call.fn(Api.uploadFile), mockResponse],
251
+ [matchers.call.fn(pollAssetStatus), undefined],
252
+ ])
253
+ .call.fn(pollAssetStatus)
254
+ .run();
255
+ });
48
256
  });
49
257
 
50
258
  describe('createViberTemplate Saga', () => {
@@ -54,18 +262,18 @@ describe('Viber Sagas', () => {
54
262
  it('handles creating template successfully', () => {
55
263
  const fakeResponse = {
56
264
  response: { id: 2, content: template.content },
57
- status: { code: 201 }
265
+ status: { code: 201 },
58
266
  };
59
267
 
60
268
  return expectSaga(sagas.createViberTemplate, { template, callback })
61
269
  .provide([
62
- [call(Api.createViberTemplate, { template }), fakeResponse]
270
+ [call(Api.createViberTemplate, { template }), fakeResponse],
63
271
  ])
64
272
  .put({
65
273
  type: types.CREATE_VIBER_TEMPLATE_SUCCESS,
66
274
  data: fakeResponse.response,
67
275
  statusCode: fakeResponse.status.code,
68
- errorMsg: undefined
276
+ errorMsg: undefined,
69
277
  })
70
278
  .run()
71
279
  .then(() => {
@@ -76,21 +284,74 @@ describe('Viber Sagas', () => {
76
284
  it('handles failure in creating template', () => {
77
285
  const error = new Error({ message: 'Creation failed', status: { code: 400 } });
78
286
 
79
- const errorMsg = 'Creation failed';
80
287
  return expectSaga(sagas.createViberTemplate, { template, callback })
81
288
  .provide([
82
- [call(Api.createViberTemplate, { template }), throwError(error)]
289
+ [call(Api.createViberTemplate, { template }), throwError(error)],
83
290
  ])
84
291
  .put({
85
292
  type: types.CREATE_VIBER_TEMPLATE_FAILURE,
86
293
  error,
87
- errorMsg : undefined
294
+ errorMsg: undefined,
88
295
  })
89
296
  .run()
90
297
  .then(() => {
91
298
  expect(callback).toHaveBeenCalledWith(null, undefined);
92
299
  });
93
300
  });
301
+
302
+ it('handles 4xx API response (treated as failure)', () => {
303
+ const cb = jest.fn();
304
+ const fakeResponse = {
305
+ response: {},
306
+ status: { code: 400 },
307
+ message: 'bad request',
308
+ };
309
+ return expectSaga(sagas.createViberTemplate, { template, callback: cb })
310
+ .provide([
311
+ [call(Api.createViberTemplate, { template }), fakeResponse],
312
+ ])
313
+ .put({
314
+ type: types.CREATE_VIBER_TEMPLATE_FAILURE,
315
+ error: 'bad request',
316
+ errorMsg: 'bad request',
317
+ })
318
+ .run()
319
+ .then(() => {
320
+ expect(cb).toHaveBeenCalledWith(null, 'bad request');
321
+ });
322
+ });
323
+
324
+ it('handles success when no callback is supplied', () => {
325
+ const fakeResponse = {
326
+ response: { id: 42 },
327
+ status: { code: 201 },
328
+ };
329
+ return expectSaga(sagas.createViberTemplate, { template })
330
+ .provide([
331
+ [call(Api.createViberTemplate, { template }), fakeResponse],
332
+ ])
333
+ .put({
334
+ type: types.CREATE_VIBER_TEMPLATE_SUCCESS,
335
+ data: fakeResponse.response,
336
+ statusCode: 201,
337
+ errorMsg: undefined,
338
+ })
339
+ .run();
340
+ });
341
+
342
+ it('handles failure when no callback is supplied', () => {
343
+ const error = new Error('Creation failed');
344
+ return expectSaga(sagas.createViberTemplate, { template })
345
+ .provide([
346
+ [call(Api.createViberTemplate, { template }), throwError(error)],
347
+ ])
348
+ .put({
349
+ type: types.CREATE_VIBER_TEMPLATE_FAILURE,
350
+ error,
351
+ errorMsg: undefined,
352
+ })
353
+ .run();
354
+ });
94
355
  });
95
356
 
96
357
  describe('editTemplate Saga', () => {
@@ -100,18 +361,18 @@ describe('Viber Sagas', () => {
100
361
  it('handles editing template successfully', () => {
101
362
  const fakeResponse = {
102
363
  response: { updated: true },
103
- status: { code: 200 }
364
+ status: { code: 200 },
104
365
  };
105
366
 
106
367
  return expectSaga(sagas.editTemplate, { template, callback })
107
368
  .provide([
108
- [call(Api.createViberTemplate, { template }), fakeResponse]
369
+ [call(Api.createViberTemplate, { template }), fakeResponse],
109
370
  ])
110
371
  .put({
111
372
  type: types.EDIT_VIBER_TEMPLATE_SUCCESS,
112
373
  data: fakeResponse.response,
113
374
  statusCode: fakeResponse.status.code,
114
- errorMsg: undefined
375
+ errorMsg: undefined,
115
376
  })
116
377
  .run()
117
378
  .then(() => {
@@ -123,7 +384,7 @@ describe('Viber Sagas', () => {
123
384
  const errorMsg = 'Error in editing template';
124
385
  return expectSaga(sagas.editTemplate, { template, callback })
125
386
  .provide([
126
- [call(Api.createViberTemplate, { template }), throwError({ message: errorMsg, status: { code: 400 } })]
387
+ [call(Api.createViberTemplate, { template }), throwError({ message: errorMsg, status: { code: 400 } })],
127
388
  ])
128
389
  .put({
129
390
  type: types.EDIT_VIBER_TEMPLATE_FAILURE,
@@ -135,6 +396,60 @@ describe('Viber Sagas', () => {
135
396
  expect(callback).toHaveBeenCalledWith(null, undefined);
136
397
  });
137
398
  });
399
+
400
+ it('handles 4xx API response (treated as failure)', () => {
401
+ const cb = jest.fn();
402
+ const fakeResponse = {
403
+ response: {},
404
+ status: { code: 422 },
405
+ message: 'invalid payload',
406
+ };
407
+ return expectSaga(sagas.editTemplate, { template, callback: cb })
408
+ .provide([
409
+ [call(Api.createViberTemplate, { template }), fakeResponse],
410
+ ])
411
+ .put({
412
+ type: types.EDIT_VIBER_TEMPLATE_FAILURE,
413
+ error: 'invalid payload',
414
+ errorMsg: 'invalid payload',
415
+ })
416
+ .run()
417
+ .then(() => {
418
+ expect(cb).toHaveBeenCalledWith(null, 'invalid payload');
419
+ });
420
+ });
421
+
422
+ it('handles success when no callback is supplied', () => {
423
+ const fakeResponse = {
424
+ response: { updated: true },
425
+ status: { code: 200 },
426
+ };
427
+ return expectSaga(sagas.editTemplate, { template })
428
+ .provide([
429
+ [call(Api.createViberTemplate, { template }), fakeResponse],
430
+ ])
431
+ .put({
432
+ type: types.EDIT_VIBER_TEMPLATE_SUCCESS,
433
+ data: fakeResponse.response,
434
+ statusCode: 200,
435
+ errorMsg: undefined,
436
+ })
437
+ .run();
438
+ });
439
+
440
+ it('handles failure when no callback is supplied', () => {
441
+ const error = new Error('Edit failed');
442
+ return expectSaga(sagas.editTemplate, { template })
443
+ .provide([
444
+ [call(Api.createViberTemplate, { template }), throwError(error)],
445
+ ])
446
+ .put({
447
+ type: types.EDIT_VIBER_TEMPLATE_FAILURE,
448
+ error,
449
+ errorMsg: undefined,
450
+ })
451
+ .run();
452
+ });
138
453
  });
139
454
 
140
455
  describe('getTemplateDetails Saga', () => {
@@ -143,16 +458,16 @@ describe('Viber Sagas', () => {
143
458
 
144
459
  it('handles fetching template details successfully', () => {
145
460
  const fakeResponse = {
146
- response: { id: id, name: 'Detailed Template' }
461
+ response: { id, name: 'Detailed Template' },
147
462
  };
148
463
 
149
464
  return expectSaga(sagas.getTemplateDetails, { id, callback })
150
465
  .provide([
151
- [call(Api.getTemplateDetails, { id, channel: 'VIBER' }), fakeResponse]
466
+ [call(Api.getTemplateDetails, { id, channel: 'VIBER' }), fakeResponse],
152
467
  ])
153
468
  .put({
154
469
  type: types.GET_VIBER_TEMPLATE_DETAILS_SUCCESS,
155
- data: fakeResponse.response
470
+ data: fakeResponse.response,
156
471
  })
157
472
  .run()
158
473
  .then(() => {
@@ -165,23 +480,33 @@ describe('Viber Sagas', () => {
165
480
 
166
481
  return expectSaga(sagas.getTemplateDetails, { id, callback })
167
482
  .provide([
168
- [call(Api.getTemplateDetails, { id, channel: 'VIBER' }), throwError(error)]
483
+ [call(Api.getTemplateDetails, { id, channel: 'VIBER' }), throwError(error)],
169
484
  ])
170
485
  .put({
171
486
  type: types.GET_VIBER_TEMPLATE_DETAILS_FAILURE,
172
- error
487
+ error,
173
488
  })
174
489
  .run()
175
490
  .then(() => {
176
491
  expect(callback).not.toHaveBeenCalledWith();
177
492
  });
178
493
  });
179
- });
180
494
 
181
- describe('v2ViberSagas Combined', () => {
182
- it('should initialize all Viber-related watcher sagas without error', () => {
183
- return expectSaga(v2ViberSagas).run();
495
+ it('handles success without a callback', () => {
496
+ const fakeResponse = { response: { id, name: 'No CB' } };
497
+ return expectSaga(sagas.getTemplateDetails, { id })
498
+ .provide([
499
+ [call(Api.getTemplateDetails, { id, channel: 'VIBER' }), fakeResponse],
500
+ ])
501
+ .put({
502
+ type: types.GET_VIBER_TEMPLATE_DETAILS_SUCCESS,
503
+ data: fakeResponse.response,
504
+ })
505
+ .run();
184
506
  });
185
507
  });
186
508
 
187
- });
509
+ describe('v2ViberSagas Combined', () => {
510
+ it('should initialize all Viber-related watcher sagas without error', () => expectSaga(sagas.v2ViberSagas).run());
511
+ });
512
+ });