@capillarytech/creatives-library 8.0.241 → 8.0.242-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.
- package/package.json +1 -1
- package/sagas/__tests__/assetPolling.test.js +607 -0
- package/sagas/assetPolling.js +156 -0
- package/services/api.js +16 -0
- package/services/tests/api.test.js +124 -0
- package/translations/en.json +1 -0
- package/utils/assetStatusConstants.js +12 -0
- package/utils/asyncAssetUpload.js +161 -0
- package/utils/tests/asyncAssetUpload.test.js +292 -0
- package/utils/transformerUtils.js +42 -0
- package/v2Components/CapImageUpload/constants.js +2 -0
- package/v2Components/CapImageUpload/index.js +54 -14
- package/v2Components/CapImageUpload/index.scss +4 -1
- package/v2Components/CapImageUpload/messages.js +4 -0
- package/v2Components/CapImageUrlUpload/constants.js +19 -0
- package/v2Components/CapImageUrlUpload/index.js +455 -0
- package/v2Components/CapImageUrlUpload/index.scss +35 -0
- package/v2Components/CapImageUrlUpload/messages.js +47 -0
- package/v2Containers/App/constants.js +5 -0
- package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +1 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +57 -2
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +1 -0
- package/v2Containers/CreativesContainer/constants.js +2 -0
- package/v2Containers/CreativesContainer/index.js +152 -0
- package/v2Containers/CreativesContainer/messages.js +4 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +25 -0
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +18 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +46 -0
- package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +4 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +8 -0
- package/v2Containers/Templates/ChannelTypeIllustration.js +13 -1
- package/v2Containers/Templates/_templates.scss +203 -0
- package/v2Containers/Templates/actions.js +2 -1
- package/v2Containers/Templates/constants.js +1 -0
- package/v2Containers/Templates/index.js +273 -30
- package/v2Containers/Templates/messages.js +24 -0
- package/v2Containers/Templates/reducer.js +2 -0
- package/v2Containers/Templates/tests/index.test.js +10 -0
- package/v2Containers/TemplatesV2/index.js +3 -2
- package/v2Containers/TemplatesV2/messages.js +4 -0
- package/v2Containers/WebPush/Create/components/ButtonForm.js +175 -0
- package/v2Containers/WebPush/Create/components/ButtonItem.js +101 -0
- package/v2Containers/WebPush/Create/components/ButtonList.js +144 -0
- package/v2Containers/WebPush/Create/components/_buttons.scss +246 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +554 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +607 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +633 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +666 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +74 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +80 -0
- package/v2Containers/WebPush/Create/index.js +1755 -0
- package/v2Containers/WebPush/Create/index.scss +123 -0
- package/v2Containers/WebPush/Create/messages.js +199 -0
- package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +241 -0
- package/v2Containers/WebPush/Create/preview/NotificationContainer.js +290 -0
- package/v2Containers/WebPush/Create/preview/PreviewContent.js +81 -0
- package/v2Containers/WebPush/Create/preview/PreviewControls.js +240 -0
- package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +23 -0
- package/v2Containers/WebPush/Create/preview/WebPushPreview.js +144 -0
- package/v2Containers/WebPush/Create/preview/assets/Light.svg +53 -0
- package/v2Containers/WebPush/Create/preview/assets/Top.svg +5 -0
- package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
- package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
- package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +106 -0
- package/v2Containers/WebPush/Create/preview/assets/iOS.svg +26 -0
- package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +18 -0
- package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +29 -0
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +44 -0
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +110 -0
- package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +45 -0
- package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +72 -0
- package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +55 -0
- package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +70 -0
- package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +512 -0
- package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +77 -0
- package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +527 -0
- package/v2Containers/WebPush/Create/preview/constants.js +162 -0
- package/v2Containers/WebPush/Create/preview/notification-container.scss +104 -0
- package/v2Containers/WebPush/Create/preview/preview.scss +409 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +300 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +303 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_base.scss +188 -0
- package/v2Containers/WebPush/Create/preview/styles/_ios.scss +106 -0
- package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +107 -0
- package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +75 -0
- package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +174 -0
- package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +909 -0
- package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +1077 -0
- package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +723 -0
- package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +943 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +128 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +121 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +144 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +127 -0
- package/v2Containers/WebPush/Create/utils/urlValidation.js +116 -0
- package/v2Containers/WebPush/Create/utils/urlValidation.test.js +449 -0
- package/v2Containers/WebPush/actions.js +60 -0
- package/v2Containers/WebPush/constants.js +108 -0
- package/v2Containers/WebPush/index.js +2 -0
- package/v2Containers/WebPush/reducer.js +104 -0
- package/v2Containers/WebPush/sagas.js +119 -0
- package/v2Containers/WebPush/selectors.js +65 -0
- package/v2Containers/WebPush/tests/reducer.test.js +863 -0
- package/v2Containers/WebPush/tests/sagas.test.js +566 -0
- package/v2Containers/WebPush/tests/selectors.test.js +960 -0
- package/v2Containers/Whatsapp/constants.js +9 -0
- package/v2Containers/Whatsapp/reducer.js +34 -5
- package/v2Containers/Whatsapp/sagas.js +61 -10
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +132 -0
- package/v2Containers/Whatsapp/tests/reducer.test.js +188 -0
- package/v2Containers/Whatsapp/tests/saga.test.js +420 -7
package/package.json
CHANGED
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
import { expectSaga, testSaga } from 'redux-saga-test-plan';
|
|
2
|
+
import * as matchers from 'redux-saga-test-plan/matchers';
|
|
3
|
+
import { throwError } from 'redux-saga-test-plan/providers';
|
|
4
|
+
import { call, put } from 'redux-saga/effects';
|
|
5
|
+
import { pollAssetStatus } from '../assetPolling';
|
|
6
|
+
import { getAssetStatus } from '../../services/api';
|
|
7
|
+
|
|
8
|
+
// Dynamic provider for delay calls - matches call effects with a single number argument
|
|
9
|
+
// This is needed because function references don't match between modules
|
|
10
|
+
// The effect structure: { '@@redux-saga/IO': true, CALL: { context: null, fn: delay, args: [ms] } }
|
|
11
|
+
const delayProvider = (effect, next) => {
|
|
12
|
+
// Check if this is a call effect
|
|
13
|
+
if (effect && effect.CALL) {
|
|
14
|
+
const args = effect.CALL.args;
|
|
15
|
+
// Match any call with a single number argument (delay calls)
|
|
16
|
+
// delay(ms) calls have exactly one numeric argument
|
|
17
|
+
if (Array.isArray(args) && args.length === 1 && typeof args[0] === 'number' && args[0] >= 0) {
|
|
18
|
+
// Return resolved promise immediately to skip actual delay
|
|
19
|
+
return Promise.resolve();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Not a delay call, pass to next provider/matcher
|
|
23
|
+
return next();
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Increase timeout for tests that involve delays
|
|
27
|
+
jest.setTimeout(20000);
|
|
28
|
+
|
|
29
|
+
describe('assetPolling saga', () => {
|
|
30
|
+
describe('pollAssetStatus', () => {
|
|
31
|
+
const mockAssetId = 'asset-123';
|
|
32
|
+
const mockAssetType = 'image';
|
|
33
|
+
const mockAsset = { _id: mockAssetId, type: 'IMAGE', url: 'https://example.com/image.jpg' };
|
|
34
|
+
|
|
35
|
+
// Mock Date.now() to return predictable values for duration calculation
|
|
36
|
+
const mockStartTime = 1000;
|
|
37
|
+
let originalDateNow;
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
originalDateNow = Date.now;
|
|
41
|
+
// Mock Date.now() to always return the same value so duration = 0
|
|
42
|
+
Date.now = jest.fn(() => mockStartTime);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(() => {
|
|
46
|
+
Date.now = originalDateNow;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const createMockConfig = (overrides = {}) => ({
|
|
50
|
+
type: mockAssetType,
|
|
51
|
+
assetId: mockAssetId,
|
|
52
|
+
onCompleted: (data) => ({ type: 'ASSET_COMPLETED', payload: data }),
|
|
53
|
+
onFailed: (data) => ({ type: 'ASSET_FAILED', payload: data }),
|
|
54
|
+
onTimeout: (data) => ({ type: 'ASSET_TIMEOUT', payload: data }),
|
|
55
|
+
maxDuration: 50000, // Longer duration to avoid timeout issues
|
|
56
|
+
initialDelay: 0, // No delay for faster tests
|
|
57
|
+
pollInterval: 0, // No interval for faster tests
|
|
58
|
+
...overrides,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should complete successfully when asset status is completed on first poll', () => {
|
|
62
|
+
const config = createMockConfig();
|
|
63
|
+
|
|
64
|
+
return expectSaga(pollAssetStatus, config)
|
|
65
|
+
.provide({
|
|
66
|
+
call: delayProvider,
|
|
67
|
+
})
|
|
68
|
+
.provide([
|
|
69
|
+
[
|
|
70
|
+
matchers.call.fn(getAssetStatus),
|
|
71
|
+
{
|
|
72
|
+
response: {
|
|
73
|
+
status: 'completed',
|
|
74
|
+
asset: mockAsset,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
])
|
|
79
|
+
.put({
|
|
80
|
+
type: 'ASSET_COMPLETED',
|
|
81
|
+
payload: {
|
|
82
|
+
assetId: mockAssetId,
|
|
83
|
+
asset: mockAsset,
|
|
84
|
+
duration: 0, // Date.now() - startTime = 0 when mocked
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
.run({ timeout: 15000 });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should handle failed status immediately', () => {
|
|
91
|
+
const config = createMockConfig();
|
|
92
|
+
const errorMessage = 'Processing failed';
|
|
93
|
+
|
|
94
|
+
return expectSaga(pollAssetStatus, config)
|
|
95
|
+
.provide([
|
|
96
|
+
[
|
|
97
|
+
matchers.call.fn(getAssetStatus),
|
|
98
|
+
{
|
|
99
|
+
response: {
|
|
100
|
+
status: 'failed',
|
|
101
|
+
error: errorMessage,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
])
|
|
106
|
+
.put({
|
|
107
|
+
type: 'ASSET_FAILED',
|
|
108
|
+
payload: {
|
|
109
|
+
assetId: mockAssetId,
|
|
110
|
+
error: errorMessage,
|
|
111
|
+
duration: 0, // Date.now() - startTime = 0 when mocked
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
.run({ timeout: 10000 });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should timeout when maxDuration is exceeded', () => {
|
|
118
|
+
const config = createMockConfig({
|
|
119
|
+
maxDuration: 10,
|
|
120
|
+
initialDelay: 0,
|
|
121
|
+
pollInterval: 5,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Mock Date.now() to simulate time passing - override the beforeEach mock
|
|
125
|
+
let callCount = 0;
|
|
126
|
+
const originalDateNow = Date.now;
|
|
127
|
+
Date.now = jest.fn(() => {
|
|
128
|
+
callCount++;
|
|
129
|
+
// First call sets startTime, second call checks timeout (should exceed maxDuration)
|
|
130
|
+
if (callCount === 1) {
|
|
131
|
+
return mockStartTime; // startTime
|
|
132
|
+
}
|
|
133
|
+
return mockStartTime + 11; // Exceeds maxDuration of 10
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return expectSaga(pollAssetStatus, config)
|
|
137
|
+
.provide([
|
|
138
|
+
// Delay provider handles all delay calls
|
|
139
|
+
{
|
|
140
|
+
call: delayProvider,
|
|
141
|
+
},
|
|
142
|
+
// Keep returning processing status
|
|
143
|
+
[
|
|
144
|
+
matchers.call.fn(getAssetStatus),
|
|
145
|
+
{
|
|
146
|
+
response: {
|
|
147
|
+
status: 'processing',
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
])
|
|
152
|
+
.put({
|
|
153
|
+
type: 'ASSET_TIMEOUT',
|
|
154
|
+
payload: {
|
|
155
|
+
assetId: mockAssetId,
|
|
156
|
+
message: 'Asset processing is taking longer than expected. Please check back later.',
|
|
157
|
+
duration: 11, // mockStartTime + 11 - mockStartTime = 11
|
|
158
|
+
},
|
|
159
|
+
})
|
|
160
|
+
.run({ timeout: 15000 })
|
|
161
|
+
.finally(() => {
|
|
162
|
+
Date.now = originalDateNow;
|
|
163
|
+
callCount = 0;
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should use default values when config options are not provided', () => {
|
|
168
|
+
const config = {
|
|
169
|
+
type: mockAssetType,
|
|
170
|
+
assetId: mockAssetId,
|
|
171
|
+
onCompleted: (data) => ({ type: 'ASSET_COMPLETED', payload: data }),
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return expectSaga(pollAssetStatus, config)
|
|
175
|
+
.provide([
|
|
176
|
+
[
|
|
177
|
+
matchers.call.fn(getAssetStatus),
|
|
178
|
+
{
|
|
179
|
+
response: {
|
|
180
|
+
status: 'completed',
|
|
181
|
+
asset: mockAsset,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
])
|
|
186
|
+
.put({
|
|
187
|
+
type: 'ASSET_COMPLETED',
|
|
188
|
+
payload: {
|
|
189
|
+
assetId: mockAssetId,
|
|
190
|
+
asset: mockAsset,
|
|
191
|
+
duration: 0, // Date.now() - startTime = 0 when mocked
|
|
192
|
+
},
|
|
193
|
+
})
|
|
194
|
+
.run({ timeout: 15000 });
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should handle missing onCompleted callback', () => {
|
|
198
|
+
const config = {
|
|
199
|
+
type: mockAssetType,
|
|
200
|
+
assetId: mockAssetId,
|
|
201
|
+
onFailed: (data) => ({ type: 'ASSET_FAILED', payload: data }),
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
return expectSaga(pollAssetStatus, config)
|
|
205
|
+
.provide([
|
|
206
|
+
[
|
|
207
|
+
matchers.call.fn(getAssetStatus),
|
|
208
|
+
{
|
|
209
|
+
response: {
|
|
210
|
+
status: 'completed',
|
|
211
|
+
asset: mockAsset,
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
])
|
|
216
|
+
.not.put({ type: 'ASSET_COMPLETED' })
|
|
217
|
+
.run({ timeout: 15000 });
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should handle missing onTimeout callback', () => {
|
|
221
|
+
const config = {
|
|
222
|
+
type: mockAssetType,
|
|
223
|
+
assetId: mockAssetId,
|
|
224
|
+
maxDuration: 10,
|
|
225
|
+
initialDelay: 0,
|
|
226
|
+
pollInterval: 5,
|
|
227
|
+
onCompleted: (data) => ({ type: 'ASSET_COMPLETED', payload: data }),
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
return expectSaga(pollAssetStatus, config)
|
|
231
|
+
.provide([
|
|
232
|
+
[
|
|
233
|
+
matchers.call.fn(getAssetStatus),
|
|
234
|
+
{
|
|
235
|
+
response: {
|
|
236
|
+
status: 'processing',
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
])
|
|
241
|
+
.not.put({ type: 'ASSET_TIMEOUT' })
|
|
242
|
+
.run({ timeout: 15000 });
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should use custom initialDelay and pollInterval', () => {
|
|
246
|
+
const config = createMockConfig({
|
|
247
|
+
initialDelay: 0,
|
|
248
|
+
pollInterval: 0,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
return expectSaga(pollAssetStatus, config)
|
|
252
|
+
.provide([
|
|
253
|
+
[
|
|
254
|
+
matchers.call.fn(getAssetStatus),
|
|
255
|
+
{
|
|
256
|
+
response: {
|
|
257
|
+
status: 'completed',
|
|
258
|
+
asset: mockAsset,
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
])
|
|
263
|
+
.put({
|
|
264
|
+
type: 'ASSET_COMPLETED',
|
|
265
|
+
payload: {
|
|
266
|
+
assetId: mockAssetId,
|
|
267
|
+
asset: mockAsset,
|
|
268
|
+
duration: 0, // Date.now() - startTime = 0 when mocked
|
|
269
|
+
},
|
|
270
|
+
})
|
|
271
|
+
.run({ timeout: 15000 });
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should handle failed status with default error message', () => {
|
|
275
|
+
const config = createMockConfig();
|
|
276
|
+
|
|
277
|
+
return expectSaga(pollAssetStatus, config)
|
|
278
|
+
.provide([
|
|
279
|
+
[
|
|
280
|
+
matchers.call.fn(getAssetStatus),
|
|
281
|
+
{
|
|
282
|
+
response: {
|
|
283
|
+
status: 'failed',
|
|
284
|
+
// No error field
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
])
|
|
289
|
+
.put({
|
|
290
|
+
type: 'ASSET_FAILED',
|
|
291
|
+
payload: {
|
|
292
|
+
assetId: mockAssetId,
|
|
293
|
+
error: 'Processing failed',
|
|
294
|
+
duration: 0, // Date.now() - startTime = 0 when mocked
|
|
295
|
+
},
|
|
296
|
+
})
|
|
297
|
+
.run({ timeout: 15000 });
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('should handle different asset types', () => {
|
|
301
|
+
const videoConfig = createMockConfig({ type: 'video' });
|
|
302
|
+
|
|
303
|
+
return expectSaga(pollAssetStatus, videoConfig)
|
|
304
|
+
.provide([
|
|
305
|
+
[
|
|
306
|
+
matchers.call.fn(getAssetStatus),
|
|
307
|
+
{
|
|
308
|
+
response: {
|
|
309
|
+
status: 'completed',
|
|
310
|
+
asset: { ...mockAsset, type: 'VIDEO' },
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
])
|
|
315
|
+
.put({
|
|
316
|
+
type: 'ASSET_COMPLETED',
|
|
317
|
+
payload: {
|
|
318
|
+
assetId: mockAssetId,
|
|
319
|
+
asset: { ...mockAsset, type: 'VIDEO' },
|
|
320
|
+
duration: 0, // Date.now() - startTime = 0 when mocked
|
|
321
|
+
},
|
|
322
|
+
})
|
|
323
|
+
.run({ timeout: 15000 });
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('should handle API response with success: false', () => {
|
|
327
|
+
const config = createMockConfig();
|
|
328
|
+
const errorMessage = 'API request failed';
|
|
329
|
+
|
|
330
|
+
return expectSaga(pollAssetStatus, config)
|
|
331
|
+
.provide({
|
|
332
|
+
call: delayProvider,
|
|
333
|
+
})
|
|
334
|
+
.provide([
|
|
335
|
+
[
|
|
336
|
+
matchers.call.fn(getAssetStatus),
|
|
337
|
+
{
|
|
338
|
+
success: false,
|
|
339
|
+
message: errorMessage,
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
])
|
|
343
|
+
.put({
|
|
344
|
+
type: 'ASSET_FAILED',
|
|
345
|
+
payload: {
|
|
346
|
+
assetId: mockAssetId,
|
|
347
|
+
error: errorMessage,
|
|
348
|
+
duration: 0,
|
|
349
|
+
},
|
|
350
|
+
})
|
|
351
|
+
.run({ timeout: 15000 });
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should handle API response with error property', () => {
|
|
355
|
+
const config = createMockConfig();
|
|
356
|
+
const errorMessage = 'Network error occurred';
|
|
357
|
+
|
|
358
|
+
return expectSaga(pollAssetStatus, config)
|
|
359
|
+
.provide({
|
|
360
|
+
call: delayProvider,
|
|
361
|
+
})
|
|
362
|
+
.provide([
|
|
363
|
+
[
|
|
364
|
+
matchers.call.fn(getAssetStatus),
|
|
365
|
+
{
|
|
366
|
+
error: errorMessage,
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
])
|
|
370
|
+
.put({
|
|
371
|
+
type: 'ASSET_FAILED',
|
|
372
|
+
payload: {
|
|
373
|
+
assetId: mockAssetId,
|
|
374
|
+
error: errorMessage,
|
|
375
|
+
duration: 0,
|
|
376
|
+
},
|
|
377
|
+
})
|
|
378
|
+
.run({ timeout: 15000 });
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('should handle null or undefined API response', () => {
|
|
382
|
+
const config = createMockConfig();
|
|
383
|
+
|
|
384
|
+
return expectSaga(pollAssetStatus, config)
|
|
385
|
+
.provide({
|
|
386
|
+
call: delayProvider,
|
|
387
|
+
})
|
|
388
|
+
.provide([
|
|
389
|
+
[
|
|
390
|
+
matchers.call.fn(getAssetStatus),
|
|
391
|
+
null,
|
|
392
|
+
],
|
|
393
|
+
])
|
|
394
|
+
.put({
|
|
395
|
+
type: 'ASSET_FAILED',
|
|
396
|
+
payload: {
|
|
397
|
+
assetId: mockAssetId,
|
|
398
|
+
error: 'Failed to fetch asset status: Invalid API response',
|
|
399
|
+
duration: 0,
|
|
400
|
+
},
|
|
401
|
+
})
|
|
402
|
+
.run({ timeout: 15000 });
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('should handle API response with missing response property', () => {
|
|
406
|
+
const config = createMockConfig();
|
|
407
|
+
|
|
408
|
+
return expectSaga(pollAssetStatus, config)
|
|
409
|
+
.provide({
|
|
410
|
+
call: delayProvider,
|
|
411
|
+
})
|
|
412
|
+
.provide([
|
|
413
|
+
[
|
|
414
|
+
matchers.call.fn(getAssetStatus),
|
|
415
|
+
{
|
|
416
|
+
status: 200,
|
|
417
|
+
// Missing response property
|
|
418
|
+
},
|
|
419
|
+
],
|
|
420
|
+
])
|
|
421
|
+
.put({
|
|
422
|
+
type: 'ASSET_FAILED',
|
|
423
|
+
payload: {
|
|
424
|
+
assetId: mockAssetId,
|
|
425
|
+
error: 'Invalid asset status response: Missing or invalid status field',
|
|
426
|
+
duration: 0,
|
|
427
|
+
},
|
|
428
|
+
})
|
|
429
|
+
.run({ timeout: 15000 });
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('should handle API response with empty response object', () => {
|
|
433
|
+
const config = createMockConfig();
|
|
434
|
+
|
|
435
|
+
return expectSaga(pollAssetStatus, config)
|
|
436
|
+
.provide({
|
|
437
|
+
call: delayProvider,
|
|
438
|
+
})
|
|
439
|
+
.provide([
|
|
440
|
+
[
|
|
441
|
+
matchers.call.fn(getAssetStatus),
|
|
442
|
+
{
|
|
443
|
+
response: {},
|
|
444
|
+
// Empty response object without status
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
])
|
|
448
|
+
.put({
|
|
449
|
+
type: 'ASSET_FAILED',
|
|
450
|
+
payload: {
|
|
451
|
+
assetId: mockAssetId,
|
|
452
|
+
error: 'Invalid asset status response: Missing or invalid status field',
|
|
453
|
+
duration: 0,
|
|
454
|
+
},
|
|
455
|
+
})
|
|
456
|
+
.run({ timeout: 15000 });
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('should handle API response with invalid status field (not a string)', () => {
|
|
460
|
+
const config = createMockConfig();
|
|
461
|
+
|
|
462
|
+
return expectSaga(pollAssetStatus, config)
|
|
463
|
+
.provide({
|
|
464
|
+
call: delayProvider,
|
|
465
|
+
})
|
|
466
|
+
.provide([
|
|
467
|
+
[
|
|
468
|
+
matchers.call.fn(getAssetStatus),
|
|
469
|
+
{
|
|
470
|
+
response: {
|
|
471
|
+
status: 200, // Invalid: status should be a string, not a number
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
])
|
|
476
|
+
.put({
|
|
477
|
+
type: 'ASSET_FAILED',
|
|
478
|
+
payload: {
|
|
479
|
+
assetId: mockAssetId,
|
|
480
|
+
error: 'Invalid asset status response: Missing or invalid status field',
|
|
481
|
+
duration: 0,
|
|
482
|
+
},
|
|
483
|
+
})
|
|
484
|
+
.run({ timeout: 15000 });
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('should handle API response with error message property', () => {
|
|
488
|
+
const config = createMockConfig();
|
|
489
|
+
const errorMessage = 'Custom error message';
|
|
490
|
+
|
|
491
|
+
return expectSaga(pollAssetStatus, config)
|
|
492
|
+
.provide({
|
|
493
|
+
call: delayProvider,
|
|
494
|
+
})
|
|
495
|
+
.provide([
|
|
496
|
+
[
|
|
497
|
+
matchers.call.fn(getAssetStatus),
|
|
498
|
+
{
|
|
499
|
+
success: false,
|
|
500
|
+
message: errorMessage,
|
|
501
|
+
},
|
|
502
|
+
],
|
|
503
|
+
])
|
|
504
|
+
.put({
|
|
505
|
+
type: 'ASSET_FAILED',
|
|
506
|
+
payload: {
|
|
507
|
+
assetId: mockAssetId,
|
|
508
|
+
error: errorMessage,
|
|
509
|
+
duration: 0,
|
|
510
|
+
},
|
|
511
|
+
})
|
|
512
|
+
.run({ timeout: 15000 });
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('should handle timeout without onTimeout callback', () => {
|
|
516
|
+
const config = {
|
|
517
|
+
type: mockAssetType,
|
|
518
|
+
assetId: mockAssetId,
|
|
519
|
+
maxDuration: 10,
|
|
520
|
+
initialDelay: 0,
|
|
521
|
+
pollInterval: 5,
|
|
522
|
+
onCompleted: (data) => ({ type: 'ASSET_COMPLETED', payload: data }),
|
|
523
|
+
onFailed: (data) => ({ type: 'ASSET_FAILED', payload: data }),
|
|
524
|
+
// No onTimeout callback
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
// Mock Date.now() to simulate time passing
|
|
528
|
+
let callCount = 0;
|
|
529
|
+
const originalDateNow = Date.now;
|
|
530
|
+
Date.now = jest.fn(() => {
|
|
531
|
+
callCount++;
|
|
532
|
+
if (callCount === 1) {
|
|
533
|
+
return mockStartTime; // startTime
|
|
534
|
+
}
|
|
535
|
+
return mockStartTime + 11; // Exceeds maxDuration of 10
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
return expectSaga(pollAssetStatus, config)
|
|
539
|
+
.provide({
|
|
540
|
+
call: delayProvider,
|
|
541
|
+
})
|
|
542
|
+
.provide([
|
|
543
|
+
[
|
|
544
|
+
matchers.call.fn(getAssetStatus),
|
|
545
|
+
{
|
|
546
|
+
response: {
|
|
547
|
+
status: 'processing',
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
],
|
|
551
|
+
])
|
|
552
|
+
.not.put({ type: 'ASSET_TIMEOUT' })
|
|
553
|
+
.run({ timeout: 15000 })
|
|
554
|
+
.finally(() => {
|
|
555
|
+
Date.now = originalDateNow;
|
|
556
|
+
callCount = 0;
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it('should handle catch block error when getAssetStatus throws', () => {
|
|
561
|
+
const config = createMockConfig();
|
|
562
|
+
const networkError = new Error('Network request failed');
|
|
563
|
+
|
|
564
|
+
return expectSaga(pollAssetStatus, config)
|
|
565
|
+
.provide({
|
|
566
|
+
call: delayProvider,
|
|
567
|
+
})
|
|
568
|
+
.provide([
|
|
569
|
+
[
|
|
570
|
+
matchers.call.fn(getAssetStatus),
|
|
571
|
+
throwError(networkError),
|
|
572
|
+
],
|
|
573
|
+
])
|
|
574
|
+
.put({
|
|
575
|
+
type: 'ASSET_FAILED',
|
|
576
|
+
payload: {
|
|
577
|
+
assetId: mockAssetId,
|
|
578
|
+
error: 'Failed to check asset status: Network request failed',
|
|
579
|
+
},
|
|
580
|
+
})
|
|
581
|
+
.run({ timeout: 15000 });
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
it('should handle catch block error without onFailed callback', () => {
|
|
585
|
+
const config = {
|
|
586
|
+
type: mockAssetType,
|
|
587
|
+
assetId: mockAssetId,
|
|
588
|
+
onCompleted: (data) => ({ type: 'ASSET_COMPLETED', payload: data }),
|
|
589
|
+
// No onFailed callback
|
|
590
|
+
};
|
|
591
|
+
const networkError = new Error('Network request failed');
|
|
592
|
+
|
|
593
|
+
return expectSaga(pollAssetStatus, config)
|
|
594
|
+
.provide({
|
|
595
|
+
call: delayProvider,
|
|
596
|
+
})
|
|
597
|
+
.provide([
|
|
598
|
+
[
|
|
599
|
+
matchers.call.fn(getAssetStatus),
|
|
600
|
+
throwError(networkError),
|
|
601
|
+
],
|
|
602
|
+
])
|
|
603
|
+
.not.put({ type: 'ASSET_FAILED' })
|
|
604
|
+
.run({ timeout: 15000 });
|
|
605
|
+
});
|
|
606
|
+
});
|
|
607
|
+
});
|