UNPKG

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