UNPKG

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