Ver código fonte

Working test

Add tests for thumbnail:* events.

Show build output

Try adding a non image file?

fixes

Add -b flags to local acceptance tests

fix hanging thumbnail test

Add a corrupt image

revoke

Require passing integration tests

extended debugging?

Try more browser versions

it helps to pick a browser that's available

sort stuff in .travis.yml and add whitespace for easier navigation

[REVERT BEFORE MERGE] only run acceptance tests on this pr

make sure lerna publishes stuff to local

do git tag version during integration test

update lerna

fix install / bootstrap execution order

?

--no-ci

dont do npm ls because it errors with lerna

set a git branch name

blehbleh

ughhhhhhhhhhhh

onerror?

no onerror i guess

onerror 2

Revert "onerror 2"

This reverts commit 64d88551fa1cd337b27e25d16a604dd781a15b43.

onerror 3

more debug data

disable browsers that work

fix syntax for older browsers

thumbnails suite

check different edge versions

abc

use addeventlistener for error, which is different from onerror

no clue

skip thumb test in edge or whatever

Re enable other browsers.

re enable tasks

fix edge skip

this one was intentionally disabled

Remove debug leftovers

edge → supportschoosefile

:ready → :all-generated
Renée Kooi 6 anos atrás
pai
commit
a67a558eec

+ 24 - 13
.travis.yml

@@ -1,6 +1,28 @@
 language: node_js
 node_js:
 - 8.11.4
+
+before_install:
+  - nvm install-latest-npm
+
+install:
+  - npm install
+  - npm run bootstrap -- --no-ci
+
+script:
+  - npm run build
+  - npm run test
+  - 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then npm run test:acceptance; fi'
+  - npm run uploadcdn
+
+cache:
+  apt: true
+  directories:
+  - ~/.npm
+
+services:
+  - docker
+
 addons:
   apt:
     sources:
@@ -12,19 +34,7 @@ addons:
       secure: nAMJ/d1fm9urTYsQ+1uqj6Jjf71J8rzwYBSZbTDAeUEZzAdvGc0a9H3PYWM4pnUDPo5s1c9MMetXi2XNdUbXgMKHbEnePZ2mJamqFtXMmpG8pgFmMqj+btMd7Yybt070tRsn4Vy0uBSi2H/en7F3j+grABJV+SAXqWkSB7CU1fZaN/u0DpoGBNj1ZNwkYCIhpLueYJTPRWBOodMAarXuFv5+7KFOKuZM3tF/JjsMNSSaDgTkz13BZnbX6vNPxGJJNJcyJGSaXrVW8hh1Zmvnk/XdiLy+vt7Wz1wz3A9ebiFDuydo5AAkxrLFsCJ5nGEqLg3bkr6NaTRpbM84ZT3i1FQMTdKP6OHHqwAeBscB6BkyhZhzvsFtl2YRBNK9mA3OtOYvBmTkFkNqvrPQlfu7cFtyG5+AUfSCiTTgS/vWIwoqSVAXaOEqN8Fp54ecUdkzCTttl3gXteZzNLRYvyQcFpoJb6E+dS8qAW0OFOteiwKVuPCh3nGUzBP13bRo1i9UAX7ZCTlpjinkxE8ryzbToo6ZcVQMBAkKhaw/x8GzOtfm5rgYMeQzGEoBJNfr7qqfs7JMxAIEMYjrTL9PXVOp/R8F3FdsqbV70jSyfsxMSMkwSWFRmVslG8+Djy8P3LnckGy1FEbMHnH8GZHZg+hbBzN8Be1/1fV0oRRAr939WRc=
     access_key:
       secure: OY3oWwiJghfty9wSPVvlhirvFGxPHDdIRuVkzAv6j7C/hj2BWYAP/UHrwdQ9XiYisHi/B5mGeyRVlrAf0MNGrG84rTDUbTWZbmktfuxl7A+Y6c0czk+s4SdhOiANG5b3tFl5wKq8h7uhrWH5/jWoKQ2Fz1VDCqxTvvZQbo41jSBhi7TBia626hxEePzdaiuw6HhGFZtfaoVs/FX30ylz8WDNrBjwCynjxsT52BaQrVvgEhuyzlOpI69YkZBPOq4fc3KiZ2YR43gLTx8K+sYCE9yJxdg1xT/UAawEhmedU83nyBZVo4rr7+03AixIxtI28MUCfBMlcsGwBxcKEKY/IWcp9UkPCq6+zALQoncV478tP21eYvlmxSFhYCrv+WEQlN+BcNjr4OJlmmFDbCVaF7r9qLeQPImU0+9iJU3OjrW7lpfLxORpGDEr2Nx6awKkIJCxNyK9weefeNo6Fz3V1kkyZ/7yWFeniJnRUCbahrB2XgzxIE+W307s1Qs4fm6JK7hVLTtG4fBzjChmAyGIzu744ws9WqmjvkC9D7OfnuXqanv/VcBFqPiudInerv7NL8FketUC+fxe/7XJfcxdaDGBjk8Kq7zXDohGRGymUXEoMDNJsKkMMlaKzdf7tgqdhsRJoH9NCVqrDXuG5al0UtrDP5RS7qfoxUunJmNFhlg=
-cache:
-  apt: true
-  directories:
-  - ~/.npm
-before_install:
-- nvm install-latest-npm
-services:
-  - docker
-script:
-- npm run build
-- npm run test
-- '{ if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then npm run test:acceptance; fi } || true'
-- npm run uploadcdn
+
 env:
   global:
   - CXX=g++-4.8
@@ -50,6 +60,7 @@ env:
   - secure: V6qVB6vHAuSt1YPgzYpknLglnxbYJM5bsjAjQi78zZQSqrrWCMBgp5Q+Sq7Ad8nycgnPl8W8VpMnUIBqZcK4ciCW+vJBtZ7fBJLIEGMEk8+c8az8JePFcBRiaEicZPvZFuIHPteaAy6jK1GRVpjSjJi3NYEMpcDUs2aTM2bIpX0ks2WjUqTXiK8EvIaK2LTOgSBvyGUOiHwwTkom+PyD50t7jP98g+9Pn7GPwtWaUo1K8UpLArwR2fZ/nm0F2DU6ohz7o2t4LATFXuZhbOCj7O9vh+CmBf+/0C7giT3K23LbckDSK8CNTCDC2bsv8zDo5ZZtgdOvdatOD+VlsbAMkmUzzDgQ3410arZ8g1LVQw8eJwu1H+etcAinYf4r+Tr7s3dTBliMX70/IpjZrkHR9uXEi4LelBNYn6TKdLTW3lfVWkiFs/HPu4SoAmu1+sxSvwxVDDLeexQmb2fwp0r2tDKZ3eRfpQXLPACBaORnGW6ki9qXo8PBdaElxPbpzEWzAfT3F6zRjebpow+cdFBQrAga7/LrOrXerZxH2ekRWKgXzuhw7ERwVFrVN4/NlmDJXo+rm4na7n7CxY/QpZcdpthksDO9KYCqmn5dqMqFGislRvRe7PqIti8gC4pSdeUhDKsIh9OEgVNjCczvnA8rMRQ5aNmFx6wspmrBvO7qRLM=
   - secure: rVsiFPA9TvH/d2wkP8+1i5QGUuYw0q2BUAUdxyxO9hQcG/nRiHXtQfLbTRZHKwvqf0vyV6J1pJqLlVN4JO/bPhAvk55KAQJWl8UqyaeZiEN9KMcTr3fJuNFlBj4ciYiZ3BWwakblsiaGCjKMRdjki58a9f8XL2rcM8R6ccndjkTMYnBKaopSsAgouI8D5n74wQz6lODUayGOlbwlGLfGtPYplUfSLK4wghC+jgWsNjJySqJhfgYS0JCZc10Qw+FI2BoU4SZ4+P0L0YPIC9zV/cUW4qDT11N/oUgwfjZbPWfM9A/xn/d7sgDH+SpeoGdYler/lvxojj6L2mD/wAh8/lg1E6nL2aKgExE3z+fd2XV8L0osB/sulB7/Exrezg/mVejAx2IkWVHi4VEJmcTV+3WeEvTFOM3fID8dOVf+GUv+hcHdZMxS/hfj3keKCYG1P5ameMJO8FehRqhetNYnr6FTyrK+S+xitaZ/nXrTbHItPS0pZ4XA6CFs5uzMBPeDnk5/D7paPyrE/k2HAc1WmA6g37OyzYIMEV1laBz8IG0qMqg6JJmr09P/Iwrim5Ex2fAssT9Yr1WuOE2gWoF0A3XuVXQHVf4tJT6x/WDKChmbX588a47AvBgkFyoXLRilUYlET2tWnEpVxUovsbJXqvHwTXWMLO9riRjjeInbpvA=
   - secure: eq5hOqRBN2R7YO2dYdn5OjZc/zLLYYDZcCpCu/K/8fU4HYWTqxrBntjv8T0sZ5qdlAs3IniEfXxemz9V3zwvxR+vh2bGuYr2Xo7RRa2TIDuw+KUPZogrVxhXHPKfyJqstxy+dee2+pWhGkAP7caiu49eyqlboBMkzgpO/xcdehEWYRY5jPgvnlH+QRZ3GADKs1JEeltHDiZ6rYA7nj5Tyx9UoLgv4Av9UXdC29we7dLFTkVfCHE//7wfZW9+/IbxthA4qMjQOFaBrmagN5yweDg87iPTqNMth7FjzOavdUgQ2TW6d10VDEhLIZh36gLGreViKMDCEWKMQ8f/mv05Ao8+DXyXgxIn56II8lhUp5ukQ27ZWixfEKFx2lynJWRZE0pWwf8ec1+bXLQiBOE181Cl4nUT/TbFWzvV6yA+cMiQKe4y59bC4nhkK3IYgpR5kfCFOT+1tFknQ4hNJNacWwUmaDFMxYJaXEtRUn5jJa7eGRYSCrmnymbnzZ6w3Q3nQGNvNxpbBIXX/pzs0VDVTxSlgN4gA+n2jeCyjgVVrMQ/HoAS4uwm1cx89AttW+TANppg1PqWhhrJYuVEZSnvV8PM6R7rbvlS5tluezQj41YklgjsSopH7//+dbGGDNbrTTLic4J9PJR3yEtlAMdOCi53iT0R0Dt5X2WBv2QT5Eg=
+
 notifications:
   slack:
     secure: L3iQQE8sZ0ik1Z26gPoNMiIam9EOEwYhraHCY60Jk/wmfH6SW/727yKXpgcb/yayx37rUZplvoO7H8e05ISxTJKSepEeqbBUIBQs48S8hr+FHk0VPtpP4HGxqaITRLm+mI1coPRvfISxzrB8d240oup6muhC9Ws4/LXi6v8miyIOs2zoYmGxd56TrUeON3UYlKt6dMava0V4bugARzrafN/tfyI9ccqbHzQLBspQvBI61DzZ5I2vnWpkjfWgIHz9Fl4VzXHqMXwjuTUEu8ibA12b3dHZiJEAoqeb9Oj9QcLPbstPLhlNTZZaOrfiFtwLctI2rFh37slDpAfk5idv3ycxcoG5rbCxgyg5i6dpQqrqHxnyglgHg2/nZ+YA5okeS7nJJNtU/4S6AFRWOUUWMVVY0VBEV+8w+uurl0PDy80RUY3uyK64qAgQ8U0M81/Ys1oyWyn78TqHcbby7V2Ws5I9Yakrq8D+mdfsWYCio8F6LXHSwJ0mt2FanJtdDvpPk9sAwsXZN0n8xhELt5TiRp3bzVIQ0IPUgF54dTG9/zWRvC1P4TFaFU/2fg73ZEUC5aWJoFMnLSZjbZvp5gwpCVd0MjSBk80nF9dHYcavIgJ0wMGI3BMb8Nn6+T11Gw/ycr7OGU4NMkj7i8vSFgKF74piWZyiNW8orkMN6XZgM+o=

+ 12 - 5
bin/endtoend-build-ci

@@ -39,15 +39,22 @@ npm run build
 # https://github.com/facebook/create-react-app/pull/4626
 (cd && npm-auth-to-token -u user -p password -e user@example.com -r "$VERDACCIO_REGISTRY")
 
+git checkout -b endtoend-test-build
+# HACK this thing changes all the time for some reason on CI
+# so I'll just ignore it…
+git checkout -- package-lock.json
+
 # Simulate a publish of everything, to the local registry,
 # without changing things in git
-# Use --cd-version to skip version prompts
-lerna publish --yes \
+lerna version prerelease --yes \
+  --exact \
   --force-publish \
-  --registry="$VERDACCIO_REGISTRY" \
   --npm-client=npm \
-  --no-git-tag-version --no-push \
-  --canary
+  --no-push
+lerna publish from-git --yes \
+  --registry="$VERDACCIO_REGISTRY" \
+  --no-verify-access \
+  --npm-client=npm
 
 # revert version changes
 git checkout -- packages/*/package.json packages/@uppy/*/package.json

+ 1 - 1
bin/endtoend-build-tests

@@ -26,5 +26,5 @@ done
 # Speeecial tests that need custom builds.
 pushd "${__root}/test/endtoend/create-react-app"
   npm install
-  npm run build > /dev/null
+  npm run build
 popd

Diferenças do arquivo suprimidas por serem muito extensas
+ 373 - 242
package-lock.json


+ 2 - 2
package.json

@@ -49,7 +49,7 @@
     "isomorphic-fetch": "2.2.1",
     "jest": "^23.5.0",
     "json3": "^3.3.2",
-    "lerna": "^3.0.6",
+    "lerna": "^3.4.0",
     "lint-staged": "^6.1.1",
     "minify-stream": "^1.2.0",
     "mkdirp": "0.5.1",
@@ -126,7 +126,7 @@
     "web:update:frontpage:code:sample": "cd website && ./node_modules/.bin/hexo generate && cp -f public/frontpage-code-sample.html ./themes/uppy/layout/partials/frontpage-code-sample.html",
     "web": "npm-run-all web:clean web:build",
     "uploadcdn": "bin/upload-to-cdn.sh",
-    "prepare": "lerna bootstrap --hoist",
+    "bootstrap": "lerna bootstrap --hoist",
     "contributors": "githubcontrib --owner transloadit --repo uppy --cols 6 $([ \"${GITHUB_TOKEN:-}\" == \"\" ] && echo \"\" || echo \"--authToken ${GITHUB_TOKEN}\") --showlogin true --sortOrder desc",
     "contributors:save": "replace-x -m '<!--contributors-->[\\s\\S]+<!--/contributors-->' \"<!--contributors-->\n## Contributors\n\n$(npm run --silent contributors)\n<!--/contributors-->\" README.md"
   },

+ 20 - 17
packages/@uppy/thumbnail-generator/src/index.js

@@ -20,7 +20,10 @@ module.exports = class ThumbnailGenerator extends Plugin {
       thumbnailWidth: 200
     }
 
-    this.opts = Object.assign({}, defaultOptions, opts)
+    this.opts = {
+      ...defaultOptions,
+      ...opts
+    }
 
     this.addToQueue = this.addToQueue.bind(this)
     this.onRestored = this.onRestored.bind(this)
@@ -35,21 +38,20 @@ module.exports = class ThumbnailGenerator extends Plugin {
    */
   createThumbnail (file, targetWidth) {
     const originalUrl = URL.createObjectURL(file.data)
-    const revoke = () => URL.revokeObjectURL(originalUrl)
+
     const onload = new Promise((resolve, reject) => {
       const image = new Image()
       image.src = originalUrl
-      image.onload = () => {
+      image.addEventListener('load', () => {
+        URL.revokeObjectURL(originalUrl)
         resolve(image)
-      }
-      image.onerror = () => {
-        // The onerror event is totally useless unfortunately, as far as I know
-        reject(new Error('Could not create thumbnail'))
-      }
+      })
+      image.addEventListener('error', (event) => {
+        URL.revokeObjectURL(originalUrl)
+        reject(event.error || new Error('Could not create thumbnail'))
+      })
     })
 
-    onload.then(revoke, revoke)
-
     return onload
       .then(image => {
         const targetHeight = this.getProportionalHeight(image, targetWidth)
@@ -155,9 +157,7 @@ module.exports = class ThumbnailGenerator extends Plugin {
    * Set the preview URL for a file.
    */
   setPreviewURL (fileID, preview) {
-    this.uppy.setFileState(fileID, {
-      preview: preview
-    })
+    this.uppy.setFileState(fileID, { preview })
   }
 
   addToQueue (item) {
@@ -176,7 +176,8 @@ module.exports = class ThumbnailGenerator extends Plugin {
         .then(() => this.processQueue())
     } else {
       this.queueProcessing = false
-      this.uppy.emit('thumbnail:ready')
+      this.uppy.log('[ThumbnailGenerator] Emptied thumbnail queue')
+      this.uppy.emit('thumbnail:all-generated')
     }
   }
 
@@ -185,11 +186,13 @@ module.exports = class ThumbnailGenerator extends Plugin {
       return this.createThumbnail(file, this.opts.thumbnailWidth)
         .then(preview => {
           this.setPreviewURL(file.id, preview)
-          this.uppy.emit('thumbnail:generated', file, preview)
+          this.uppy.log(`[ThumbnailGenerator] Generated thumbnail for ${file.id}`)
+          this.uppy.emit('thumbnail:generated', this.uppy.getFile(file.id), preview)
         })
         .catch(err => {
-          console.warn(err.stack || err.message)
-          this.uppy.emit('thumbnail:error', file, err)
+          this.uppy.log(`[ThumbnailGenerator] Failed thumbnail for ${file.id}`)
+          this.uppy.log(err, 'warning')
+          this.uppy.emit('thumbnail:error', this.uppy.getFile(file.id), err)
         })
     }
     return Promise.resolve()

+ 67 - 20
packages/@uppy/thumbnail-generator/src/index.test.js

@@ -4,25 +4,34 @@ const emitter = require('namespace-emitter')
 
 const delay = duration => new Promise(resolve => setTimeout(resolve, duration))
 
+function MockCore () {
+  const core = emitter()
+  const files = {}
+  core.mockFile = (id, f) => { files[id] = f }
+  core.getFile = (id) => files[id]
+  core.log = () => null
+  return core
+}
+
 describe('uploader/ThumbnailGeneratorPlugin', () => {
   it('should initialise successfully', () => {
-    const plugin = new ThumbnailGeneratorPlugin(null, {})
+    const plugin = new ThumbnailGeneratorPlugin(new MockCore(), {})
     expect(plugin instanceof Plugin).toEqual(true)
   })
 
   it('should accept the thumbnailWidth option and override the default', () => {
-    const plugin1 = new ThumbnailGeneratorPlugin(null) // eslint-disable-line no-new
+    const plugin1 = new ThumbnailGeneratorPlugin(new MockCore()) // eslint-disable-line no-new
     expect(plugin1.opts.thumbnailWidth).toEqual(200)
 
-    const plugin2 = new ThumbnailGeneratorPlugin(null, { thumbnailWidth: 100 }) // eslint-disable-line no-new
+    const plugin2 = new ThumbnailGeneratorPlugin(new MockCore(), { thumbnailWidth: 100 }) // eslint-disable-line no-new
     expect(plugin2.opts.thumbnailWidth).toEqual(100)
   })
 
   describe('install', () => {
     it('should subscribe to uppy file-added event', () => {
-      const core = {
+      const core = Object.assign(new MockCore(), {
         on: jest.fn()
-      }
+      })
 
       const plugin = new ThumbnailGeneratorPlugin(core)
       plugin.addToQueue = jest.fn()
@@ -35,10 +44,10 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
 
   describe('uninstall', () => {
     it('should unsubscribe from uppy file-added event', () => {
-      const core = {
+      const core = Object.assign(new MockCore(), {
         on: jest.fn(),
         off: jest.fn()
-      }
+      })
 
       const plugin = new ThumbnailGeneratorPlugin(core)
       plugin.addToQueue = jest.fn()
@@ -55,7 +64,7 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
 
   describe('queue', () => {
     it('should add a new file to the queue and start processing the queue when queueProcessing is false', () => {
-      const core = {}
+      const core = new MockCore()
       const plugin = new ThumbnailGeneratorPlugin(core)
       plugin.processQueue = jest.fn()
 
@@ -73,7 +82,7 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
     })
 
     it('should process items in the queue one by one', () => {
-      const core = {}
+      const core = new MockCore()
       const plugin = new ThumbnailGeneratorPlugin(core)
 
       plugin.requestThumbnail = jest.fn(() => delay(100))
@@ -106,9 +115,47 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
     })
   })
 
+  describe('events', () => {
+    const core = new MockCore()
+    const plugin = new ThumbnailGeneratorPlugin(core)
+    plugin.createThumbnail = jest.fn((file) => delay(100).then(() => `blob:${file.id}.png`))
+    plugin.setPreviewURL = jest.fn()
+
+    function add (file) {
+      core.mockFile(file.id, file)
+      plugin.addToQueue(file)
+    }
+
+    it('should emit thumbnail:generated when a thumbnail was generated', () => new Promise((resolve, reject) => {
+      const expected = ['bar', 'bar2', 'bar3']
+      core.on('thumbnail:generated', (file, preview) => {
+        try {
+          expect(file.id).toBe(expected.shift())
+          expect(preview).toBe(`blob:${file.id}.png`)
+        } catch (err) {
+          return reject(err)
+        }
+        if (expected.length === 0) resolve()
+      })
+      add({ id: 'bar', type: 'image/png' })
+      add({ id: 'bar2', type: 'image/png' })
+      add({ id: 'bar3', type: 'image/png' })
+    }))
+
+    it('should emit thumbnail:all-generated when all thumbnails were generated', () => {
+      return new Promise((resolve) => {
+        core.on('thumbnail:all-generated', resolve)
+        add({ id: 'bar4', type: 'image/png' })
+        add({ id: 'bar5', type: 'image/png' })
+      }).then(() => {
+        expect(plugin.queue).toHaveLength(0)
+      })
+    })
+  })
+
   describe('requestThumbnail', () => {
     it('should call createThumbnail if it is a supported filetype', () => {
-      const core = {}
+      const core = new MockCore()
       const plugin = new ThumbnailGeneratorPlugin(core)
 
       plugin.createThumbnail = jest
@@ -127,7 +174,7 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
     })
 
     it('should not call createThumbnail if it is not a supported filetype', () => {
-      const core = {}
+      const core = new MockCore()
       const plugin = new ThumbnailGeneratorPlugin(core)
 
       plugin.createThumbnail = jest
@@ -142,7 +189,7 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
     })
 
     it('should not call createThumbnail if the file is remote', () => {
-      const core = {}
+      const core = new MockCore()
       const plugin = new ThumbnailGeneratorPlugin(core)
 
       plugin.createThumbnail = jest
@@ -157,7 +204,7 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
     })
 
     it('should call setPreviewURL with the thumbnail image', () => {
-      const core = {}
+      const core = new MockCore()
       const plugin = new ThumbnailGeneratorPlugin(core)
 
       plugin.createThumbnail = jest
@@ -199,7 +246,7 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
 
   describe('getProportionalHeight', () => {
     it('should calculate the resized height based on the specified width of the image whilst keeping aspect ratio', () => {
-      const core = {}
+      const core = new MockCore()
       const plugin = new ThumbnailGeneratorPlugin(core)
       expect(
         plugin.getProportionalHeight({ width: 200, height: 100 }, 50)
@@ -215,7 +262,7 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
 
   describe('canvasToBlob', () => {
     it('should use canvas.toBlob if available', () => {
-      const core = {}
+      const core = new MockCore()
       const plugin = new ThumbnailGeneratorPlugin(core)
       const canvas = {
         toBlob: jest.fn()
@@ -242,7 +289,7 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
     })
 
     xit('should scale down the image by the specified number of steps', () => {
-      const core = {}
+      const core = new MockCore()
       const plugin = new ThumbnailGeneratorPlugin(core)
       const image = {
         width: 1000,
@@ -299,7 +346,7 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
 
   describe('resizeImage', () => {
     it('should return a canvas with the resized image on it', () => {
-      const core = {}
+      const core = new MockCore()
       const plugin = new ThumbnailGeneratorPlugin(core)
       const image = {
         width: 1000,
@@ -324,7 +371,7 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
     })
 
     it('should upsize if original image is smaller than target size', () => {
-      const core = {}
+      const core = new MockCore()
       const plugin = new ThumbnailGeneratorPlugin(core)
       const image = {
         width: 100,
@@ -356,7 +403,7 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
         b: { preview: 'blob:def' },
         c: { preview: 'blob:xyz', isRestored: true }
       }
-      const core = Object.assign(emitter(), {
+      const core = Object.assign(new MockCore(), {
         getState () {
           return { files }
         },
@@ -380,7 +427,7 @@ describe('uploader/ThumbnailGeneratorPlugin', () => {
       const files = {
         a: { preview: 'http://abc', isRestored: true }
       }
-      const core = Object.assign(emitter(), {
+      const core = Object.assign(new MockCore(), {
         getState () {
           return { files }
         },

+ 19 - 2
test/endtoend/thumbnails/main.js

@@ -1,3 +1,4 @@
+/* eslint-disable */
 require('es6-promise/auto')
 require('whatwg-fetch')
 const Uppy = require('@uppy/core')
@@ -13,10 +14,26 @@ const uppyThumbnails = Uppy({
 uppyThumbnails.use(ThumbnailGenerator, {})
 uppyThumbnails.use(FileInput, { target: '#uppyThumbnails', pretty: false })
 
-uppyThumbnails.on('thumbnail:generated', (file) => {
+uppyThumbnails.on('file-added', (file) => {
+  const el = document.createElement('p')
+  el.className = 'file-name'
+  el.textContent = file.name
+  document.body.appendChild(el)
+})
+
+// Dump errors to the screen so saucelabs shows them in screenshots.
+uppyThumbnails.on('thumbnail:error', (file, err) => {
+  const el = document.createElement('pre')
+  el.style = 'font: 14pt monospace; background: red; color: white'
+  el.textContent = `Error: ${err.stack}`
+  document.body.appendChild(el)
+})
+
+uppyThumbnails.on('thumbnail:generated', (file, preview) => {
   const img = new Image()
   img.src = file.preview
+  img.className = 'file-preview'
+  img.style.display = 'block'
 
   document.body.appendChild(img)
 })
-window.ready = new Promise((resolve) => uppyThumbnails.on('thumbnail:ready', resolve))

+ 55 - 5
test/endtoend/thumbnails/test.js

@@ -1,4 +1,4 @@
-/* global browser, expect, $ */
+/* global browser, expect, $, $$ */
 const path = require('path')
 const fs = require('fs')
 const { selectFakeFile, supportsChooseFile } = require('../utils')
@@ -8,21 +8,39 @@ const testURL = 'http://localhost:4567/thumbnails'
 const images = [
   path.join(__dirname, '../../resources/image.jpg'),
   path.join(__dirname, '../../resources/baboon.png'),
-  path.join(__dirname, '../../resources/kodim23.png')
+  path.join(__dirname, '../../resources/kodim23.png'),
+  path.join(__dirname, '../../resources/invalid.png')
+]
+const notImages = [
+  { type: 'text/javascript', file: __filename }
 ]
 
-describe.only('ThumbnailGenerator', () => {
+describe('ThumbnailGenerator', () => {
   beforeEach(() => {
     browser.url(testURL)
   })
 
-  it('should generate thumbnails for images', () => {
+  it('should generate thumbnails for images', function () {
+    // FIXME why isn't the selectFakeFile alternative below working?
+    if (!supportsChooseFile()) {
+      return this.skip()
+    }
+
     $('#uppyThumbnails .uppy-FileInput-input').waitForExist()
 
+    browser.execute(/* must be valid ES5 for IE */ function () {
+      window.thumbnailsReady = new Promise(function (resolve) {
+        window.uppyThumbnails.on('thumbnail:all-generated', resolve)
+      })
+    })
+
     if (supportsChooseFile()) {
       for (const img of images) {
         browser.chooseFile('#uppyThumbnails .uppy-FileInput-input', img)
       }
+      for (const { file } of notImages) {
+        browser.chooseFile('#uppyThumbnails .uppy-FileInput-input', file)
+      }
     } else {
       for (const img of images) {
         browser.execute(
@@ -33,8 +51,40 @@ describe.only('ThumbnailGenerator', () => {
           fs.readFileSync(img, 'base64') // b64
         )
       }
+      for (const { type, file } of notImages) {
+        browser.execute(
+          selectFakeFile,
+          'uppyThumbnails',
+          path.basename(file), // name
+          type, // type
+          fs.readFileSync(file, 'base64') // b64
+        )
+      }
+    }
+
+    browser.executeAsync(/* must be valid ES5 for IE */ function (done) {
+      window.thumbnailsReady.then(done)
+    })
+
+    // const names = $$('p.file-name')
+    const previews = $$('img.file-preview')
+
+    // Names should all be listed before previews--indicates that previews were generated asynchronously.
+    /* Nevermind this, chooseFile() doesn't accept multiple files so they are added one by one and the thumbnails
+     * have finished generating by the time we add the next.
+    const nys = names.map((el) => el.getLocation('y'))
+    const pys = previews.map((el) => el.getLocation('y'))
+    for (const ny of nys) {
+      for (const py of pys) {
+        expect(ny).to.be.below(py, 'names should be listed before previews')
+      }
     }
+    */
 
-    browser.pause(20 * 1000)
+    expect(previews).to.have.lengthOf(3) // ex. the invalid image
+    for (const p of previews) {
+      expect(p.getAttribute('src')).to.match(/^blob:/)
+      expect(p.getElementSize('width')).to.equal(200)
+    }
   })
 })

+ 3 - 1
test/endtoend/utils.js

@@ -2,11 +2,13 @@
 const path = require('path')
 const { spawn } = require('child_process')
 
+// This function must be valid ES5, because it is run in the browser
+// and IE10/IE11 do not support new syntax features
 function selectFakeFile (uppyID, name, type, b64) {
   if (!b64) b64 = 'PHN2ZyB2aWV3Qm94PSIwIDAgMTIwIDEyMCI+CiAgPGNpcmNsZSBjeD0iNjAiIGN5PSI2MCIgcj0iNTAiLz4KPC9zdmc+Cg=='
 
   var blob = new Blob(
-    [`data:image/svg+xml;base64,${b64}`],
+    ['data:image/svg+xml;base64,' + b64],
     { type: type || 'image/svg+xml' }
   )
   window[uppyID].addFile({

+ 17 - 5
test/endtoend/wdio.local.conf.js

@@ -1,12 +1,24 @@
 const base = require('./wdio.base.conf')
 const { CompanionService } = require('./utils')
 
+// Use "npm run test:acceptance:local -- -b chrome" to test in chrome
+// "npm run test:acceptance:local -- -b firefox -b chrome" to test in FF and chrome
+let prevIsDashB = false
+const capabilities = []
+process.argv.forEach((arg) => {
+  if (prevIsDashB) {
+    capabilities.push({ browserName: arg })
+  }
+  prevIsDashB = arg === '-b'
+})
+
+// default to testing in firefox
+if (capabilities.length === 0) {
+  capabilities.push({ browserName: 'firefox' })
+}
+
 exports.config = Object.assign(base.config, {
-  capabilities: [
-    { browserName: 'firefox' }
-    // { browserName: 'MicrosoftEdge', version: '14.14393', platform: 'Windows 10' },
-    // { browserName: 'safari', version: '11.0', platform: 'macOS 10.12' }
-  ],
+  capabilities,
 
   // If you only want to run your tests until a specific amount of tests have failed use
   // bail (default is 0 - don't bail, run all tests).

+ 10 - 4
test/endtoend/wdio.remote.conf.js

@@ -2,18 +2,24 @@ const base = require('./wdio.base.conf')
 const { CompanionService } = require('./utils')
 
 function createCapability (capability) {
-  return Object.assign({
+  return {
     'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
-    build: process.env.TRAVIS_BUILD_NUMBER
-  }, capability)
+    build: process.env.TRAVIS_BUILD_NUMBER,
+    extendedDebugging: true,
+    ...capability
+  }
 }
 
 exports.config = Object.assign(base.config, {
   capabilities: [
     { browserName: 'firefox', version: '38.0', platform: 'Linux' },
+    { browserName: 'firefox', version: '61.0', platform: 'Windows 10' },
     { browserName: 'internet explorer', version: '10.0', platform: 'Windows 7' },
+    { browserName: 'internet explorer', version: '11.0', platform: 'Windows 7' },
     { browserName: 'chrome', version: '50.0', platform: 'Windows 7' },
-    { browserName: 'MicrosoftEdge', version: '14.14393', platform: 'Windows 10' },
+    { browserName: 'chrome', version: '69.0', platform: 'Windows 10' },
+    { browserName: 'MicrosoftEdge', version: '14', platform: 'Windows 10' },
+    { browserName: 'MicrosoftEdge', version: '17', platform: 'Windows 10' },
     // { browserName: 'safari', version: '11.0', platform: 'macOS 10.12' },
     { browserName: 'safari', version: '10.0', platformName: 'iOS', platformVersion: '10.0', deviceOrientation: 'portrait', deviceName: 'iPhone 6 Simulator', appiumVersion: '1.7.1' },
     { browserName: 'chrome', platformName: 'Android', platformVersion: '6.0', deviceOrientation: 'portrait', deviceName: 'Android Emulator', appiumVersion: '1.7.1' }

BIN
test/resources/invalid.png


+ 3 - 0
website/src/docs/contributing.md

@@ -14,8 +14,11 @@ After you have successfully forked the repo, clone and install the project:
 git clone git@github.com:YOUR_USERNAME/uppy.git
 cd uppy
 npm install
+npm run bootstrap
 ```
 
+We use lerna to manage the many plugin packages Uppy has. You should always do `npm run bootstrap` after an `npm install` to make sure lerna has installed the dependencies of each package and that the `package-lock.json` in the repository root is up to date.
+
 Our website’s examples section is also our playground, please read the [Local Previews](#Local-Previews) section to get up and running.
 
 ## Tests

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff