UNPKG

60.9 kBJavaScriptView Raw
1const fs = require('fs')
2const path = require('path')
3const prettierBytes = require('@transloadit/prettier-bytes')
4const Core = require('./index')
5const Plugin = require('./Plugin')
6const AcquirerPlugin1 = require('../../../../test/mocks/acquirerPlugin1')
7const AcquirerPlugin2 = require('../../../../test/mocks/acquirerPlugin2')
8const InvalidPlugin = require('../../../../test/mocks/invalidPlugin')
9const InvalidPluginWithoutId = require('../../../../test/mocks/invalidPluginWithoutId')
10const InvalidPluginWithoutType = require('../../../../test/mocks/invalidPluginWithoutType')
11const DeepFrozenStore = require('../../../../test/resources/DeepFrozenStore.js')
12
13jest.mock('cuid', () => {
14 return () => 'cjd09qwxb000dlql4tp4doz8h'
15})
16jest.mock('@uppy/utils/lib/findDOMElement', () => {
17 return () => null
18})
19
20const sampleImage = fs.readFileSync(path.join(__dirname, '../../../../test/resources/image.jpg'))
21
22describe('src/Core', () => {
23 const RealCreateObjectUrl = global.URL.createObjectURL
24 beforeEach(() => {
25 global.URL.createObjectURL = jest.fn().mockReturnValue('newUrl')
26 })
27
28 afterEach(() => {
29 global.URL.createObjectURL = RealCreateObjectUrl
30 })
31
32 it('should expose a class', () => {
33 const core = Core()
34 expect(core.constructor.name).toEqual('Uppy')
35 })
36
37 it('should have a string `id` option that defaults to "uppy"', () => {
38 const core = Core()
39 expect(core.getID()).toEqual('uppy')
40
41 const core2 = Core({ id: 'profile' })
42 expect(core2.getID()).toEqual('profile')
43 })
44
45 describe('plugins', () => {
46 it('should add a plugin to the plugin stack', () => {
47 const core = Core()
48 core.use(AcquirerPlugin1)
49 expect(Object.keys(core.plugins.acquirer).length).toEqual(1)
50 })
51
52 it('should prevent the same plugin from being added more than once', () => {
53 const core = Core()
54 core.use(AcquirerPlugin1)
55
56 expect(() => {
57 core.use(AcquirerPlugin1)
58 }).toThrowErrorMatchingSnapshot()
59 })
60
61 it('should not be able to add an invalid plugin', () => {
62 const core = Core()
63
64 expect(() => {
65 core.use(InvalidPlugin)
66 }).toThrowErrorMatchingSnapshot()
67 })
68
69 it('should not be able to add a plugin that has no id', () => {
70 const core = Core()
71
72 expect(() =>
73 core.use(InvalidPluginWithoutId)).toThrowErrorMatchingSnapshot()
74 })
75
76 it('should not be able to add a plugin that has no type', () => {
77 const core = Core()
78
79 expect(() =>
80 core.use(InvalidPluginWithoutType)).toThrowErrorMatchingSnapshot()
81 })
82
83 it('should return the plugin that matches the specified name', () => {
84 const core = new Core()
85 expect(core.getPlugin('foo')).toEqual(null)
86
87 core.use(AcquirerPlugin1)
88 const plugin = core.getPlugin('TestSelector1')
89 expect(plugin.id).toEqual('TestSelector1')
90 expect(plugin instanceof Plugin)
91 })
92
93 it('should call the specified method on all the plugins', () => {
94 const core = new Core()
95 core.use(AcquirerPlugin1)
96 core.use(AcquirerPlugin2)
97 core.iteratePlugins(plugin => {
98 plugin.run('hello')
99 })
100 expect(core.plugins.acquirer[0].mocks.run.mock.calls.length).toEqual(1)
101 expect(core.plugins.acquirer[0].mocks.run.mock.calls[0]).toEqual([
102 'hello',
103 ])
104 expect(core.plugins.acquirer[1].mocks.run.mock.calls.length).toEqual(1)
105 expect(core.plugins.acquirer[1].mocks.run.mock.calls[0]).toEqual([
106 'hello',
107 ])
108 })
109
110 it('should uninstall and the remove the specified plugin', () => {
111 const core = new Core()
112 core.use(AcquirerPlugin1)
113 core.use(AcquirerPlugin2)
114 expect(Object.keys(core.plugins.acquirer).length).toEqual(2)
115
116 const plugin = core.getPlugin('TestSelector1')
117 core.removePlugin(plugin)
118 expect(Object.keys(core.plugins.acquirer).length).toEqual(1)
119 expect(plugin.mocks.uninstall.mock.calls.length).toEqual(1)
120 expect(core.plugins.acquirer[0].mocks.run.mock.calls.length).toEqual(0)
121 })
122 })
123
124 describe('state', () => {
125 it('should update all the plugins with the new state when the updateAll method is called', () => {
126 const core = new Core()
127 core.use(AcquirerPlugin1)
128 core.use(AcquirerPlugin2)
129 core.updateAll({ foo: 'bar' })
130 expect(core.plugins.acquirer[0].mocks.update.mock.calls.length).toEqual(1)
131 expect(core.plugins.acquirer[0].mocks.update.mock.calls[0]).toEqual([
132 { foo: 'bar' },
133 ])
134 expect(core.plugins.acquirer[1].mocks.update.mock.calls.length).toEqual(1)
135 expect(core.plugins.acquirer[1].mocks.update.mock.calls[0]).toEqual([
136 { foo: 'bar' },
137 ])
138 })
139
140 it('should update the state', () => {
141 const core = new Core()
142 const stateUpdateEventMock = jest.fn()
143 core.on('state-update', stateUpdateEventMock)
144 core.use(AcquirerPlugin1)
145 core.use(AcquirerPlugin2)
146
147 core.setState({ foo: 'bar', bee: 'boo' })
148 core.setState({ foo: 'baar' })
149
150 const newState = {
151 bee: 'boo',
152 capabilities: { individualCancellation: true, uploadProgress: true, resumableUploads: false },
153 files: {},
154 currentUploads: {},
155 allowNewUpload: true,
156 foo: 'baar',
157 info: { isHidden: true, message: '', type: 'info' },
158 meta: {},
159 plugins: {},
160 totalProgress: 0,
161 }
162
163 expect(core.getState()).toEqual(newState)
164
165 expect(core.plugins.acquirer[0].mocks.update.mock.calls[1]).toEqual([
166 newState,
167 ])
168 expect(core.plugins.acquirer[1].mocks.update.mock.calls[1]).toEqual([
169 newState,
170 ])
171
172 expect(stateUpdateEventMock.mock.calls.length).toEqual(2)
173 // current state
174 expect(stateUpdateEventMock.mock.calls[1][0]).toEqual({
175 bee: 'boo',
176 capabilities: { individualCancellation: true, uploadProgress: true, resumableUploads: false },
177 files: {},
178 currentUploads: {},
179 allowNewUpload: true,
180 foo: 'bar',
181 info: { isHidden: true, message: '', type: 'info' },
182 meta: {},
183 plugins: {},
184 totalProgress: 0,
185 })
186 // new state
187 expect(stateUpdateEventMock.mock.calls[1][1]).toEqual({
188 bee: 'boo',
189 capabilities: { individualCancellation: true, uploadProgress: true, resumableUploads: false },
190 files: {},
191 currentUploads: {},
192 allowNewUpload: true,
193 foo: 'baar',
194 info: { isHidden: true, message: '', type: 'info' },
195 meta: {},
196 plugins: {},
197 totalProgress: 0,
198 })
199 })
200
201 it('should get the state', () => {
202 const core = new Core()
203
204 core.setState({ foo: 'bar' })
205
206 expect(core.getState()).toMatchObject({ foo: 'bar' })
207 })
208 })
209
210 it('should reset when the reset method is called', () => {
211 // use DeepFrozenStore in some tests to make sure we are not mutating things
212 const core = new Core({
213 store: DeepFrozenStore(),
214 })
215 // const corePauseEventMock = jest.fn()
216 const coreCancelEventMock = jest.fn()
217 const coreStateUpdateEventMock = jest.fn()
218 core.on('cancel-all', coreCancelEventMock)
219 core.on('state-update', coreStateUpdateEventMock)
220 core.setState({ foo: 'bar', totalProgress: 30 })
221
222 core.reset()
223
224 expect(coreCancelEventMock.mock.calls.length).toEqual(1)
225 expect(coreStateUpdateEventMock.mock.calls.length).toEqual(2)
226 expect(coreStateUpdateEventMock.mock.calls[1][1]).toEqual({
227 capabilities: { individualCancellation: true, uploadProgress: true, resumableUploads: false },
228 files: {},
229 currentUploads: {},
230 allowNewUpload: true,
231 error: null,
232 foo: 'bar',
233 info: { isHidden: true, message: '', type: 'info' },
234 meta: {},
235 plugins: {},
236 totalProgress: 0,
237 })
238 })
239
240 it('should clear all uploads and files on cancelAll()', () => {
241 const core = new Core()
242
243 core.addFile({
244 source: 'jest',
245 name: 'foo1.jpg',
246 type: 'image/jpeg',
247 data: new File([sampleImage], { type: 'image/jpeg' }),
248 })
249
250 core.addFile({
251 source: 'jest',
252 name: 'foo2.jpg',
253 type: 'image/jpeg',
254 data: new File([sampleImage], { type: 'image/jpeg' }),
255 })
256
257 const fileIDs = Object.keys(core.getState().files)
258 const id = core._createUpload(fileIDs)
259
260 expect(core.getState().currentUploads[id]).toBeDefined()
261 expect(Object.keys(core.getState().files).length).toEqual(2)
262
263 core.cancelAll()
264
265 expect(core.getState().currentUploads[id]).toBeUndefined()
266 expect(Object.keys(core.getState().files).length).toEqual(0)
267 })
268
269 it('should close, reset and uninstall when the close method is called', () => {
270 // use DeepFrozenStore in some tests to make sure we are not mutating things
271 const core = new Core({
272 store: DeepFrozenStore(),
273 })
274 core.use(AcquirerPlugin1)
275
276 const coreCancelEventMock = jest.fn()
277 const coreStateUpdateEventMock = jest.fn()
278 const plugin = core.plugins.acquirer[0]
279
280 core.on('cancel-all', coreCancelEventMock)
281 core.on('state-update', coreStateUpdateEventMock)
282
283 core.close()
284
285 expect(coreCancelEventMock.mock.calls.length).toEqual(1)
286 expect(coreStateUpdateEventMock.mock.calls.length).toEqual(1)
287 expect(coreStateUpdateEventMock.mock.calls[0][1]).toEqual({
288 capabilities: { individualCancellation: true, uploadProgress: true, resumableUploads: false },
289 files: {},
290 currentUploads: {},
291 allowNewUpload: true,
292 error: null,
293 info: { isHidden: true, message: '', type: 'info' },
294 meta: {},
295 plugins: {},
296 totalProgress: 0,
297 })
298 expect(plugin.mocks.uninstall.mock.calls.length).toEqual(1)
299 expect(core.plugins[Object.keys(core.plugins)[0]].length).toEqual(0)
300 })
301
302 describe('upload hooks', () => {
303 it('should add data returned from upload hooks to the .upload() result', () => {
304 const core = new Core()
305 core.addPreProcessor((fileIDs, uploadID) => {
306 core.addResultData(uploadID, { pre: 'ok' })
307 })
308 core.addPostProcessor((fileIDs, uploadID) => {
309 core.addResultData(uploadID, { post: 'ok' })
310 })
311 core.addUploader((fileIDs, uploadID) => {
312 core.addResultData(uploadID, { upload: 'ok' })
313 })
314 return core.upload().then((result) => {
315 expect(result.pre).toBe('ok')
316 expect(result.upload).toBe('ok')
317 expect(result.post).toBe('ok')
318 })
319 })
320 })
321
322 describe('preprocessors', () => {
323 it('should add a preprocessor', () => {
324 const core = new Core()
325 const preprocessor = function () {}
326 core.addPreProcessor(preprocessor)
327 expect(core.preProcessors[0]).toEqual(preprocessor)
328 })
329
330 it('should remove a preprocessor', () => {
331 const core = new Core()
332 const preprocessor1 = function () {}
333 const preprocessor2 = function () {}
334 const preprocessor3 = function () {}
335 core.addPreProcessor(preprocessor1)
336 core.addPreProcessor(preprocessor2)
337 core.addPreProcessor(preprocessor3)
338 expect(core.preProcessors.length).toEqual(3)
339 core.removePreProcessor(preprocessor2)
340 expect(core.preProcessors.length).toEqual(2)
341 })
342
343 it('should execute all the preprocessors when uploading a file', () => {
344 const core = new Core()
345 const preprocessor1 = jest.fn()
346 const preprocessor2 = jest.fn()
347 core.addPreProcessor(preprocessor1)
348 core.addPreProcessor(preprocessor2)
349
350 core.addFile({
351 source: 'jest',
352 name: 'foo.jpg',
353 type: 'image/jpeg',
354 data: new File([sampleImage], { type: 'image/jpeg' }),
355 })
356
357 return core.upload()
358 .then(() => {
359 const fileId = Object.keys(core.getState().files)[0]
360 expect(preprocessor1.mock.calls.length).toEqual(1)
361
362 expect(preprocessor1.mock.calls[0][0].length).toEqual(1)
363 expect(preprocessor1.mock.calls[0][0][0]).toEqual(fileId)
364
365 expect(preprocessor2.mock.calls[0][0].length).toEqual(1)
366 expect(preprocessor2.mock.calls[0][0][0]).toEqual(fileId)
367 })
368 })
369
370 it('should not pass removed file IDs to next step', async () => {
371 const core = new Core()
372 const uploader = jest.fn()
373 core.addPreProcessor((fileIDs) => {
374 core.removeFile(fileIDs[0])
375 })
376 core.addUploader(uploader)
377
378 core.addFile({
379 source: 'jest',
380 name: 'rmd.jpg',
381 type: 'image/jpeg',
382 data: new File([sampleImage], { type: 'image/jpeg' }),
383 })
384 core.addFile({
385 source: 'jest',
386 name: 'kept.jpg',
387 type: 'image/jpeg',
388 data: new File([sampleImage], { type: 'image/jpeg' }),
389 })
390
391 await core.upload()
392
393 expect(uploader.mock.calls.length).toEqual(1)
394 expect(uploader.mock.calls[0][0].length).toEqual(1, 'Got 1 file ID')
395 expect(core.getFile(uploader.mock.calls[0][0][0]).name).toEqual('kept.jpg')
396 })
397
398 it('should update the file progress state when preprocess-progress event is fired', () => {
399 const core = new Core()
400 core.addFile({
401 source: 'jest',
402 name: 'foo.jpg',
403 type: 'image/jpeg',
404 data: new File([sampleImage], { type: 'image/jpeg' }),
405 })
406
407 const fileId = Object.keys(core.getState().files)[0]
408 const file = core.getFile(fileId)
409 core.emit('preprocess-progress', file, {
410 mode: 'determinate',
411 message: 'something',
412 value: 0,
413 })
414 expect(core.getFile(fileId).progress).toEqual({
415 percentage: 0,
416 bytesUploaded: 0,
417 bytesTotal: 17175,
418 uploadComplete: false,
419 uploadStarted: null,
420 preprocess: { mode: 'determinate', message: 'something', value: 0 },
421 })
422 })
423
424 it('should update the file progress state when preprocess-complete event is fired', () => {
425 const core = new Core()
426
427 core.addFile({
428 source: 'jest',
429 name: 'foo.jpg',
430 type: 'image/jpeg',
431 data: new File([sampleImage], { type: 'image/jpeg' }),
432 })
433
434 const fileID = Object.keys(core.getState().files)[0]
435 const file = core.getFile(fileID)
436 core.emit('preprocess-complete', file, {
437 mode: 'determinate',
438 message: 'something',
439 value: 0,
440 })
441 expect(core.getFile(fileID).progress).toEqual({
442 percentage: 0,
443 bytesUploaded: 0,
444 bytesTotal: 17175,
445 uploadComplete: false,
446 uploadStarted: null,
447 })
448 })
449 })
450
451 describe('postprocessors', () => {
452 it('should add a postprocessor', () => {
453 const core = new Core()
454 const postprocessor = function () {}
455 core.addPostProcessor(postprocessor)
456 expect(core.postProcessors[0]).toEqual(postprocessor)
457 })
458
459 it('should remove a postprocessor', () => {
460 const core = new Core()
461 const postprocessor1 = function () {}
462 const postprocessor2 = function () {}
463 const postprocessor3 = function () {}
464 core.addPostProcessor(postprocessor1)
465 core.addPostProcessor(postprocessor2)
466 core.addPostProcessor(postprocessor3)
467 expect(core.postProcessors.length).toEqual(3)
468 core.removePostProcessor(postprocessor2)
469 expect(core.postProcessors.length).toEqual(2)
470 })
471
472 it('should execute all the postprocessors when uploading a file', () => {
473 const core = new Core()
474 const postprocessor1 = jest.fn()
475 const postprocessor2 = jest.fn()
476 core.addPostProcessor(postprocessor1)
477 core.addPostProcessor(postprocessor2)
478
479 core.addFile({
480 source: 'jest',
481 name: 'foo.jpg',
482 type: 'image/jpeg',
483 data: new File([sampleImage], { type: 'image/jpeg' }),
484 })
485
486 return core.upload().then(() => {
487 expect(postprocessor1.mock.calls.length).toEqual(1)
488 // const lastModifiedTime = new Date()
489 // const fileId = 'foojpg' + lastModifiedTime.getTime()
490 const fileId = 'uppy-foo/jpg-1e-image'
491
492 expect(postprocessor1.mock.calls[0][0].length).toEqual(1)
493 expect(postprocessor1.mock.calls[0][0][0].substring(0, 17)).toEqual(
494 fileId.substring(0, 17)
495 )
496
497 expect(postprocessor2.mock.calls[0][0].length).toEqual(1)
498 expect(postprocessor2.mock.calls[0][0][0].substring(0, 17)).toEqual(
499 fileId.substring(0, 17)
500 )
501 })
502 })
503
504 it('should update the file progress state when postprocess-progress event is fired', () => {
505 const core = new Core()
506
507 core.addFile({
508 source: 'jest',
509 name: 'foo.jpg',
510 type: 'image/jpeg',
511 data: new File([sampleImage], { type: 'image/jpeg' }),
512 })
513
514 const fileId = Object.keys(core.getState().files)[0]
515 const file = core.getFile(fileId)
516 core.emit('postprocess-progress', file, {
517 mode: 'determinate',
518 message: 'something',
519 value: 0,
520 })
521 expect(core.getFile(fileId).progress).toEqual({
522 percentage: 0,
523 bytesUploaded: 0,
524 bytesTotal: 17175,
525 uploadComplete: false,
526 uploadStarted: null,
527 postprocess: { mode: 'determinate', message: 'something', value: 0 },
528 })
529 })
530
531 it('should update the file progress state when postprocess-complete event is fired', () => {
532 const core = new Core()
533
534 core.addFile({
535 source: 'jest',
536 name: 'foo.jpg',
537 type: 'image/jpeg',
538 data: new File([sampleImage], { type: 'image/jpeg' }),
539 })
540
541 const fileId = Object.keys(core.getState().files)[0]
542 const file = core.getFile(fileId)
543 core.emit('postprocess-complete', file, {
544 mode: 'determinate',
545 message: 'something',
546 value: 0,
547 })
548 expect(core.getFile(fileId).progress).toEqual({
549 percentage: 0,
550 bytesUploaded: 0,
551 bytesTotal: 17175,
552 uploadComplete: false,
553 uploadStarted: null,
554 })
555 })
556 })
557
558 describe('uploaders', () => {
559 it('should add an uploader', () => {
560 const core = new Core()
561 const uploader = function () {}
562 core.addUploader(uploader)
563 expect(core.uploaders[0]).toEqual(uploader)
564 })
565
566 it('should remove an uploader', () => {
567 const core = new Core()
568 const uploader1 = function () {}
569 const uploader2 = function () {}
570 const uploader3 = function () {}
571 core.addUploader(uploader1)
572 core.addUploader(uploader2)
573 core.addUploader(uploader3)
574 expect(core.uploaders.length).toEqual(3)
575 core.removeUploader(uploader2)
576 expect(core.uploaders.length).toEqual(2)
577 })
578 })
579
580 describe('adding a file', () => {
581 it('should call onBeforeFileAdded if it was specified in the options when initialising the class', () => {
582 const onBeforeFileAdded = jest.fn()
583 const core = new Core({
584 onBeforeFileAdded,
585 })
586
587 core.addFile({
588 source: 'jest',
589 name: 'foo.jpg',
590 type: 'image/jpeg',
591 data: new File([sampleImage], { type: 'image/jpeg' }),
592 })
593
594 expect(onBeforeFileAdded.mock.calls.length).toEqual(1)
595 expect(onBeforeFileAdded.mock.calls[0][0].name).toEqual('foo.jpg')
596 expect(onBeforeFileAdded.mock.calls[0][1]).toEqual({})
597 })
598
599 it('should add a file', () => {
600 const fileData = new File([sampleImage], { type: 'image/jpeg' })
601 const fileAddedEventMock = jest.fn()
602 const core = new Core()
603 core.on('file-added', fileAddedEventMock)
604
605 const fileId = core.addFile({
606 source: 'jest',
607 name: 'foo.jpg',
608 type: 'image/jpeg',
609 data: fileData,
610 })
611 const newFile = {
612 extension: 'jpg',
613 id: fileId,
614 isRemote: false,
615 meta: { name: 'foo.jpg', type: 'image/jpeg' },
616 name: 'foo.jpg',
617 preview: undefined,
618 data: fileData,
619 progress: {
620 bytesTotal: 17175,
621 bytesUploaded: 0,
622 percentage: 0,
623 uploadComplete: false,
624 uploadStarted: null,
625 },
626 remote: '',
627 size: 17175,
628 source: 'jest',
629 type: 'image/jpeg',
630 }
631 expect(core.getFile(fileId)).toEqual(newFile)
632 expect(fileAddedEventMock.mock.calls[0][0]).toEqual(newFile)
633 })
634
635 it('should not allow a file that does not meet the restrictions', () => {
636 const core = new Core({
637 restrictions: {
638 allowedFileTypes: ['image/gif', 'video/webm'],
639 },
640 })
641
642 expect(() => {
643 core.addFile({
644 source: 'jest',
645 name: 'foo.jpg',
646 type: 'image/jpeg',
647 data: new File([sampleImage], { type: 'image/jpeg' }),
648 })
649 }).toThrow('You can only upload: image/gif, video/webm')
650
651 expect(() => {
652 core.addFile({
653 source: 'jest',
654 name: 'foo.webm',
655 type: 'video/webm; codecs="vp8, opus"',
656 data: new File([sampleImage], { type: 'video/webm; codecs="vp8, opus"' }),
657 })
658 }).not.toThrow()
659 })
660
661 it('should not allow a dupicate file, a file with the same id', () => {
662 const core = new Core()
663 const sameFileBlob = new File([sampleImage], { type: 'image/jpeg' })
664 core.addFile({
665 source: 'jest',
666 name: 'foo.jpg',
667 type: 'image/jpeg',
668 data: sameFileBlob,
669 })
670 expect(() => {
671 core.addFile({
672 source: 'jest',
673 name: 'foo.jpg',
674 type: 'image/jpeg',
675 data: sameFileBlob,
676 meta: {
677 notARelativePath: 'folder/a',
678 },
679 })
680 }).toThrow(
681 "Cannot add the duplicate file 'foo.jpg', it already exists"
682 )
683 expect(core.getFiles().length).toEqual(1)
684 })
685
686 it('should allow a duplicate file if its relativePath is different, thus the id is different', () => {
687 const core = new Core()
688 core.addFile({
689 source: 'jest',
690 name: 'foo.jpg',
691 type: 'image/jpeg',
692 data: new File([sampleImage], { type: 'image/jpeg' }),
693 })
694 core.addFile({
695 source: 'jest',
696 name: 'foo.jpg',
697 type: 'image/jpeg',
698 data: new File([sampleImage], { type: 'image/jpeg' }),
699 meta: {
700 relativePath: 'folder/a',
701 },
702 })
703 expect(core.getFiles().length).toEqual(2)
704 })
705
706 it('should not allow a file if onBeforeFileAdded returned false', () => {
707 const core = new Core({
708 onBeforeFileAdded: (file, files) => {
709 if (file.source === 'jest') {
710 return false
711 }
712 },
713 })
714 expect(() => {
715 core.addFile({
716 source: 'jest',
717 name: 'foo.jpg',
718 type: 'image/jpeg',
719 data: new File([sampleImage], { type: 'image/jpeg' }),
720 })
721 }).toThrow(
722 'Cannot add the file because onBeforeFileAdded returned false.'
723 )
724 expect(core.getFiles().length).toEqual(0)
725 })
726
727 describe('with allowMultipleUploads: false', () => {
728 it('allows no new files after upload', async () => {
729 const core = new Core({ allowMultipleUploads: false })
730 core.addFile({
731 source: 'jest',
732 name: 'foo.jpg',
733 type: 'image/jpeg',
734 data: new File([sampleImage], { type: 'image/jpeg' }),
735 })
736
737 await core.upload()
738
739 expect(() => {
740 core.addFile({
741 source: 'jest',
742 name: '123.foo',
743 type: 'image/jpeg',
744 data: new File([sampleImage], { type: 'image/jpeg' }),
745 })
746 }).toThrow(
747 /Cannot add new files: already uploading/
748 )
749 })
750
751 it('does not allow new files after the removeFile() if some file is still present', async () => {
752 const core = new Core({ allowMultipleUploads: false })
753
754 // adding 2 files
755 const fileId1 = core.addFile({
756 source: 'jest',
757 name: '1.jpg',
758 type: 'image/jpeg',
759 data: new File([sampleImage], { type: 'image/jpeg' }),
760 })
761 core.addFile({
762 source: 'jest',
763 name: '2.jpg',
764 type: 'image/jpeg',
765 data: new File([sampleImage], { type: 'image/jpeg' }),
766 })
767
768 // removing 1 file
769 core.removeFile(fileId1)
770
771 await expect(core.upload()).resolves.toBeDefined()
772 })
773
774 it('allows new files after the last removeFile()', async () => {
775 const core = new Core({ allowMultipleUploads: false })
776
777 // adding 2 files
778 const fileId1 = core.addFile({
779 source: 'jest',
780 name: '1.jpg',
781 type: 'image/jpeg',
782 data: new File([sampleImage], { type: 'image/jpeg' }),
783 })
784 const fileId2 = core.addFile({
785 source: 'jest',
786 name: '2.jpg',
787 type: 'image/jpeg',
788 data: new File([sampleImage], { type: 'image/jpeg' }),
789 })
790
791 // removing 2 files
792 core.removeFile(fileId1)
793 core.removeFile(fileId2)
794
795 await expect(core.upload()).resolves.toBeDefined()
796 })
797 })
798
799 it('does not dedupe different files', async () => {
800 const core = new Core()
801 const data = new Blob([sampleImage], { type: 'image/jpeg' })
802 data.lastModified = 1562770350937
803
804 core.addFile({
805 source: 'jest',
806 name: 'foo.jpg',
807 type: 'image/jpeg',
808 data,
809 })
810 core.addFile({
811 source: 'jest',
812 name: 'foo푸.jpg',
813 type: 'image/jpeg',
814 data,
815 })
816
817 expect(core.getFiles()).toHaveLength(2)
818 expect(core.getFile('uppy-foo/jpg-1e-image/jpeg-17175-1562770350937')).toBeDefined()
819 expect(core.getFile('uppy-foo//jpg-1l3o-1e-image/jpeg-17175-1562770350937')).toBeDefined()
820 })
821 })
822
823 describe('uploading a file', () => {
824 it('should return a { successful, failed } pair containing file objects', () => {
825 const core = new Core()
826 core.addUploader((fileIDs) => Promise.resolve())
827
828 core.addFile({ source: 'jest', name: 'foo.jpg', type: 'image/jpeg', data: new Uint8Array() })
829 core.addFile({ source: 'jest', name: 'bar.jpg', type: 'image/jpeg', data: new Uint8Array() })
830
831 return expect(core.upload()).resolves.toMatchObject({
832 successful: [
833 { name: 'foo.jpg' },
834 { name: 'bar.jpg' },
835 ],
836 failed: [],
837 })
838 })
839
840 it('should return files with errors in the { failed } key', () => {
841 // use DeepFrozenStore in some tests to make sure we are not mutating things
842 const core = new Core({
843 store: DeepFrozenStore(),
844 })
845 core.addUploader((fileIDs) => {
846 fileIDs.forEach((fileID) => {
847 const file = core.getFile(fileID)
848 if (/bar/.test(file.name)) {
849 core.emit('upload-error', file, new Error('This is bar and I do not like bar'))
850 }
851 })
852 return Promise.resolve()
853 })
854
855 core.addFile({ source: 'jest', name: 'foo.jpg', type: 'image/jpeg', data: new Uint8Array() })
856 core.addFile({ source: 'jest', name: 'bar.jpg', type: 'image/jpeg', data: new Uint8Array() })
857
858 return expect(core.upload()).resolves.toMatchObject({
859 successful: [
860 { name: 'foo.jpg' },
861 ],
862 failed: [
863 { name: 'bar.jpg', error: 'This is bar and I do not like bar' },
864 ],
865 })
866 })
867
868 it('should only upload files that are not already assigned to another upload id', () => {
869 const core = new Core()
870 core.store.state.currentUploads = {
871 upload1: {
872 fileIDs: ['uppy-file1/jpg-1e-image/jpeg', 'uppy-file2/jpg-1e-image/jpeg', 'uppy-file3/jpg-1e-image/jpeg'],
873 },
874 upload2: {
875 fileIDs: ['uppy-file4/jpg-1e-image/jpeg', 'uppy-file5/jpg-1e-image/jpeg', 'uppy-file6/jpg-1e-image/jpeg'],
876 },
877 }
878 core.addUploader((fileIDs) => Promise.resolve())
879
880 core.addFile({ source: 'jest', name: 'foo.jpg', type: 'image/jpeg', data: new Uint8Array() })
881 core.addFile({ source: 'jest', name: 'bar.jpg', type: 'image/jpeg', data: new Uint8Array() })
882 core.addFile({ source: 'file3', name: 'file3.jpg', type: 'image/jpeg', data: new Uint8Array() })
883
884 return expect(core.upload()).resolves.toMatchSnapshot()
885 })
886
887 it('should not upload if onBeforeUpload returned false', () => {
888 const core = new Core({
889 onBeforeUpload: (files) => {
890 for (var fileId in files) {
891 if (files[fileId].name === '123.foo') {
892 return false
893 }
894 }
895 },
896 })
897 core.addFile({
898 source: 'jest',
899 name: 'foo.jpg',
900 type: 'image/jpeg',
901 data: new File([sampleImage], { type: 'image/jpeg' }),
902 })
903 core.addFile({
904 source: 'jest',
905 name: 'bar.jpg',
906 type: 'image/jpeg',
907 data: new File([sampleImage], { type: 'image/jpeg' }),
908 })
909 core.addFile({
910 source: 'jest',
911 name: '123.foo',
912 type: 'image/jpeg',
913 data: new File([sampleImage], { type: 'image/jpeg' }),
914 })
915 return core.upload().catch((err) => {
916 expect(err).toMatchObject(new Error('Not starting the upload because onBeforeUpload returned false'))
917 })
918 })
919
920 it('only allows a single upload() batch when allowMultipleUploads: false', async () => {
921 const core = new Core({ allowMultipleUploads: false })
922 core.addFile({
923 source: 'jest',
924 name: 'foo.jpg',
925 type: 'image/jpeg',
926 data: new File([sampleImage], { type: 'image/jpeg' }),
927 })
928 core.addFile({
929 source: 'jest',
930 name: 'bar.jpg',
931 type: 'image/jpeg',
932 data: new File([sampleImage], { type: 'image/jpeg' }),
933 })
934
935 await expect(core.upload()).resolves.toBeDefined()
936 await expect(core.upload()).rejects.toThrow(
937 /Cannot create a new upload: already uploading\./
938 )
939 })
940
941 it('allows new files again with allowMultipleUploads: false after reset() was called', async () => {
942 const core = new Core({ allowMultipleUploads: false })
943
944 core.addFile({
945 source: 'jest',
946 name: 'bar.jpg',
947 type: 'image/jpeg',
948 data: new File([sampleImage], { type: 'image/jpeg' }),
949 })
950 await expect(core.upload()).resolves.toBeDefined()
951
952 core.reset()
953
954 core.addFile({
955 source: 'jest',
956 name: '123.foo',
957 type: 'image/jpeg',
958 data: new File([sampleImage], { type: 'image/jpeg' }),
959 })
960 await expect(core.upload()).resolves.toBeDefined()
961 })
962 })
963
964 describe('removing a file', () => {
965 it('should remove the file', () => {
966 const fileRemovedEventMock = jest.fn()
967
968 const core = new Core()
969 core.on('file-removed', fileRemovedEventMock)
970
971 core.addFile({
972 source: 'jest',
973 name: 'foo.jpg',
974 type: 'image/jpeg',
975 data: new File([sampleImage], { type: 'image/jpeg' }),
976 })
977
978 const fileId = Object.keys(core.getState().files)[0]
979 expect(core.getFiles().length).toEqual(1)
980 core.setState({
981 totalProgress: 50,
982 })
983
984 const file = core.getFile(fileId)
985 core.removeFile(fileId)
986
987 expect(core.getFiles().length).toEqual(0)
988 expect(fileRemovedEventMock.mock.calls[0][0]).toEqual(file)
989 expect(core.getState().totalProgress).toEqual(0)
990 })
991 })
992
993 describe('retries', () => {
994 it('should start a new upload with failed files', async () => {
995 const onUpload = jest.fn()
996 const onRetryAll = jest.fn()
997
998 const core = new Core()
999 core.on('upload', onUpload)
1000 core.on('retry-all', onRetryAll)
1001
1002 const id = core.addFile({
1003 source: 'jest',
1004 name: 'foo.jpg',
1005 type: 'image/jpeg',
1006 data: new File([sampleImage], { type: 'image/jpeg' }),
1007 })
1008 core.setFileState(id, {
1009 error: 'something went wrong',
1010 })
1011
1012 await core.retryAll()
1013 expect(onRetryAll).toHaveBeenCalled()
1014 expect(onUpload).toHaveBeenCalled()
1015 })
1016
1017 it('should not start a new upload if there are no failed files', async () => {
1018 const onUpload = jest.fn()
1019
1020 const core = new Core()
1021 core.on('upload', onUpload)
1022
1023 core.addFile({
1024 source: 'jest',
1025 name: 'foo.jpg',
1026 type: 'image/jpeg',
1027 data: new File([sampleImage], { type: 'image/jpeg' }),
1028 })
1029
1030 await core.retryAll()
1031 expect(onUpload).not.toHaveBeenCalled()
1032 })
1033 })
1034
1035 describe('restoring a file', () => {
1036 xit('should restore a file', () => {})
1037
1038 xit("should fail to restore a file if it doesn't exist", () => {})
1039 })
1040
1041 describe('get a file', () => {
1042 it('should get the specified file', () => {
1043 const core = new Core()
1044
1045 core.addFile({
1046 source: 'jest',
1047 name: 'foo.jpg',
1048 type: 'image/jpeg',
1049 data: new File([sampleImage], { type: 'image/jpeg' }),
1050 })
1051
1052 const fileId = Object.keys(core.getState().files)[0]
1053 expect(core.getFile(fileId).name).toEqual('foo.jpg')
1054
1055 expect(core.getFile('non existant file')).toEqual(undefined)
1056 })
1057 })
1058
1059 describe('getFiles', () => {
1060 it('should return an empty array if there are no files', () => {
1061 const core = new Core()
1062
1063 expect(core.getFiles()).toEqual([])
1064 })
1065
1066 it('should return all files as an array', () => {
1067 const core = new Core()
1068
1069 core.addFile({
1070 source: 'jest',
1071 name: 'foo.jpg',
1072 type: 'image/jpeg',
1073 data: new File([sampleImage], { type: 'image/jpeg' }),
1074 })
1075 core.addFile({
1076 source: 'jest',
1077 name: 'empty.dat',
1078 type: 'application/octet-stream',
1079 data: new File([Buffer.alloc(1000)], { type: 'application/octet-stream' }),
1080 })
1081
1082 expect(core.getFiles()).toHaveLength(2)
1083 expect(core.getFiles().map((file) => file.name).sort()).toEqual(['empty.dat', 'foo.jpg'])
1084 })
1085 })
1086
1087 describe('setOptions', () => {
1088 it('should change options on the fly', () => {
1089 const core = new Core()
1090 core.setOptions({
1091 id: 'lolUppy',
1092 autoProceed: true,
1093 allowMultipleUploads: true,
1094 })
1095
1096 expect(core.opts.id).toEqual('lolUppy')
1097 expect(core.opts.autoProceed).toEqual(true)
1098 expect(core.opts.allowMultipleUploads).toEqual(true)
1099 })
1100
1101 it('should change locale on the fly', () => {
1102 const core = new Core()
1103 expect(core.i18n('cancel')).toEqual('Cancel')
1104
1105 core.setOptions({
1106 locale: {
1107 strings: {
1108 cancel: 'Отмена',
1109 },
1110 },
1111 })
1112
1113 expect(core.i18n('cancel')).toEqual('Отмена')
1114 expect(core.i18n('logOut')).toEqual('Log out')
1115 })
1116
1117 it('should change meta on the fly', () => {
1118 const core = new Core({
1119 meta: {
1120 foo: 'bar',
1121 },
1122 })
1123 expect(core.state.meta).toMatchObject({
1124 foo: 'bar',
1125 })
1126
1127 core.setOptions({
1128 meta: {
1129 beep: 'boop',
1130 },
1131 })
1132
1133 expect(core.state.meta).toMatchObject({
1134 foo: 'bar',
1135 beep: 'boop',
1136 })
1137 })
1138
1139 it('should change restrictions on the fly', () => {
1140 const core = new Core({
1141 restrictions: {
1142 allowedFileTypes: ['image/jpeg'],
1143 maxNumberOfFiles: 2,
1144 },
1145 })
1146
1147 try {
1148 core.addFile({
1149 source: 'jest',
1150 name: 'foo1.png',
1151 type: 'image/png',
1152 data: new File([sampleImage], { type: 'image/png' }),
1153 })
1154 } catch (err) {
1155 expect(err).toMatchObject(new Error('You can only upload: image/jpeg'))
1156 }
1157
1158 core.setOptions({
1159 restrictions: {
1160 allowedFileTypes: ['image/png'],
1161 },
1162 })
1163
1164 expect(core.opts.restrictions.allowedFileTypes).toMatchObject(['image/png'])
1165
1166 expect(() => {
1167 core.addFile({
1168 source: 'jest',
1169 name: 'foo1.png',
1170 type: 'image/png',
1171 data: new File([sampleImage], { type: 'image/png' }),
1172 })
1173 }).not.toThrow()
1174
1175 expect(core.getFiles().length).toEqual(1)
1176 })
1177 })
1178
1179 describe('meta data', () => {
1180 it('should set meta data by calling setMeta', () => {
1181 // use DeepFrozenStore in some tests to make sure we are not mutating things
1182 const core = new Core({
1183 store: DeepFrozenStore(),
1184 meta: { foo2: 'bar2' },
1185 })
1186 core.setMeta({ foo: 'bar', bur: 'mur' })
1187 core.setMeta({ boo: 'moo', bur: 'fur' })
1188 expect(core.getState().meta).toEqual({
1189 foo: 'bar',
1190 foo2: 'bar2',
1191 boo: 'moo',
1192 bur: 'fur',
1193 })
1194 })
1195
1196 it('should update meta data for a file by calling updateMeta', () => {
1197 const core = new Core()
1198
1199 core.addFile({
1200 source: 'jest',
1201 name: 'foo.jpg',
1202 type: 'image/jpeg',
1203 data: new File([sampleImage], { type: 'image/jpeg' }),
1204 })
1205
1206 const fileId = Object.keys(core.getState().files)[0]
1207 core.setFileMeta(fileId, { foo: 'bar', bur: 'mur' })
1208 core.setFileMeta(fileId, { boo: 'moo', bur: 'fur' })
1209 expect(core.getFile(fileId).meta).toEqual({
1210 name: 'foo.jpg',
1211 type: 'image/jpeg',
1212 foo: 'bar',
1213 bur: 'fur',
1214 boo: 'moo',
1215 })
1216 })
1217
1218 it('should merge meta data when add file', () => {
1219 const core = new Core({
1220 meta: { foo2: 'bar2' },
1221 })
1222 core.addFile({
1223 source: 'jest',
1224 name: 'foo.jpg',
1225 type: 'image/jpeg',
1226 meta: {
1227 resize: 5000,
1228 },
1229 data: new File([sampleImage], { type: 'image/jpeg' }),
1230 })
1231 const fileId = Object.keys(core.getState().files)[0]
1232 expect(core.getFile(fileId).meta).toEqual({
1233 name: 'foo.jpg',
1234 type: 'image/jpeg',
1235 foo2: 'bar2',
1236 resize: 5000,
1237 })
1238 })
1239 })
1240
1241 describe('progress', () => {
1242 it('should calculate the progress of a file upload', () => {
1243 const core = new Core()
1244
1245 core.addFile({
1246 source: 'jest',
1247 name: 'foo.jpg',
1248 type: 'image/jpeg',
1249 data: new File([sampleImage], { type: 'image/jpeg' }),
1250 })
1251
1252 const fileId = Object.keys(core.getState().files)[0]
1253 const file = core.getFile(fileId)
1254 core.emit('upload-progress', file, {
1255 bytesUploaded: 12345,
1256 bytesTotal: 17175,
1257 })
1258 expect(core.getFile(fileId).progress).toEqual({
1259 percentage: 72,
1260 bytesUploaded: 12345,
1261 bytesTotal: 17175,
1262 uploadComplete: false,
1263 uploadStarted: null,
1264 })
1265
1266 core.emit('upload-progress', file, {
1267 bytesUploaded: 17175,
1268 bytesTotal: 17175,
1269 })
1270
1271 core._calculateProgress.flush()
1272
1273 expect(core.getFile(fileId).progress).toEqual({
1274 percentage: 100,
1275 bytesUploaded: 17175,
1276 bytesTotal: 17175,
1277 uploadComplete: false,
1278 uploadStarted: null,
1279 })
1280 })
1281
1282 it('should work with unsized files', async () => {
1283 const core = new Core()
1284 let proceedUpload
1285 let finishUpload
1286 const promise = new Promise((resolve) => { proceedUpload = resolve })
1287 const finishPromise = new Promise((resolve) => { finishUpload = resolve })
1288 core.addUploader(async ([id]) => {
1289 core.emit('upload-started', core.getFile(id))
1290 await promise
1291 core.emit('upload-progress', core.getFile(id), {
1292 bytesTotal: 3456,
1293 bytesUploaded: 1234,
1294 })
1295 await finishPromise
1296 core.emit('upload-success', core.getFile(id), { uploadURL: 'lol' })
1297 })
1298
1299 core.addFile({
1300 source: 'instagram',
1301 name: 'foo.jpg',
1302 type: 'image/jpeg',
1303 data: {},
1304 })
1305
1306 core._calculateTotalProgress()
1307
1308 const uploadPromise = core.upload()
1309 await new Promise((resolve) => core.once('upload-started', resolve))
1310
1311 expect(core.getFiles()[0].size).toBeNull()
1312 expect(core.getFiles()[0].progress).toMatchObject({
1313 bytesUploaded: 0,
1314 // null indicates unsized
1315 bytesTotal: null,
1316 percentage: 0,
1317 })
1318
1319 proceedUpload()
1320 // wait for progress event
1321 await promise
1322
1323 expect(core.getFiles()[0].size).toBeNull()
1324 expect(core.getFiles()[0].progress).toMatchObject({
1325 bytesUploaded: 1234,
1326 bytesTotal: 3456,
1327 percentage: 36,
1328 })
1329
1330 expect(core.getState().totalProgress).toBe(36)
1331
1332 finishUpload()
1333 // wait for success event
1334 await finishPromise
1335
1336 expect(core.getFiles()[0].size).toBeNull()
1337 expect(core.getFiles()[0].progress).toMatchObject({
1338 bytesUploaded: 3456,
1339 bytesTotal: 3456,
1340 percentage: 100,
1341 })
1342
1343 await uploadPromise
1344
1345 core.close()
1346 })
1347
1348 it('should estimate progress for unsized files', () => {
1349 const core = new Core()
1350
1351 core.once('file-added', (file) => {
1352 core.emit('upload-started', file)
1353 core.emit('upload-progress', file, {
1354 bytesTotal: 3456,
1355 bytesUploaded: 1234,
1356 })
1357 })
1358 core.addFile({
1359 source: 'instagram',
1360 name: 'foo.jpg',
1361 type: 'image/jpeg',
1362 data: {},
1363 })
1364
1365 core.once('file-added', (file) => {
1366 core.emit('upload-started', file)
1367 core.emit('upload-progress', file, {
1368 bytesTotal: null,
1369 bytesUploaded: null,
1370 })
1371 })
1372 core.addFile({
1373 source: 'instagram',
1374 name: 'bar.jpg',
1375 type: 'image/jpeg',
1376 data: {},
1377 })
1378
1379 core._calculateTotalProgress()
1380
1381 // foo.jpg at 35%, bar.jpg at 0%
1382 expect(core.getState().totalProgress).toBe(18)
1383
1384 core.close()
1385 })
1386
1387 it('should calculate the total progress of all file uploads', () => {
1388 // use DeepFrozenStore in some tests to make sure we are not mutating things
1389 const core = new Core({
1390 store: DeepFrozenStore(),
1391 })
1392
1393 core.addFile({
1394 source: 'jest',
1395 name: 'foo.jpg',
1396 type: 'image/jpeg',
1397 data: new File([sampleImage], { type: 'image/jpeg' }),
1398 })
1399 core.addFile({
1400 source: 'jest',
1401 name: 'foo2.jpg',
1402 type: 'image/jpeg',
1403 data: new File([sampleImage], { type: 'image/jpeg' }),
1404 })
1405
1406 const [file1, file2] = core.getFiles()
1407 core.setFileState(file1.id, { progress: { ...file1.progress, uploadStarted: new Date() } })
1408 core.setFileState(file2.id, { progress: { ...file2.progress, uploadStarted: new Date() } })
1409
1410 core.emit('upload-progress', core.getFile(file1.id), {
1411 bytesUploaded: 12345,
1412 bytesTotal: 17175,
1413 })
1414
1415 core.emit('upload-progress', core.getFile(file2.id), {
1416 bytesUploaded: 10201,
1417 bytesTotal: 17175,
1418 })
1419
1420 core._calculateTotalProgress()
1421 core._calculateProgress.flush()
1422
1423 expect(core.getState().totalProgress).toEqual(66)
1424 })
1425
1426 it('should reset the progress', () => {
1427 const resetProgressEvent = jest.fn()
1428 const core = new Core()
1429 core.on('reset-progress', resetProgressEvent)
1430
1431 core.addFile({
1432 source: 'jest',
1433 name: 'foo.jpg',
1434 type: 'image/jpeg',
1435 data: new File([sampleImage], { type: 'image/jpeg' }),
1436 })
1437 core.addFile({
1438 source: 'jest',
1439 name: 'foo2.jpg',
1440 type: 'image/jpeg',
1441 data: new File([sampleImage], { type: 'image/jpeg' }),
1442 })
1443
1444 const [file1, file2] = core.getFiles()
1445 core.setFileState(file1.id, { progress: { ...file1.progress, uploadStarted: new Date() } })
1446 core.setFileState(file2.id, { progress: { ...file2.progress, uploadStarted: new Date() } })
1447
1448 core.emit('upload-progress', core.getFile(file1.id), {
1449 bytesUploaded: 12345,
1450 bytesTotal: 17175,
1451 })
1452
1453 core.emit('upload-progress', core.getFile(file2.id), {
1454 bytesUploaded: 10201,
1455 bytesTotal: 17175,
1456 })
1457
1458 core._calculateTotalProgress()
1459 core._calculateProgress.flush()
1460
1461 expect(core.getState().totalProgress).toEqual(66)
1462
1463 core.resetProgress()
1464
1465 expect(core.getFile(file1.id).progress).toEqual({
1466 percentage: 0,
1467 bytesUploaded: 0,
1468 bytesTotal: 17175,
1469 uploadComplete: false,
1470 uploadStarted: null,
1471 })
1472 expect(core.getFile(file2.id).progress).toEqual({
1473 percentage: 0,
1474 bytesUploaded: 0,
1475 bytesTotal: 17175,
1476 uploadComplete: false,
1477 uploadStarted: null,
1478 })
1479 expect(core.getState().totalProgress).toEqual(0)
1480 expect(resetProgressEvent.mock.calls.length).toEqual(1)
1481 })
1482 })
1483
1484 describe('checkRestrictions', () => {
1485 it('should enforce the maxNumberOfFiles rule', () => {
1486 const core = new Core({
1487 restrictions: {
1488 maxNumberOfFiles: 1,
1489 },
1490 })
1491
1492 // add 2 files
1493 core.addFile({
1494 source: 'jest',
1495 name: 'foo1.jpg',
1496 type: 'image/jpeg',
1497 data: new File([sampleImage], { type: 'image/jpeg' }),
1498 })
1499 try {
1500 core.addFile({
1501 source: 'jest',
1502 name: 'foo2.jpg',
1503 type: 'image/jpeg',
1504 data: new File([sampleImage], { type: 'image/jpeg' }),
1505 })
1506 throw new Error('should have thrown')
1507 } catch (err) {
1508 expect(err).toMatchObject(new Error('You can only upload 1 file'))
1509 expect(core.getState().info.message).toEqual('You can only upload 1 file')
1510 }
1511 })
1512
1513 xit('should enforce the minNumberOfFiles rule', () => {})
1514
1515 it('should enforce the allowedFileTypes rule', () => {
1516 const core = new Core({
1517 restrictions: {
1518 allowedFileTypes: ['image/gif', 'image/png'],
1519 },
1520 })
1521
1522 try {
1523 core.addFile({
1524 source: 'jest',
1525 name: 'foo2.jpg',
1526 type: 'image/jpeg',
1527 data: new File([sampleImage], { type: 'image/jpeg' }),
1528 })
1529 throw new Error('should have thrown')
1530 } catch (err) {
1531 expect(err).toMatchObject(new Error('You can only upload: image/gif, image/png'))
1532 expect(core.getState().info.message).toEqual('You can only upload: image/gif, image/png')
1533 }
1534 })
1535
1536 it('should throw if allowedFileTypes is not an array', () => {
1537 try {
1538 const core = Core({
1539 restrictions: {
1540 allowedFileTypes: 'image/gif',
1541 },
1542 })
1543 core.log('hi')
1544 } catch (err) {
1545 expect(err).toMatchObject(new Error('`restrictions.allowedFileTypes` must be an array'))
1546 }
1547 })
1548
1549 it('should enforce the allowedFileTypes rule with file extensions', () => {
1550 const core = new Core({
1551 restrictions: {
1552 allowedFileTypes: ['.gif', '.jpg', '.jpeg'],
1553 },
1554 })
1555
1556 try {
1557 core.addFile({
1558 source: 'jest',
1559 name: 'foo2.png',
1560 type: '',
1561 data: new File([sampleImage], { type: 'image/jpeg' }),
1562 })
1563 throw new Error('should have thrown')
1564 } catch (err) {
1565 expect(err).toMatchObject(new Error('You can only upload: .gif, .jpg, .jpeg'))
1566 expect(core.getState().info.message).toEqual('You can only upload: .gif, .jpg, .jpeg')
1567 }
1568
1569 expect(() => core.addFile({
1570 source: 'jest',
1571 name: 'foo2.JPG',
1572 type: '',
1573 data: new File([sampleImage], { type: 'image/jpeg' }),
1574 }).not.toThrow())
1575 })
1576
1577 it('should enforce the maxFileSize rule', () => {
1578 const core = new Core({
1579 restrictions: {
1580 maxFileSize: 1234,
1581 },
1582 })
1583
1584 try {
1585 core.addFile({
1586 source: 'jest',
1587 name: 'foo.jpg',
1588 type: 'image/jpeg',
1589 data: new File([sampleImage], { type: 'image/jpeg' }),
1590 })
1591 throw new Error('should have thrown')
1592 } catch (err) {
1593 expect(err).toMatchObject(new Error('This file exceeds maximum allowed size of 1.2 KB'))
1594 expect(core.getState().info.message).toEqual('This file exceeds maximum allowed size of 1.2 KB')
1595 }
1596 })
1597
1598 it('should enforce the minFileSize rule', () => {
1599 const core = new Core({
1600 restrictions: {
1601 minFileSize: 1073741824,
1602 },
1603 })
1604
1605 try {
1606 core.addFile({
1607 source: 'jest',
1608 name: 'foo.jpg',
1609 type: 'image/jpeg',
1610 data: new File([sampleImage], { type: 'image/jpeg' }),
1611 })
1612 throw new Error('should have thrown')
1613 } catch (err) {
1614 expect(err).toMatchObject(new Error('This file is smaller than the allowed size of 1 GB'))
1615 expect(core.getState().info.message).toEqual('This file is smaller than the allowed size of 1 GB')
1616 }
1617 })
1618
1619 it('should enforce the maxTotalFileSize rule', () => {
1620 const core = new Core({
1621 restrictions: {
1622 maxTotalFileSize: 34000,
1623 },
1624 })
1625
1626 core.addFile({
1627 source: 'jest',
1628 name: 'foo.jpg',
1629 type: 'image/jpeg',
1630 data: new File([sampleImage], { type: 'image/jpeg' }),
1631 })
1632
1633 expect(() => {
1634 core.addFile({
1635 source: 'jest',
1636 name: 'foo1.jpg',
1637 type: 'image/jpeg',
1638 data: new File([sampleImage], { type: 'image/jpeg' }),
1639 })
1640 }).toThrowError(
1641 new Error('This file exceeds maximum allowed size of 33 KB')
1642 )
1643 })
1644
1645 it('should check if a file validateRestrictions', () => {
1646 const core = new Core({
1647 restrictions: {
1648 minFileSize: 300000,
1649 },
1650 })
1651
1652 const core2 = new Core({
1653 restrictions: {
1654 allowedFileTypes: ['image/png'],
1655 },
1656 })
1657
1658 const newFile = {
1659 source: 'jest',
1660 name: 'foo1.jpg',
1661 extension: 'jpg',
1662 type: 'image/jpeg',
1663 data: new File([sampleImage], { type: 'image/jpeg' }),
1664 isFolder: false,
1665 mimeType: 'image/jpeg',
1666 modifiedDate: '2016-04-13T15:11:31.204Z',
1667 size: 270733,
1668 }
1669
1670 const validateRestrictions1 = core.validateRestrictions(newFile)
1671 const validateRestrictions2 = core2.validateRestrictions(newFile)
1672
1673 expect(validateRestrictions1).toMatchObject(
1674 {
1675 result: false,
1676 reason: 'This file is smaller than the allowed size of 293 KB',
1677 }
1678 )
1679 expect(validateRestrictions2).toMatchObject(
1680 {
1681 result: false,
1682 reason: 'You can only upload: image/png',
1683 }
1684 )
1685 })
1686
1687 it('should emit `restriction-failed` event when some rule is violated', () => {
1688 const maxFileSize = 100
1689 const core = new Core({
1690 restrictions: {
1691 maxFileSize,
1692 },
1693 })
1694 const restrictionsViolatedEventMock = jest.fn()
1695 const file = {
1696 name: 'test.jpg',
1697 data: new Blob([Buffer.alloc(2 * maxFileSize)]),
1698 }
1699 const errorMessage = `${core.i18n('exceedsSize')} ${prettierBytes(maxFileSize)}`
1700 try {
1701 core.on('restriction-failed', restrictionsViolatedEventMock)
1702 core.addFile(file)
1703 } catch (err) {}
1704
1705 expect(restrictionsViolatedEventMock.mock.calls.length).toEqual(1)
1706 expect(restrictionsViolatedEventMock.mock.calls[0][0].name).toEqual(file.name)
1707 expect(restrictionsViolatedEventMock.mock.calls[0][1].message).toEqual(errorMessage)
1708 })
1709 })
1710
1711 describe('actions', () => {
1712 it('should update the state when receiving the error event', () => {
1713 const core = new Core()
1714 core.emit('error', new Error('foooooo'))
1715 expect(core.getState().error).toEqual('foooooo')
1716 })
1717
1718 it('should update the state when receiving the upload-error event', () => {
1719 const core = new Core()
1720 core.setState({
1721 files: {
1722 fileId: {
1723 id: 'fileId',
1724 name: 'filename',
1725 },
1726 },
1727 })
1728 core.emit('upload-error', core.getFile('fileId'), new Error('this is the error'))
1729 expect(core.getState().info).toEqual({
1730 message: 'Failed to upload filename',
1731 details: 'this is the error',
1732 isHidden: false,
1733 type: 'error',
1734 })
1735 })
1736
1737 it('should reset the error state when receiving the upload event', () => {
1738 const core = new Core()
1739 core.emit('error', { foo: 'bar' })
1740 core.emit('upload')
1741 expect(core.getState().error).toEqual(null)
1742 })
1743 })
1744
1745 describe('updateOnlineStatus', () => {
1746 const RealNavigatorOnline = global.window.navigator.onLine
1747
1748 function mockNavigatorOnline (status) {
1749 Object.defineProperty(
1750 global.window.navigator,
1751 'onLine',
1752 {
1753 value: status,
1754 writable: true,
1755 }
1756 )
1757 }
1758
1759 afterEach(() => {
1760 global.window.navigator.onLine = RealNavigatorOnline
1761 })
1762
1763 it('should emit the correct event based on whether there is a network connection', () => {
1764 const onlineEventMock = jest.fn()
1765 const offlineEventMock = jest.fn()
1766 const backOnlineEventMock = jest.fn()
1767 const core = new Core()
1768 core.on('is-offline', offlineEventMock)
1769 core.on('is-online', onlineEventMock)
1770 core.on('back-online', backOnlineEventMock)
1771
1772 mockNavigatorOnline(true)
1773 core.updateOnlineStatus()
1774 expect(onlineEventMock.mock.calls.length).toEqual(1)
1775 expect(offlineEventMock.mock.calls.length).toEqual(0)
1776 expect(backOnlineEventMock.mock.calls.length).toEqual(0)
1777
1778 mockNavigatorOnline(false)
1779 core.updateOnlineStatus()
1780 expect(onlineEventMock.mock.calls.length).toEqual(1)
1781 expect(offlineEventMock.mock.calls.length).toEqual(1)
1782 expect(backOnlineEventMock.mock.calls.length).toEqual(0)
1783
1784 mockNavigatorOnline(true)
1785 core.updateOnlineStatus()
1786 expect(onlineEventMock.mock.calls.length).toEqual(2)
1787 expect(offlineEventMock.mock.calls.length).toEqual(1)
1788 expect(backOnlineEventMock.mock.calls.length).toEqual(1)
1789 })
1790 })
1791
1792 describe('info', () => {
1793 it('should set a string based message to be displayed infinitely', () => {
1794 const infoVisibleEvent = jest.fn()
1795 const core = new Core()
1796 core.on('info-visible', infoVisibleEvent)
1797
1798 core.info('This is the message', 'info', 0)
1799 expect(core.getState().info).toEqual({
1800 isHidden: false,
1801 type: 'info',
1802 message: 'This is the message',
1803 details: null,
1804 })
1805 expect(infoVisibleEvent.mock.calls.length).toEqual(1)
1806 expect(typeof core.infoTimeoutID).toEqual('undefined')
1807 })
1808
1809 it('should set a object based message to be displayed infinitely', () => {
1810 const infoVisibleEvent = jest.fn()
1811 const core = new Core()
1812 core.on('info-visible', infoVisibleEvent)
1813
1814 core.info({
1815 message: 'This is the message',
1816 details: {
1817 foo: 'bar',
1818 },
1819 }, 'warning', 0)
1820 expect(core.getState().info).toEqual({
1821 isHidden: false,
1822 type: 'warning',
1823 message: 'This is the message',
1824 details: {
1825 foo: 'bar',
1826 },
1827 })
1828 expect(infoVisibleEvent.mock.calls.length).toEqual(1)
1829 expect(typeof core.infoTimeoutID).toEqual('undefined')
1830 })
1831
1832 it('should set an info message to be displayed for a period of time before hiding', (done) => {
1833 const infoVisibleEvent = jest.fn()
1834 const infoHiddenEvent = jest.fn()
1835 const core = new Core()
1836 core.on('info-visible', infoVisibleEvent)
1837 core.on('info-hidden', infoHiddenEvent)
1838
1839 core.info('This is the message', 'info', 100)
1840 expect(typeof core.infoTimeoutID).toEqual('number')
1841 expect(infoHiddenEvent.mock.calls.length).toEqual(0)
1842 setTimeout(() => {
1843 expect(infoHiddenEvent.mock.calls.length).toEqual(1)
1844 expect(core.getState().info).toEqual({
1845 isHidden: true,
1846 type: 'info',
1847 message: 'This is the message',
1848 details: null,
1849 })
1850 done()
1851 }, 110)
1852 })
1853
1854 it('should hide an info message', () => {
1855 const infoVisibleEvent = jest.fn()
1856 const infoHiddenEvent = jest.fn()
1857 const core = new Core()
1858 core.on('info-visible', infoVisibleEvent)
1859 core.on('info-hidden', infoHiddenEvent)
1860
1861 core.info('This is the message', 'info', 0)
1862 expect(typeof core.infoTimeoutID).toEqual('undefined')
1863 expect(infoHiddenEvent.mock.calls.length).toEqual(0)
1864 core.hideInfo()
1865 expect(infoHiddenEvent.mock.calls.length).toEqual(1)
1866 expect(core.getState().info).toEqual({
1867 isHidden: true,
1868 type: 'info',
1869 message: 'This is the message',
1870 details: null,
1871 })
1872 })
1873 })
1874
1875 describe('createUpload', () => {
1876 it('should assign the specified files to a new upload', () => {
1877 const core = new Core()
1878 core.addFile({
1879 source: 'jest',
1880 name: 'foo.jpg',
1881 type: 'image/jpeg',
1882 data: new File([sampleImage], { type: 'image/jpeg' }),
1883 })
1884
1885 core._createUpload(Object.keys(core.getState().files))
1886 const uploadId = Object.keys(core.getState().currentUploads)[0]
1887 const currentUploadsState = {}
1888 currentUploadsState[uploadId] = {
1889 fileIDs: Object.keys(core.getState().files),
1890 step: 0,
1891 result: {},
1892 }
1893 expect(core.getState().currentUploads).toEqual(currentUploadsState)
1894 })
1895 })
1896
1897 describe('i18n', () => {
1898 it('merges in custom locale strings', () => {
1899 const core = new Core({
1900 locale: {
1901 strings: {
1902 test: 'beep boop',
1903 },
1904 },
1905 })
1906
1907 expect(core.i18n('exceedsSize')).toBe('This file exceeds maximum allowed size of')
1908 expect(core.i18n('test')).toBe('beep boop')
1909 })
1910 })
1911
1912 describe('default restrictions', () => {
1913 it('should be merged with supplied restrictions', () => {
1914 const core = new Core({
1915 restrictions: {
1916 maxNumberOfFiles: 3,
1917 },
1918 })
1919
1920 expect(core.opts.restrictions.maxNumberOfFiles).toBe(3)
1921 expect(core.opts.restrictions.minNumberOfFiles).toBe(null)
1922 })
1923 })
1924
1925 describe('log', () => {
1926 it('should log via provided logger function', () => {
1927 const myTestLogger = {
1928 debug: jest.fn(),
1929 warn: jest.fn(),
1930 error: jest.fn(),
1931 }
1932
1933 const core = new Core({
1934 logger: myTestLogger,
1935 })
1936
1937 core.log('test test')
1938 core.log('test test', 'error')
1939 core.log('test test', 'error')
1940 core.log('test test', 'warning')
1941
1942 // logger.debug should have been called 1 time above,
1943 // but we call log in Core’s constructor to output VERSION, hence +1 here
1944 expect(core.opts.logger.debug.mock.calls.length).toBe(2)
1945 expect(core.opts.logger.error.mock.calls.length).toBe(2)
1946 expect(core.opts.logger.warn.mock.calls.length).toBe(1)
1947 })
1948
1949 it('should log via provided logger function, even if debug: true', () => {
1950 const myTestLogger = {
1951 debug: jest.fn(),
1952 warn: jest.fn(),
1953 error: jest.fn(),
1954 }
1955
1956 const core = new Core({
1957 logger: myTestLogger,
1958 debug: true,
1959 })
1960
1961 core.log('test test')
1962 core.log('test test', 'error')
1963 core.log('test test', 'error')
1964 core.log('test test', 'warning')
1965
1966 // logger.debug should have been called 1 time above,
1967 // but we call log in Core’s constructor to output VERSION, hence +1 here
1968 expect(core.opts.logger.debug.mock.calls.length).toBe(2)
1969 expect(core.opts.logger.error.mock.calls.length).toBe(2)
1970 // logger.warn should have been called 1 time above,
1971 // but we warn in Core when using both logger and debug: true, hence +1 here
1972 expect(core.opts.logger.warn.mock.calls.length).toBe(2)
1973 })
1974
1975 it('should log to console when logger: Uppy.debugLogger or debug: true is set', () => {
1976 console.debug = jest.fn()
1977 console.error = jest.fn()
1978
1979 const core = new Core({
1980 logger: Core.debugLogger,
1981 })
1982
1983 core.log('test test')
1984 core.log('beep boop')
1985 core.log('beep beep', 'error')
1986
1987 // console.debug debug should have been called 2 times above,
1988 // ibut we call log n Core’ constructor to output VERSION, hence +1 here
1989 expect(console.debug.mock.calls.length).toBe(3)
1990 expect(console.error.mock.calls.length).toBe(1)
1991
1992 console.debug.mockClear()
1993 console.error.mockClear()
1994
1995 const core2 = new Core({
1996 debug: true,
1997 })
1998
1999 core2.log('test test')
2000 core2.log('beep boop')
2001 core2.log('beep beep', 'error')
2002
2003 // console.debug debug should have been called 2 times here,
2004 // but we call log in Core constructor to output VERSION, hence +1 here
2005 expect(console.debug.mock.calls.length).toBe(3)
2006 expect(console.error.mock.calls.length).toBe(1)
2007 })
2008
2009 it('should only log errors to console when logger is not set', () => {
2010 console.debug = jest.fn()
2011 console.error = jest.fn()
2012
2013 const core = new Core()
2014
2015 core.log('test test')
2016 core.log('beep boop')
2017 core.log('beep beep', 'error')
2018
2019 expect(console.debug.mock.calls.length).toBe(0)
2020 expect(console.error.mock.calls.length).toBe(1)
2021 })
2022 })
2023})