Core.test.js 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323
  1. const fs = require('fs')
  2. const path = require('path')
  3. const Core = require('./Core')
  4. const utils = require('./Utils')
  5. const Plugin = require('./Plugin')
  6. const AcquirerPlugin1 = require('../../test/mocks/acquirerPlugin1')
  7. const AcquirerPlugin2 = require('../../test/mocks/acquirerPlugin2')
  8. const InvalidPlugin = require('../../test/mocks/invalidPlugin')
  9. const InvalidPluginWithoutId = require('../../test/mocks/invalidPluginWithoutId')
  10. const InvalidPluginWithoutType = require('../../test/mocks/invalidPluginWithoutType')
  11. jest.mock('cuid', () => {
  12. return () => 'cjd09qwxb000dlql4tp4doz8h'
  13. })
  14. const sampleImage = fs.readFileSync(path.join(__dirname, '../../test/resources/image.jpg'))
  15. describe('src/Core', () => {
  16. const RealCreateObjectUrl = global.URL.createObjectURL
  17. beforeEach(() => {
  18. jest.spyOn(utils, 'findDOMElement').mockImplementation(path => {
  19. return 'some config...'
  20. })
  21. jest.spyOn(utils, 'createThumbnail').mockImplementation(path => {
  22. return Promise.resolve(`data:image/jpeg;base64,${sampleImage.toString('base64')}`)
  23. })
  24. utils.createThumbnail.mockClear()
  25. global.URL.createObjectURL = jest.fn().mockReturnValue('newUrl')
  26. })
  27. afterEach(() => {
  28. global.URL.createObjectURL = RealCreateObjectUrl
  29. })
  30. it('should expose a class', () => {
  31. const core = Core()
  32. expect(core.constructor.name).toEqual('Uppy')
  33. })
  34. it('should have a string `id` option that defaults to "uppy"', () => {
  35. const core = Core()
  36. expect(core.getID()).toEqual('uppy')
  37. const core2 = Core({ id: 'profile' })
  38. expect(core2.getID()).toEqual('profile')
  39. })
  40. describe('plugins', () => {
  41. it('should add a plugin to the plugin stack', () => {
  42. const core = Core()
  43. core.use(AcquirerPlugin1)
  44. expect(Object.keys(core.plugins.acquirer).length).toEqual(1)
  45. })
  46. it('should prevent the same plugin from being added more than once', () => {
  47. const core = Core()
  48. core.use(AcquirerPlugin1)
  49. expect(() => {
  50. core.use(AcquirerPlugin1)
  51. }).toThrowErrorMatchingSnapshot()
  52. })
  53. it('should not be able to add an invalid plugin', () => {
  54. const core = Core()
  55. expect(() => {
  56. core.use(InvalidPlugin)
  57. }).toThrowErrorMatchingSnapshot()
  58. })
  59. it('should not be able to add a plugin that has no id', () => {
  60. const core = Core()
  61. expect(() =>
  62. core.use(InvalidPluginWithoutId)
  63. ).toThrowErrorMatchingSnapshot()
  64. })
  65. it('should not be able to add a plugin that has no type', () => {
  66. const core = Core()
  67. expect(() =>
  68. core.use(InvalidPluginWithoutType)
  69. ).toThrowErrorMatchingSnapshot()
  70. })
  71. it('should return the plugin that matches the specified name', () => {
  72. const core = new Core()
  73. expect(core.getPlugin('foo')).toEqual(null)
  74. core.use(AcquirerPlugin1)
  75. const plugin = core.getPlugin('TestSelector1')
  76. expect(plugin.id).toEqual('TestSelector1')
  77. expect(plugin instanceof Plugin)
  78. })
  79. it('should call the specified method on all the plugins', () => {
  80. const core = new Core()
  81. core.use(AcquirerPlugin1)
  82. core.use(AcquirerPlugin2)
  83. core.iteratePlugins(plugin => {
  84. plugin.run('hello')
  85. })
  86. expect(core.plugins.acquirer[0].mocks.run.mock.calls.length).toEqual(1)
  87. expect(core.plugins.acquirer[0].mocks.run.mock.calls[0]).toEqual([
  88. 'hello'
  89. ])
  90. expect(core.plugins.acquirer[1].mocks.run.mock.calls.length).toEqual(1)
  91. expect(core.plugins.acquirer[1].mocks.run.mock.calls[0]).toEqual([
  92. 'hello'
  93. ])
  94. })
  95. it('should uninstall and the remove the specified plugin', () => {
  96. const core = new Core()
  97. core.use(AcquirerPlugin1)
  98. core.use(AcquirerPlugin2)
  99. expect(Object.keys(core.plugins.acquirer).length).toEqual(2)
  100. const plugin = core.getPlugin('TestSelector1')
  101. core.removePlugin(plugin)
  102. expect(Object.keys(core.plugins.acquirer).length).toEqual(1)
  103. expect(plugin.mocks.uninstall.mock.calls.length).toEqual(1)
  104. expect(core.plugins.acquirer[0].mocks.run.mock.calls.length).toEqual(0)
  105. })
  106. })
  107. describe('state', () => {
  108. it('should update all the plugins with the new state when the updateAll method is called', () => {
  109. const core = new Core()
  110. core.use(AcquirerPlugin1)
  111. core.use(AcquirerPlugin2)
  112. core.updateAll({ foo: 'bar' })
  113. expect(core.plugins.acquirer[0].mocks.update.mock.calls.length).toEqual(1)
  114. expect(core.plugins.acquirer[0].mocks.update.mock.calls[0]).toEqual([
  115. { foo: 'bar' }
  116. ])
  117. expect(core.plugins.acquirer[1].mocks.update.mock.calls.length).toEqual(1)
  118. expect(core.plugins.acquirer[1].mocks.update.mock.calls[0]).toEqual([
  119. { foo: 'bar' }
  120. ])
  121. })
  122. it('should update the state', () => {
  123. const core = new Core()
  124. const stateUpdateEventMock = jest.fn()
  125. core.on('state-update', stateUpdateEventMock)
  126. core.use(AcquirerPlugin1)
  127. core.use(AcquirerPlugin2)
  128. core.setState({ foo: 'bar', bee: 'boo' })
  129. core.setState({ foo: 'baar' })
  130. const newState = {
  131. bee: 'boo',
  132. capabilities: { resumableUploads: false },
  133. files: {},
  134. currentUploads: {},
  135. foo: 'baar',
  136. info: { isHidden: true, message: '', type: 'info' },
  137. meta: {},
  138. plugins: {},
  139. totalProgress: 0
  140. }
  141. expect(core.state).toEqual(newState)
  142. expect(core.plugins.acquirer[0].mocks.update.mock.calls[1]).toEqual([
  143. newState
  144. ])
  145. expect(core.plugins.acquirer[1].mocks.update.mock.calls[1]).toEqual([
  146. newState
  147. ])
  148. expect(stateUpdateEventMock.mock.calls.length).toEqual(2)
  149. // current state
  150. expect(stateUpdateEventMock.mock.calls[1][0]).toEqual({
  151. bee: 'boo',
  152. capabilities: { resumableUploads: false },
  153. files: {},
  154. currentUploads: {},
  155. foo: 'bar',
  156. info: { isHidden: true, message: '', type: 'info' },
  157. meta: {},
  158. plugins: {},
  159. totalProgress: 0
  160. })
  161. // new state
  162. expect(stateUpdateEventMock.mock.calls[1][1]).toEqual({
  163. bee: 'boo',
  164. capabilities: { resumableUploads: false },
  165. files: {},
  166. currentUploads: {},
  167. foo: 'baar',
  168. info: { isHidden: true, message: '', type: 'info' },
  169. meta: {},
  170. plugins: {},
  171. totalProgress: 0
  172. })
  173. })
  174. it('should get the state', () => {
  175. const core = new Core()
  176. core.setState({ foo: 'bar' })
  177. expect(core.getState()).toEqual({
  178. capabilities: { resumableUploads: false },
  179. files: {},
  180. currentUploads: {},
  181. foo: 'bar',
  182. info: { isHidden: true, message: '', type: 'info' },
  183. meta: {},
  184. plugins: {},
  185. totalProgress: 0
  186. })
  187. })
  188. })
  189. it('should reset when the reset method is called', () => {
  190. const core = new Core()
  191. // const corePauseEventMock = jest.fn()
  192. const coreCancelEventMock = jest.fn()
  193. const coreStateUpdateEventMock = jest.fn()
  194. core.on('cancel-all', coreCancelEventMock)
  195. core.on('state-update', coreStateUpdateEventMock)
  196. core.setState({ foo: 'bar', totalProgress: 30 })
  197. core.reset()
  198. // expect(corePauseEventMock.mock.calls.length).toEqual(1)
  199. expect(coreCancelEventMock.mock.calls.length).toEqual(1)
  200. expect(coreStateUpdateEventMock.mock.calls.length).toEqual(2)
  201. expect(coreStateUpdateEventMock.mock.calls[1][1]).toEqual({
  202. capabilities: { resumableUploads: false },
  203. files: {},
  204. currentUploads: {},
  205. foo: 'bar',
  206. info: { isHidden: true, message: '', type: 'info' },
  207. meta: {},
  208. plugins: {},
  209. totalProgress: 0
  210. })
  211. })
  212. it('should clear all uploads on cancelAll()', () => {
  213. const core = new Core()
  214. const id = core._createUpload([ 'a', 'b' ])
  215. expect(core.state.currentUploads[id]).toBeDefined()
  216. core.cancelAll()
  217. expect(core.state.currentUploads[id]).toBeUndefined()
  218. })
  219. it('should close, reset and uninstall when the close method is called', () => {
  220. const core = new Core()
  221. core.use(AcquirerPlugin1)
  222. // const corePauseEventMock = jest.fn()
  223. const coreCancelEventMock = jest.fn()
  224. const coreStateUpdateEventMock = jest.fn()
  225. // core.on('pause-all', corePauseEventMock)
  226. core.on('cancel-all', coreCancelEventMock)
  227. core.on('state-update', coreStateUpdateEventMock)
  228. core.close()
  229. // expect(corePauseEventMock.mock.calls.length).toEqual(1)
  230. expect(coreCancelEventMock.mock.calls.length).toEqual(1)
  231. expect(coreStateUpdateEventMock.mock.calls.length).toEqual(1)
  232. expect(coreStateUpdateEventMock.mock.calls[0][1]).toEqual({
  233. capabilities: { resumableUploads: false },
  234. files: {},
  235. currentUploads: {},
  236. info: { isHidden: true, message: '', type: 'info' },
  237. meta: {},
  238. plugins: {},
  239. totalProgress: 0
  240. })
  241. expect(core.plugins.acquirer[0].mocks.uninstall.mock.calls.length).toEqual(
  242. 1
  243. )
  244. })
  245. describe('upload hooks', () => {
  246. it('should add data returned from upload hooks to the .upload() result', () => {
  247. const core = new Core()
  248. core.addPreProcessor((fileIDs, uploadID) => {
  249. core.addResultData(uploadID, { pre: 'ok' })
  250. })
  251. core.addPostProcessor((fileIDs, uploadID) => {
  252. core.addResultData(uploadID, { post: 'ok' })
  253. })
  254. core.addUploader((fileIDs, uploadID) => {
  255. core.addResultData(uploadID, { upload: 'ok' })
  256. })
  257. core.run()
  258. return core.upload().then((result) => {
  259. expect(result.pre).toBe('ok')
  260. expect(result.upload).toBe('ok')
  261. expect(result.post).toBe('ok')
  262. })
  263. })
  264. })
  265. describe('preprocessors', () => {
  266. it('should add a preprocessor', () => {
  267. const core = new Core()
  268. const preprocessor = function () {}
  269. core.addPreProcessor(preprocessor)
  270. expect(core.preProcessors[0]).toEqual(preprocessor)
  271. })
  272. it('should remove a preprocessor', () => {
  273. const core = new Core()
  274. const preprocessor1 = function () {}
  275. const preprocessor2 = function () {}
  276. const preprocessor3 = function () {}
  277. core.addPreProcessor(preprocessor1)
  278. core.addPreProcessor(preprocessor2)
  279. core.addPreProcessor(preprocessor3)
  280. expect(core.preProcessors.length).toEqual(3)
  281. core.removePreProcessor(preprocessor2)
  282. expect(core.preProcessors.length).toEqual(2)
  283. })
  284. it('should execute all the preprocessors when uploading a file', () => {
  285. const core = new Core()
  286. const preprocessor1 = jest.fn()
  287. const preprocessor2 = jest.fn()
  288. core.addPreProcessor(preprocessor1)
  289. core.addPreProcessor(preprocessor2)
  290. core.addFile({
  291. source: 'jest',
  292. name: 'foo.jpg',
  293. type: 'image/jpeg',
  294. data: new File([sampleImage], { type: 'image/jpeg' })
  295. })
  296. return core.upload()
  297. .then(() => {
  298. const fileId = Object.keys(core.state.files)[0]
  299. expect(preprocessor1.mock.calls.length).toEqual(1)
  300. expect(preprocessor1.mock.calls[0][0].length).toEqual(1)
  301. expect(preprocessor1.mock.calls[0][0][0]).toEqual(fileId)
  302. expect(preprocessor2.mock.calls[0][0].length).toEqual(1)
  303. expect(preprocessor2.mock.calls[0][0][0]).toEqual(fileId)
  304. })
  305. })
  306. it('should update the file progress state when preprocess-progress event is fired', () => {
  307. const core = new Core()
  308. core.run()
  309. core.addFile({
  310. source: 'jest',
  311. name: 'foo.jpg',
  312. type: 'image/jpeg',
  313. data: new File([sampleImage], { type: 'image/jpeg' })
  314. })
  315. const fileId = Object.keys(core.state.files)[0]
  316. const file = core.getFile(fileId)
  317. core.emit('preprocess-progress', file, {
  318. mode: 'determinate',
  319. message: 'something',
  320. value: 0
  321. })
  322. expect(core.state.files[fileId].progress).toEqual({
  323. percentage: 0,
  324. bytesUploaded: 0,
  325. bytesTotal: 17175,
  326. uploadComplete: false,
  327. uploadStarted: false,
  328. preprocess: { mode: 'determinate', message: 'something', value: 0 }
  329. })
  330. })
  331. it('should update the file progress state when preprocess-complete event is fired', () => {
  332. const core = new Core()
  333. core.run()
  334. core.addFile({
  335. source: 'jest',
  336. name: 'foo.jpg',
  337. type: 'image/jpeg',
  338. data: new File([sampleImage], { type: 'image/jpeg' })
  339. })
  340. const fileID = Object.keys(core.state.files)[0]
  341. const file = core.state.files[fileID]
  342. core.emit('preprocess-complete', file, {
  343. mode: 'determinate',
  344. message: 'something',
  345. value: 0
  346. })
  347. expect(core.state.files[fileID].progress).toEqual({
  348. percentage: 0,
  349. bytesUploaded: 0,
  350. bytesTotal: 17175,
  351. uploadComplete: false,
  352. uploadStarted: false
  353. })
  354. })
  355. })
  356. describe('postprocessors', () => {
  357. it('should add a postprocessor', () => {
  358. const core = new Core()
  359. const postprocessor = function () {}
  360. core.addPostProcessor(postprocessor)
  361. expect(core.postProcessors[0]).toEqual(postprocessor)
  362. })
  363. it('should remove a postprocessor', () => {
  364. const core = new Core()
  365. const postprocessor1 = function () {}
  366. const postprocessor2 = function () {}
  367. const postprocessor3 = function () {}
  368. core.addPostProcessor(postprocessor1)
  369. core.addPostProcessor(postprocessor2)
  370. core.addPostProcessor(postprocessor3)
  371. expect(core.postProcessors.length).toEqual(3)
  372. core.removePostProcessor(postprocessor2)
  373. expect(core.postProcessors.length).toEqual(2)
  374. })
  375. it('should execute all the postprocessors when uploading a file', () => {
  376. const core = new Core()
  377. const postprocessor1 = jest.fn()
  378. const postprocessor2 = jest.fn()
  379. core.addPostProcessor(postprocessor1)
  380. core.addPostProcessor(postprocessor2)
  381. core.addFile({
  382. source: 'jest',
  383. name: 'foo.jpg',
  384. type: 'image/jpeg',
  385. data: new File([sampleImage], { type: 'image/jpeg' })
  386. })
  387. return core.upload().then(() => {
  388. expect(postprocessor1.mock.calls.length).toEqual(1)
  389. // const lastModifiedTime = new Date()
  390. // const fileId = 'foojpg' + lastModifiedTime.getTime()
  391. const fileId = 'uppy-foojpg-image'
  392. expect(postprocessor1.mock.calls[0][0].length).toEqual(1)
  393. expect(postprocessor1.mock.calls[0][0][0].substring(0, 17)).toEqual(
  394. fileId.substring(0, 17)
  395. )
  396. expect(postprocessor2.mock.calls[0][0].length).toEqual(1)
  397. expect(postprocessor2.mock.calls[0][0][0].substring(0, 17)).toEqual(
  398. fileId.substring(0, 17)
  399. )
  400. })
  401. })
  402. it('should update the file progress state when postprocess-progress event is fired', () => {
  403. const core = new Core()
  404. core.run()
  405. core.addFile({
  406. source: 'jest',
  407. name: 'foo.jpg',
  408. type: 'image/jpeg',
  409. data: new File([sampleImage], { type: 'image/jpeg' })
  410. })
  411. const fileId = Object.keys(core.state.files)[0]
  412. const file = core.getFile(fileId)
  413. core.emit('postprocess-progress', file, {
  414. mode: 'determinate',
  415. message: 'something',
  416. value: 0
  417. })
  418. expect(core.state.files[fileId].progress).toEqual({
  419. percentage: 0,
  420. bytesUploaded: 0,
  421. bytesTotal: 17175,
  422. uploadComplete: false,
  423. uploadStarted: false,
  424. postprocess: { mode: 'determinate', message: 'something', value: 0 }
  425. })
  426. })
  427. it('should update the file progress state when postprocess-complete event is fired', () => {
  428. const core = new Core()
  429. core.run()
  430. core.addFile({
  431. source: 'jest',
  432. name: 'foo.jpg',
  433. type: 'image/jpeg',
  434. data: new File([sampleImage], { type: 'image/jpeg' })
  435. })
  436. const fileId = Object.keys(core.state.files)[0]
  437. const file = core.state.files[fileId]
  438. core.emit('postprocess-complete', file, {
  439. mode: 'determinate',
  440. message: 'something',
  441. value: 0
  442. })
  443. expect(core.state.files[fileId].progress).toEqual({
  444. percentage: 0,
  445. bytesUploaded: 0,
  446. bytesTotal: 17175,
  447. uploadComplete: false,
  448. uploadStarted: false
  449. })
  450. })
  451. })
  452. describe('uploaders', () => {
  453. it('should add an uploader', () => {
  454. const core = new Core()
  455. const uploader = function () {}
  456. core.addUploader(uploader)
  457. expect(core.uploaders[0]).toEqual(uploader)
  458. })
  459. it('should remove an uploader', () => {
  460. const core = new Core()
  461. const uploader1 = function () {}
  462. const uploader2 = function () {}
  463. const uploader3 = function () {}
  464. core.addUploader(uploader1)
  465. core.addUploader(uploader2)
  466. core.addUploader(uploader3)
  467. expect(core.uploaders.length).toEqual(3)
  468. core.removeUploader(uploader2)
  469. expect(core.uploaders.length).toEqual(2)
  470. })
  471. })
  472. describe('adding a file', () => {
  473. it('should call onBeforeFileAdded if it was specified in the options when initialising the class', () => {
  474. const onBeforeFileAdded = jest.fn()
  475. const core = new Core({
  476. onBeforeFileAdded
  477. })
  478. core.addFile({
  479. source: 'jest',
  480. name: 'foo.jpg',
  481. type: 'image/jpeg',
  482. data: new File([sampleImage], { type: 'image/jpeg' })
  483. })
  484. expect(onBeforeFileAdded.mock.calls.length).toEqual(1)
  485. expect(onBeforeFileAdded.mock.calls[0][0].name).toEqual('foo.jpg')
  486. expect(onBeforeFileAdded.mock.calls[0][1]).toEqual({})
  487. })
  488. it('should add a file', () => {
  489. const fileData = new File([sampleImage], { type: 'image/jpeg' })
  490. const fileAddedEventMock = jest.fn()
  491. const core = new Core()
  492. core.run()
  493. core.on('file-added', fileAddedEventMock)
  494. core.addFile({
  495. source: 'jest',
  496. name: 'foo.jpg',
  497. type: 'image/jpeg',
  498. data: fileData
  499. })
  500. const fileId = Object.keys(core.state.files)[0]
  501. const newFile = {
  502. extension: 'jpg',
  503. id: fileId,
  504. isRemote: false,
  505. meta: { name: 'foo.jpg', type: 'image/jpeg' },
  506. name: 'foo.jpg',
  507. preview: undefined,
  508. data: fileData,
  509. progress: {
  510. bytesTotal: 17175,
  511. bytesUploaded: 0,
  512. percentage: 0,
  513. uploadComplete: false,
  514. uploadStarted: false
  515. },
  516. remote: '',
  517. size: 17175,
  518. source: 'jest',
  519. type: 'image/jpeg'
  520. }
  521. expect(core.state.files[fileId]).toEqual(newFile)
  522. expect(fileAddedEventMock.mock.calls[0][0]).toEqual(newFile)
  523. })
  524. it('should not allow a file that does not meet the restrictions', () => {
  525. const core = new Core({
  526. restrictions: {
  527. allowedFileTypes: ['image/gif']
  528. }
  529. })
  530. try {
  531. core.addFile({
  532. source: 'jest',
  533. name: 'foo.jpg',
  534. type: 'image/jpeg',
  535. data: new File([sampleImage], { type: 'image/jpeg' })
  536. })
  537. throw new Error('File was allowed through')
  538. } catch (err) {
  539. expect(err.message).toEqual('You can only upload: image/gif')
  540. }
  541. })
  542. it('should not allow a file if onBeforeFileAdded returned false', () => {
  543. const core = new Core({
  544. onBeforeFileAdded: (file, files) => {
  545. if (file.source === 'jest') {
  546. return false
  547. }
  548. }
  549. })
  550. core.addFile({
  551. source: 'jest',
  552. name: 'foo.jpg',
  553. type: 'image/jpeg',
  554. data: new File([sampleImage], { type: 'image/jpeg' })
  555. })
  556. expect(Object.keys(core.state.files).length).toEqual(0)
  557. })
  558. })
  559. describe('uploading a file', () => {
  560. it('should return a { successful, failed } pair containing file objects', () => {
  561. const core = new Core().run()
  562. core.addUploader((fileIDs) => Promise.resolve())
  563. core.addFile({ source: 'jest', name: 'foo.jpg', type: 'image/jpeg', data: new Uint8Array() })
  564. core.addFile({ source: 'jest', name: 'bar.jpg', type: 'image/jpeg', data: new Uint8Array() })
  565. return expect(core.upload()).resolves.toMatchObject({
  566. successful: [
  567. { name: 'foo.jpg' },
  568. { name: 'bar.jpg' }
  569. ],
  570. failed: []
  571. })
  572. })
  573. it('should return files with errors in the { failed } key', () => {
  574. const core = new Core().run()
  575. core.addUploader((fileIDs) => {
  576. fileIDs.forEach((fileID) => {
  577. const file = core.getFile(fileID)
  578. if (/bar/.test(file.name)) {
  579. core.emit('upload-error', file, new Error('This is bar and I do not like bar'))
  580. }
  581. })
  582. return Promise.resolve()
  583. })
  584. core.addFile({ source: 'jest', name: 'foo.jpg', type: 'image/jpeg', data: new Uint8Array() })
  585. core.addFile({ source: 'jest', name: 'bar.jpg', type: 'image/jpeg', data: new Uint8Array() })
  586. return expect(core.upload()).resolves.toMatchObject({
  587. successful: [
  588. { name: 'foo.jpg' }
  589. ],
  590. failed: [
  591. { name: 'bar.jpg', error: 'This is bar and I do not like bar' }
  592. ]
  593. })
  594. })
  595. it('should only upload files that are not already assigned to another upload id', () => {
  596. const core = new Core().run()
  597. core.store.state.currentUploads = {
  598. upload1: {
  599. fileIDs: ['uppy-file1jpg-image/jpeg', 'uppy-file2jpg-image/jpeg', 'uppy-file3jpg-image/jpeg']
  600. },
  601. upload2: {
  602. fileIDs: ['uppy-file4jpg-image/jpeg', 'uppy-file5jpg-image/jpeg', 'uppy-file6jpg-image/jpeg']
  603. }
  604. }
  605. core.addUploader((fileIDs) => Promise.resolve())
  606. core.addFile({ source: 'jest', name: 'foo.jpg', type: 'image/jpeg', data: new Uint8Array() })
  607. core.addFile({ source: 'jest', name: 'bar.jpg', type: 'image/jpeg', data: new Uint8Array() })
  608. core.addFile({ source: 'file3', name: 'file3.jpg', type: 'image/jpeg', data: new Uint8Array() })
  609. return expect(core.upload()).resolves.toMatchSnapshot()
  610. })
  611. it('should not upload if onBeforeUpload returned false', () => {
  612. const core = new Core({
  613. autoProceed: false,
  614. onBeforeUpload: (files) => {
  615. for (var fileId in files) {
  616. if (files[fileId].name === '123.foo') {
  617. return false
  618. }
  619. }
  620. }
  621. })
  622. core.addFile({
  623. source: 'jest',
  624. name: 'foo.jpg',
  625. type: 'image/jpeg',
  626. data: new File([sampleImage], { type: 'image/jpeg' })
  627. })
  628. core.addFile({
  629. source: 'jest',
  630. name: 'bar.jpg',
  631. type: 'image/jpeg',
  632. data: new File([sampleImage], { type: 'image/jpeg' })
  633. })
  634. core.addFile({
  635. source: 'jest',
  636. name: '123.foo',
  637. type: 'image/jpeg',
  638. data: new File([sampleImage], { type: 'image/jpeg' })
  639. })
  640. return core.upload().catch((err) => {
  641. expect(err).toMatchObject(new Error('Not starting the upload because onBeforeUpload returned false'))
  642. })
  643. })
  644. })
  645. describe('removing a file', () => {
  646. it('should remove the file', () => {
  647. const fileRemovedEventMock = jest.fn()
  648. const core = new Core()
  649. core.on('file-removed', fileRemovedEventMock)
  650. core.run()
  651. core.addFile({
  652. source: 'jest',
  653. name: 'foo.jpg',
  654. type: 'image/jpeg',
  655. data: new File([sampleImage], { type: 'image/jpeg' })
  656. })
  657. const fileId = Object.keys(core.state.files)[0]
  658. expect(Object.keys(core.state.files).length).toEqual(1)
  659. core.setState({
  660. totalProgress: 50
  661. })
  662. const file = core.getFile(fileId)
  663. core.removeFile(fileId)
  664. expect(Object.keys(core.state.files).length).toEqual(0)
  665. expect(fileRemovedEventMock.mock.calls[0][0]).toEqual(file)
  666. expect(core.state.totalProgress).toEqual(0)
  667. })
  668. })
  669. describe('restoring a file', () => {
  670. xit('should restore a file', () => {})
  671. xit("should fail to restore a file if it doesn't exist", () => {})
  672. })
  673. describe('get a file', () => {
  674. it('should get the specified file', () => {
  675. const core = new Core()
  676. core.addFile({
  677. source: 'jest',
  678. name: 'foo.jpg',
  679. type: 'image/jpeg',
  680. data: new File([sampleImage], { type: 'image/jpeg' })
  681. })
  682. const fileId = Object.keys(core.state.files)[0]
  683. expect(core.getFile(fileId).name).toEqual('foo.jpg')
  684. expect(core.getFile('non existant file')).toEqual(undefined)
  685. })
  686. })
  687. describe('getFiles', () => {
  688. it('should return an empty array if there are no files', () => {
  689. const core = new Core()
  690. expect(core.getFiles()).toEqual([])
  691. })
  692. it('should return all files as an array', () => {
  693. const core = new Core()
  694. core.addFile({
  695. source: 'jest',
  696. name: 'foo.jpg',
  697. type: 'image/jpeg',
  698. data: new File([sampleImage], { type: 'image/jpeg' })
  699. })
  700. core.addFile({
  701. source: 'jest',
  702. name: 'empty.dat',
  703. type: 'application/octet-stream',
  704. data: new File([Buffer.alloc(1000)], { type: 'application/octet-stream' })
  705. })
  706. expect(core.getFiles()).toHaveLength(2)
  707. expect(core.getFiles().map((file) => file.name).sort()).toEqual(['empty.dat', 'foo.jpg'])
  708. })
  709. })
  710. describe('meta data', () => {
  711. it('should set meta data by calling setMeta', () => {
  712. const core = new Core({
  713. meta: { foo2: 'bar2' }
  714. })
  715. core.setMeta({ foo: 'bar', bur: 'mur' })
  716. core.setMeta({ boo: 'moo', bur: 'fur' })
  717. expect(core.state.meta).toEqual({
  718. foo: 'bar',
  719. foo2: 'bar2',
  720. boo: 'moo',
  721. bur: 'fur'
  722. })
  723. })
  724. it('should update meta data for a file by calling updateMeta', () => {
  725. const core = new Core()
  726. core.addFile({
  727. source: 'jest',
  728. name: 'foo.jpg',
  729. type: 'image/jpeg',
  730. data: new File([sampleImage], { type: 'image/jpeg' })
  731. })
  732. const fileId = Object.keys(core.state.files)[0]
  733. core.setFileMeta(fileId, { foo: 'bar', bur: 'mur' })
  734. core.setFileMeta(fileId, { boo: 'moo', bur: 'fur' })
  735. expect(core.state.files[fileId].meta).toEqual({
  736. name: 'foo.jpg',
  737. type: 'image/jpeg',
  738. foo: 'bar',
  739. bur: 'fur',
  740. boo: 'moo'
  741. })
  742. })
  743. it('should merge meta data when add file', () => {
  744. const core = new Core({
  745. meta: { foo2: 'bar2' }
  746. })
  747. core.addFile({
  748. source: 'jest',
  749. name: 'foo.jpg',
  750. type: 'image/jpeg',
  751. meta: {
  752. resize: 5000
  753. },
  754. data: new File([sampleImage], { type: 'image/jpeg' })
  755. })
  756. const fileId = Object.keys(core.state.files)[0]
  757. expect(core.state.files[fileId].meta).toEqual({
  758. name: 'foo.jpg',
  759. type: 'image/jpeg',
  760. foo2: 'bar2',
  761. resize: 5000
  762. })
  763. })
  764. })
  765. describe('progress', () => {
  766. it('should calculate the progress of a file upload', () => {
  767. const core = new Core()
  768. core.addFile({
  769. source: 'jest',
  770. name: 'foo.jpg',
  771. type: 'image/jpeg',
  772. data: new File([sampleImage], { type: 'image/jpeg' })
  773. })
  774. const fileId = Object.keys(core.state.files)[0]
  775. const file = core.getFile(fileId)
  776. core._calculateProgress(file, {
  777. bytesUploaded: 12345,
  778. bytesTotal: 17175
  779. })
  780. expect(core.state.files[fileId].progress).toEqual({
  781. percentage: 71,
  782. bytesUploaded: 12345,
  783. bytesTotal: 17175,
  784. uploadComplete: false,
  785. uploadStarted: false
  786. })
  787. core._calculateProgress(file, {
  788. bytesUploaded: 17175,
  789. bytesTotal: 17175
  790. })
  791. expect(core.state.files[fileId].progress).toEqual({
  792. percentage: 100,
  793. bytesUploaded: 17175,
  794. bytesTotal: 17175,
  795. uploadComplete: false,
  796. uploadStarted: false
  797. })
  798. })
  799. it('should calculate the total progress of all file uploads', () => {
  800. const core = new Core()
  801. core.addFile({
  802. source: 'jest',
  803. name: 'foo.jpg',
  804. type: 'image/jpeg',
  805. data: new File([sampleImage], { type: 'image/jpeg' })
  806. })
  807. core.addFile({
  808. source: 'jest',
  809. name: 'foo2.jpg',
  810. type: 'image/jpeg',
  811. data: new File([sampleImage], { type: 'image/jpeg' })
  812. })
  813. const fileId1 = Object.keys(core.state.files)[0]
  814. const fileId2 = Object.keys(core.state.files)[1]
  815. const file1 = core.state.files[fileId1]
  816. const file2 = core.state.files[fileId2]
  817. core.state.files[fileId1].progress.uploadStarted = new Date()
  818. core.state.files[fileId2].progress.uploadStarted = new Date()
  819. core._calculateProgress(file1, {
  820. bytesUploaded: 12345,
  821. bytesTotal: 17175
  822. })
  823. core._calculateProgress(file2, {
  824. bytesUploaded: 10201,
  825. bytesTotal: 17175
  826. })
  827. core._calculateTotalProgress()
  828. expect(core.state.totalProgress).toEqual(65)
  829. })
  830. it('should reset the progress', () => {
  831. const resetProgressEvent = jest.fn()
  832. const core = new Core()
  833. core.run()
  834. core.on('reset-progress', resetProgressEvent)
  835. core.addFile({
  836. source: 'jest',
  837. name: 'foo.jpg',
  838. type: 'image/jpeg',
  839. data: new File([sampleImage], { type: 'image/jpeg' })
  840. })
  841. core.addFile({
  842. source: 'jest',
  843. name: 'foo2.jpg',
  844. type: 'image/jpeg',
  845. data: new File([sampleImage], { type: 'image/jpeg' })
  846. })
  847. const fileId1 = Object.keys(core.state.files)[0]
  848. const fileId2 = Object.keys(core.state.files)[1]
  849. const file1 = core.state.files[fileId1]
  850. const file2 = core.state.files[fileId2]
  851. core.state.files[fileId1].progress.uploadStarted = new Date()
  852. core.state.files[fileId2].progress.uploadStarted = new Date()
  853. core._calculateProgress(file1, {
  854. bytesUploaded: 12345,
  855. bytesTotal: 17175
  856. })
  857. core._calculateProgress(file2, {
  858. bytesUploaded: 10201,
  859. bytesTotal: 17175
  860. })
  861. core._calculateTotalProgress()
  862. expect(core.state.totalProgress).toEqual(65)
  863. core.resetProgress()
  864. expect(core.state.files[fileId1].progress).toEqual({
  865. percentage: 0,
  866. bytesUploaded: 0,
  867. bytesTotal: 17175,
  868. uploadComplete: false,
  869. uploadStarted: false
  870. })
  871. expect(core.state.files[fileId2].progress).toEqual({
  872. percentage: 0,
  873. bytesUploaded: 0,
  874. bytesTotal: 17175,
  875. uploadComplete: false,
  876. uploadStarted: false
  877. })
  878. expect(core.state.totalProgress).toEqual(0)
  879. expect(resetProgressEvent.mock.calls.length).toEqual(1)
  880. })
  881. })
  882. describe('checkRestrictions', () => {
  883. it('should enforce the maxNumberOfFiles rule', () => {
  884. const core = new Core({
  885. autoProceed: false,
  886. restrictions: {
  887. maxNumberOfFiles: 1
  888. }
  889. })
  890. // add 2 files
  891. core.addFile({
  892. source: 'jest',
  893. name: 'foo1.jpg',
  894. type: 'image/jpeg',
  895. data: new File([sampleImage], { type: 'image/jpeg' })
  896. })
  897. try {
  898. core.addFile({
  899. source: 'jest',
  900. name: 'foo2.jpg',
  901. type: 'image/jpeg',
  902. data: new File([sampleImage], { type: 'image/jpeg' })
  903. })
  904. throw new Error('should have thrown')
  905. } catch (err) {
  906. expect(err).toMatchObject(new Error('You can only upload 1 file'))
  907. expect(core.state.info.message).toEqual('You can only upload 1 file')
  908. }
  909. })
  910. xit('should enforce the minNumberOfFiles rule', () => {})
  911. it('should enforce the allowedFileTypes rule', () => {
  912. const core = new Core({
  913. autoProceed: false,
  914. restrictions: {
  915. allowedFileTypes: ['image/gif', 'image/png']
  916. }
  917. })
  918. try {
  919. core.addFile({
  920. source: 'jest',
  921. name: 'foo2.jpg',
  922. type: 'image/jpeg',
  923. data: new File([sampleImage], { type: 'image/jpeg' })
  924. })
  925. throw new Error('should have thrown')
  926. } catch (err) {
  927. expect(err).toMatchObject(new Error('You can only upload: image/gif, image/png'))
  928. expect(core.state.info.message).toEqual('You can only upload: image/gif, image/png')
  929. }
  930. })
  931. it('should enforce the allowedFileTypes rule with file extensions', () => {
  932. const core = new Core({
  933. autoProceed: false,
  934. restrictions: {
  935. allowedFileTypes: ['.gif', '.jpg', '.jpeg']
  936. }
  937. })
  938. try {
  939. core.addFile({
  940. source: 'jest',
  941. name: 'foo2.png',
  942. type: '',
  943. data: new File([sampleImage], { type: 'image/jpeg' })
  944. })
  945. throw new Error('should have thrown')
  946. } catch (err) {
  947. expect(err).toMatchObject(new Error('You can only upload: .gif, .jpg, .jpeg'))
  948. expect(core.state.info.message).toEqual('You can only upload: .gif, .jpg, .jpeg')
  949. }
  950. })
  951. it('should enforce the maxFileSize rule', () => {
  952. const core = new Core({
  953. autoProceed: false,
  954. restrictions: {
  955. maxFileSize: 1234
  956. }
  957. })
  958. try {
  959. core.addFile({
  960. source: 'jest',
  961. name: 'foo.jpg',
  962. type: 'image/jpeg',
  963. data: new File([sampleImage], { type: 'image/jpeg' })
  964. })
  965. throw new Error('should have thrown')
  966. } catch (err) {
  967. expect(err).toMatchObject(new Error('This file exceeds maximum allowed size of 1.2 KB'))
  968. expect(core.state.info.message).toEqual('This file exceeds maximum allowed size of 1.2 KB')
  969. }
  970. })
  971. })
  972. describe('actions', () => {
  973. it('should update the state when receiving the error event', () => {
  974. const core = new Core()
  975. core.run()
  976. core.emit('error', new Error('foooooo'))
  977. expect(core.state.error).toEqual('foooooo')
  978. })
  979. it('should update the state when receiving the upload-error event', () => {
  980. const core = new Core()
  981. core.run()
  982. core.state.files['fileId'] = {
  983. name: 'filename'
  984. }
  985. core.emit('upload-error', core.state.files['fileId'], new Error('this is the error'))
  986. expect(core.state.info).toEqual({'message': 'Failed to upload filename', 'details': 'this is the error', 'isHidden': false, 'type': 'error'})
  987. })
  988. it('should reset the error state when receiving the upload event', () => {
  989. const core = new Core()
  990. core.run()
  991. core.emit('error', { foo: 'bar' })
  992. core.emit('upload')
  993. expect(core.state.error).toEqual(null)
  994. })
  995. })
  996. describe('updateOnlineStatus', () => {
  997. const RealNavigatorOnline = global.window.navigator.onLine
  998. function mockNavigatorOnline (status) {
  999. Object.defineProperty(
  1000. global.window.navigator,
  1001. 'onLine',
  1002. {
  1003. value: status,
  1004. writable: true
  1005. }
  1006. )
  1007. }
  1008. afterEach(() => {
  1009. global.window.navigator.onLine = RealNavigatorOnline
  1010. })
  1011. it('should emit the correct event based on whether there is a network connection', () => {
  1012. const onlineEventMock = jest.fn()
  1013. const offlineEventMock = jest.fn()
  1014. const backOnlineEventMock = jest.fn()
  1015. const core = new Core()
  1016. core.on('is-offline', offlineEventMock)
  1017. core.on('is-online', onlineEventMock)
  1018. core.on('back-online', backOnlineEventMock)
  1019. mockNavigatorOnline(true)
  1020. core.updateOnlineStatus()
  1021. expect(onlineEventMock.mock.calls.length).toEqual(1)
  1022. expect(offlineEventMock.mock.calls.length).toEqual(0)
  1023. expect(backOnlineEventMock.mock.calls.length).toEqual(0)
  1024. mockNavigatorOnline(false)
  1025. core.updateOnlineStatus()
  1026. expect(onlineEventMock.mock.calls.length).toEqual(1)
  1027. expect(offlineEventMock.mock.calls.length).toEqual(1)
  1028. expect(backOnlineEventMock.mock.calls.length).toEqual(0)
  1029. mockNavigatorOnline(true)
  1030. core.updateOnlineStatus()
  1031. expect(onlineEventMock.mock.calls.length).toEqual(2)
  1032. expect(offlineEventMock.mock.calls.length).toEqual(1)
  1033. expect(backOnlineEventMock.mock.calls.length).toEqual(1)
  1034. })
  1035. })
  1036. describe('info', () => {
  1037. it('should set a string based message to be displayed infinitely', () => {
  1038. const infoVisibleEvent = jest.fn()
  1039. const core = new Core()
  1040. core.run()
  1041. core.on('info-visible', infoVisibleEvent)
  1042. core.info('This is the message', 'info', 0)
  1043. expect(core.state.info).toEqual({
  1044. isHidden: false,
  1045. type: 'info',
  1046. message: 'This is the message',
  1047. details: null
  1048. })
  1049. expect(infoVisibleEvent.mock.calls.length).toEqual(1)
  1050. expect(typeof core.infoTimeoutID).toEqual('undefined')
  1051. })
  1052. it('should set a object based message to be displayed infinitely', () => {
  1053. const infoVisibleEvent = jest.fn()
  1054. const core = new Core()
  1055. core.run()
  1056. core.on('info-visible', infoVisibleEvent)
  1057. core.info({
  1058. message: 'This is the message',
  1059. details: {
  1060. foo: 'bar'
  1061. }
  1062. }, 'warning', 0)
  1063. expect(core.state.info).toEqual({
  1064. isHidden: false,
  1065. type: 'warning',
  1066. message: 'This is the message',
  1067. details: {
  1068. foo: 'bar'
  1069. }
  1070. })
  1071. expect(infoVisibleEvent.mock.calls.length).toEqual(1)
  1072. expect(typeof core.infoTimeoutID).toEqual('undefined')
  1073. })
  1074. it('should set an info message to be displayed for a period of time before hiding', (done) => {
  1075. const infoVisibleEvent = jest.fn()
  1076. const infoHiddenEvent = jest.fn()
  1077. const core = new Core()
  1078. core.run()
  1079. core.on('info-visible', infoVisibleEvent)
  1080. core.on('info-hidden', infoHiddenEvent)
  1081. core.info('This is the message', 'info', 100)
  1082. expect(typeof core.infoTimeoutID).toEqual('number')
  1083. expect(infoHiddenEvent.mock.calls.length).toEqual(0)
  1084. setTimeout(() => {
  1085. expect(infoHiddenEvent.mock.calls.length).toEqual(1)
  1086. expect(core.state.info).toEqual({
  1087. isHidden: true,
  1088. type: 'info',
  1089. message: 'This is the message',
  1090. details: null
  1091. })
  1092. done()
  1093. }, 110)
  1094. })
  1095. it('should hide an info message', () => {
  1096. const infoVisibleEvent = jest.fn()
  1097. const infoHiddenEvent = jest.fn()
  1098. const core = new Core()
  1099. core.run()
  1100. core.on('info-visible', infoVisibleEvent)
  1101. core.on('info-hidden', infoHiddenEvent)
  1102. core.info('This is the message', 'info', 0)
  1103. expect(typeof core.infoTimeoutID).toEqual('undefined')
  1104. expect(infoHiddenEvent.mock.calls.length).toEqual(0)
  1105. core.hideInfo()
  1106. expect(infoHiddenEvent.mock.calls.length).toEqual(1)
  1107. expect(core.state.info).toEqual({
  1108. isHidden: true,
  1109. type: 'info',
  1110. message: 'This is the message',
  1111. details: null
  1112. })
  1113. })
  1114. })
  1115. describe('createUpload', () => {
  1116. it('should assign the specified files to a new upload', () => {
  1117. const core = new Core()
  1118. core.run()
  1119. core.addFile({
  1120. source: 'jest',
  1121. name: 'foo.jpg',
  1122. type: 'image/jpeg',
  1123. data: new File([sampleImage], { type: 'image/jpeg' })
  1124. })
  1125. core._createUpload(Object.keys(core.state.files))
  1126. const uploadId = Object.keys(core.state.currentUploads)[0]
  1127. const currentUploadsState = {}
  1128. currentUploadsState[uploadId] = {
  1129. fileIDs: Object.keys(core.state.files),
  1130. step: 0,
  1131. result: {}
  1132. }
  1133. expect(core.state.currentUploads).toEqual(currentUploadsState)
  1134. })
  1135. })
  1136. describe('i18n', () => {
  1137. it('merges in custom locale strings', () => {
  1138. const core = new Core({
  1139. locale: {
  1140. strings: {
  1141. test: 'beep boop'
  1142. }
  1143. }
  1144. })
  1145. expect(core.i18n('exceedsSize')).toBe('This file exceeds maximum allowed size of')
  1146. expect(core.i18n('test')).toBe('beep boop')
  1147. })
  1148. })
  1149. describe('default restrictions', () => {
  1150. it('should be merged with supplied restrictions', () => {
  1151. const core = new Core({
  1152. restrictions: {
  1153. maxNumberOfFiles: 3
  1154. }
  1155. })
  1156. expect(core.opts.restrictions.maxNumberOfFiles).toBe(3)
  1157. expect(core.opts.restrictions.minNumberOfFiles).toBe(null)
  1158. })
  1159. })
  1160. })