meta: Make E2E Great Again (#3444)

Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com>
This commit is contained in:
Merlijn Vos 2022-02-08 19:37:27 +03:00 committed by GitHub
parent 18bd009771
commit 6da874ed3a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
118 changed files with 3104 additions and 41771 deletions

59
.env.example Normal file
View file

@ -0,0 +1,59 @@
# Rename this file to `.env`
NODE_ENV=development
# Companion
# =======================
COMPANION_DATADIR=./output
COMPANION_DOMAIN=localhost:3020
COMPANION_PROTOCOL=http
COMPANION_PORT=3020
COMPANION_CLIENT_ORIGINS=
COMPANION_SECRET=development
COMPANION_DROPBOX_KEY=***
COMPANION_DROPBOX_SECRET=***
COMPANION_BOX_KEY=***
COMPANION_BOX_SECRET=***
COMPANION_GOOGLE_KEY=***
COMPANION_GOOGLE_SECRET=***
COMPANION_INSTAGRAM_KEY=***
COMPANION_INSTAGRAM_SECRET=***
COMPANION_FACEBOOK_KEY=***
COMPANION_FACEBOOK_SECRET=***
COMPANION_ZOOM_KEY=***
COMPANION_ZOOM_SECRET=***
COMPANION_UNSPLASH_KEY=***
COMPANION_UNSPLASH_SECRET=***
# Transloadit keys can also be used in `e2e/clients`
TRANSLOADIT_KEY=***
TRANSLOADIT_TEMPLATE=***
TRANSLOADIT_SERVICE_URL=https://api2.transloadit.com
# Development environment
# =======================
VITE_UPLOADER=tus
# VITE_UPLOADER=s3
# VITE_UPLOADER=s3-multipart
# xhr will use protocol 'multipart' in companion, if used with a remote service, e.g. google drive.
# If local upload will use browser XHR
# VITE_UPLOADER=xhr
# VITE_UPLOADER=transloadit
# VITE_UPLOADER=transloadit-s3
# VITE_UPLOADER=transloadit-xhr
VITE_COMPANION_URL=http://localhost:3020
VITE_TUS_ENDPOINT=https://tusd.tusdemo.net/files/
VITE_XHR_ENDPOINT=https://xhr-server.herokuapp.com/upload
VITE_TRANSLOADIT_KEY=***
VITE_TRANSLOADIT_TEMPLATE=***
VITE_TRANSLOADIT_SERVICE_URL=https://api2.transloadit.com

View file

@ -291,9 +291,14 @@ module.exports = {
files: ['**/*.ts', '**/*.md/*.ts', '**/*.md/*.typescript'],
excludedFiles: ['examples/angular-example/**/*.ts', 'packages/@uppy/angular/**/*.ts'],
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
],
settings: {
'import/resolver': {
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
},
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
@ -302,6 +307,7 @@ module.exports = {
rules: {
'import/prefer-default-export': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-namespace': 'off',
},
},
{
@ -320,5 +326,13 @@ module.exports = {
react: { pragma: 'React' },
},
},
{
files: ['e2e/**/*.ts'],
extends: ['plugin:cypress/recommended'],
},
{
files: ['e2e/**/*.ts', 'e2e/**/*.js'],
rules: { 'import/no-extraneous-dependencies': 'off' },
},
],
}

View file

@ -30,52 +30,27 @@ Our websites examples section is also our playground, please read the [Local
## Tests
### Unit tests
Unit tests are using Jest and can be run with:
```bash
yarn run test:unit
```
For end-to-end tests, we use [Webdriverio](http://webdriver.io). For it to run locally, you need to install a Selenium standalone server. Follow [the Webdriverio guide](https://webdriver.io/docs/selenium-standalone-service) to do so. You can also install a Selenium standalone server from NPM:
### End-to-End tests
```bash
npm install selenium-standalone -g
selenium-standalone install
```
We use [Cypress](cypress.io/) for our e2e test suite. Be sure to checkout “[Writing your first test](https://docs.cypress.io/guides/getting-started/writing-your-first-test#Add-a-test-file)” and the “[Introduction to Cypress](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress#Cypress-Can-Be-Simple-Sometimes)”. You should also be aware of the “[Best Practices](https://docs.cypress.io/guides/references/best-practices)”.
And then launch it:
To get started make sure you have your `.env` set up. If it doesnt exist, copy and rename the `.env.local` file and populate (some) of the values. What values need to be populated depends on what tests you want to run.
```bash
selenium-standalone start
```
To start the testing suite run:
After you have installed and launched the selenium standalone server, run:
yarn e2e
```bash
yarn run test:endtoend:local
```
To generate the boilerplate for a new test run:
By default, `test:endtoend:local` uses Firefox. You can use a different browser, like Chrome, by passing the `-b` flag:
```bash
yarn run test:endtoend:local -- -b chrome
```
> Note: The `--` is important, it tells yarn that the remaining arguments should be interpreted by the script itself, not by yarn.
You can run in several browsers by passing several `-b` flags:
```bash
yarn run test:endtoend:local -- -b chrome -b firefox
```
When trying to get a specific integration test to pass, its not that helpful to continuously run _all_ tests. You can use the `--suite` flag to run tests from a single `./test/endtoend` folder. For example, `--suite thumbnails` will only run the tests from `./test/endtoend/thumbnails`. It can also be used in conjunction with one or more `-b` flags.
```bash
yarn run test:endtoend:local -- -b chrome --suite thumbnails
```
These tests are also run automatically on Travis builds with [SauceLabs](https://saucelabs.com/) cloud service using different OSes.
yarn e2e:generate
## Development

View file

@ -1,5 +1,11 @@
name: CI
on: [push, pull_request]
on:
push:
branches: [ main ]
pull_request:
# We want all branches so we configure types to be the GH default again
types: [ opened, synchronize, reopened ]
jobs:
unit_tests:

View file

@ -1,5 +1,10 @@
name: Companion
on: [push, pull_request]
on:
push:
branches: [ main ]
pull_request:
# We want all branches so we configure types to be the GH default again
types: [ opened, synchronize, reopened ]
jobs:
test:

View file

@ -1,11 +1,10 @@
name: End-to-end tests
on: push
env:
GIT_AUTHOR_NAME: Uppy Bot
GIT_COMMITTER_NAME: Uppy Bot
GIT_AUTHOR_EMAIL: uppybot@uppy.io
GIT_COMMITTER_EMAIL: uppybot@uppy.io
on:
push:
branches: [ main ]
pull_request:
# We want all branches so we configure types to be the GH default again
types: [ opened, synchronize, reopened ]
jobs:
e2e:
@ -30,17 +29,13 @@ jobs:
node-version: 16.x
- name: Install dependencies
run: corepack yarn install --immutable
- name: Start Sauce Connect
uses: saucelabs/sauce-connect-action@v1.1.1
with:
username: ${{secrets.SAUCE_USERNAME}}
accessKey: ${{secrets.SAUCE_ACCESS_KEY}}
tunnelIdentifier: ${{github.run_id}}.${{github.run_number}}
- name: Build Uppy packages
run: corepack yarn build
- name: Run end-to-end browser tests
run: corepack yarn run test:endtoend
run: corepack yarn run e2e:ci
env:
SAUCE_TUNNEL_IDENTIFIER: ${{github.run_id}}.${{github.run_number}}
SAUCE_BUILD: ${{github.run_id}}
SAUCE_USERNAME: ${{secrets.SAUCE_USERNAME}}
SAUCE_ACCESS_KEY: ${{secrets.SAUCE_ACCESS_KEY}}
TRANSLOADIT_KEY: ${{secrets.TRANSLOADIT_KEY}}
COMPANION_UNSPLASH_KEY: ${{secrets.COMPANION_UNSPLASH_KEY}}
COMPANION_UNSPLASH_SECRET: ${{secrets.COMPANION_UNSPLASH_SECRET}}
VITE_TRANSLOADIT_KEY: ${{secrets.TRANSLOADIT_KEY}}
VITE_TRANSLOADIT_TEMPLATE: ${{secrets.TRANSLOADIT_TEMPLATE}}
VITE_TRANSLOADIT_SERVICE_URL: ${{secrets.TRANSLOADIT_SERVICE_URL}}

2
.gitignore vendored
View file

@ -13,7 +13,7 @@ node_modules
.yarn/install-state.gz
yarn-error.log
env.sh
.env
*.local
dist/

View file

@ -1,11 +0,0 @@
#!/usr/bin/env bash
. ./env.sh
env \
COMPANION_DATADIR="./output" \
COMPANION_DOMAIN="localhost:3020" \
COMPANION_PROTOCOL="http" \
COMPANION_PORT=3020 \
COMPANION_CLIENT_ORIGINS="" \
COMPANION_SECRET="development" \
nodemon --watch packages/@uppy/companion/src --exec node ./packages/@uppy/companion/src/standalone/start-server.js

19
bin/companion.sh Normal file
View file

@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Load local env vars. In CI, these are injected.
if [ -f .env ]; then
# https://gist.github.com/mihow/9c7f559807069a03e302605691f85572?permalink_comment_id=3625310#gistcomment-3625310
set -a
source <(sed -e '/^#/d;/^\s*$/d' -e "s/'/'\\\''/g" -e "s/=\(.*\)/='\1'/g" .env)
set +a
nodemon --watch packages/@uppy/companion/src --exec node ./packages/@uppy/companion/src/standalone/start-server.js
else
env \
COMPANION_DATADIR="./output" \
COMPANION_DOMAIN="localhost:3020" \
COMPANION_PROTOCOL="http" \
COMPANION_PORT=3020 \
COMPANION_CLIENT_ORIGINS="" \
COMPANION_SECRET="development" \
nodemon --watch packages/@uppy/companion/src --exec node ./packages/@uppy/companion/src/standalone/start-server.js
fi

View file

@ -1,19 +0,0 @@
#!/usr/bin/env bash
set -o pipefail
set -o errexit
set -o nounset
set -o xtrace
# Set magic variables for current file & dir
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
__base="$(basename ${__file} .sh)"
__root="$(cd "$(dirname "${__dir}")" && pwd)"
echo "Preparing for end to end test: copying static HTML and CSS, building JS"
rm -rf "${__root}/test/endtoend/node_modules"
yarn run build
bash "${__dir}/endtoend-build-tests"

View file

@ -1,88 +0,0 @@
#!/usr/bin/env bash
set -o pipefail
set -o errexit
set -o nounset
# Set magic variables for current file & dir
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
__base="$(basename ${__file} .sh)"
__root="$(cd "$(dirname "${__dir}")" && pwd)"
if [ -z "${CI:-}" ]; then
echo "!! Running CI-style end-to-end tests but CI environment variable is not set"
echo "It is generally ill-advised to run the full end-to-end suite on your local machine."
echo "You are probably looking for this instead:"
echo " yarn run test:endtoend:local"
echo ""
echo "Hit Ctrl+C to stop or Enter if you REALLY DO want to run the full thing."
read
else
echo ::group::endtoend_build_ci
fi
set -o xtrace
YARN="corepack yarn"
VERDACCIO_REGISTRY=http://localhost:4002
CURRENT_COMMIT="$(git rev-parse HEAD)"
cleanup() {
rm -rf "${__root}/test/endtoend/node_modules"
rm -rf "${__root}/test/endtoend/tmp"
git reset
git checkout $CURRENT_COMMIT
if [ -n "${CI:-}" ]; then
echo ::endgroup::
fi
}
function on_exit() {
# revert to public registry
$YARN config unset npmScopes --home
$YARN config unset npmRegistryServer --home
$YARN config unset npmAuthToken --home
$YARN config unset unsafeHttpWhitelist --home
cleanup
}
trap on_exit EXIT
echo "Preparing for end to end test: copying static HTML and CSS, building JS"
rm -rf "${__root}/test/endtoend/node_modules"
# list of @uppy/* packages
PACKAGES="$($YARN workspaces list --json | node -e 'require("readline").createInterface({input:process.stdin}).on("line",l=>{const{name}=JSON.parse(l);if(name?.startsWith("@uppy/"))console.log(name)})')"
cleanup
# Initialise verdaccio storage path.
mkdir -p "${__root}/test/endtoend/tmp/verdaccio"
$YARN run build
# https://github.com/facebook/create-react-app/pull/4626
TOKEN="$($YARN 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 -- yarn.lock
$YARN config set npmRegistryServer "$VERDACCIO_REGISTRY" --home
$YARN config set npmAuthToken "$TOKEN" --home
$YARN config set npmScopes.uppy.npmRegistryServer "$VERDACCIO_REGISTRY" --home
$YARN config set npmScopes.uppy.npmPublishRegistry "$VERDACCIO_REGISTRY" --home
$YARN config set npmScopes.uppy.npmAuthToken "$TOKEN" --home
$YARN config set unsafeHttpWhitelist "localhost" --home
# Simulate a publish of everything, to the local registry,
# without changing things in git
ENDTOEND=1 $YARN workspaces foreach --include '@uppy/*'\
version prerelease -d
$YARN version apply --all --prerelease
ENDTOEND=1 $YARN workspaces foreach --include '@uppy/*'\
npm publish
bash "${__dir}/endtoend-build-tests"
cleanup

View file

@ -1,37 +0,0 @@
#!/usr/bin/env bash
set -o pipefail
set -o errexit
set -o nounset
set -o xtrace
# Set magic variables for current file & dir
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
__base="$(basename "${__file}" .sh)"
__root="$(cd "$(dirname "${__dir}")" && pwd)"
__e2eSuites="${__root}/test/endtoend"
ESBUILD="$(corepack yarn workspace @uppy-tests/end2end bin esbuild)"
for t in $(cd "$__e2eSuites" && ls -d */ | cut -f1 -d'/'); do
if [ "$t" = "tmp" ]; then continue; fi
if [ "$t" = "create-react-app" ]; then continue; fi
mkdir -p "${__e2eSuites}/$t/dist"
cp "${__root}/packages/uppy/dist/uppy.min.css" "${__e2eSuites}/$t/dist"
cp "${__e2eSuites}/$t/index.html" "${__e2eSuites}/$t/dist"
entryPointName="main.mjs"
if [ "$t" = "typescript" ]; then entryPointName="main.ts"; fi
"$ESBUILD" "${__e2eSuites}/$t/$entryPointName" \
--bundle --target=es2017 \
--outfile="${__e2eSuites}/$t/dist/bundle.js"
done
# Speeecial tests that need custom builds.
pushd "${__e2eSuites}/create-react-app"
npm install
REACT_APP_ON_TRAVIS="${TRAVIS:-}" npm run build
popd

9
e2e/.parcelrc Normal file
View file

@ -0,0 +1,9 @@
{
"extends": "@parcel/config-default",
"transformers": {
"*.{js,mjs,jsx,cjs,ts,tsx}": [
"@parcel/transformer-js",
"@parcel/transformer-react-refresh-wrap"
]
}
}

View file

@ -0,0 +1,19 @@
/* eslint-disable react/react-in-jsx-scope */
import Uppy from '@uppy/core'
// eslint-disable-next-line no-unused-vars
import React from 'react'
import { Dashboard } from '@uppy/react'
import '@uppy/core/dist/style.css'
import '@uppy/dashboard/dist/style.css'
export default App
function App () {
// Ideally we test with the `useUppy` hook,
// as that's how it should be used.
// But that results in breaking the rules of hooks errors?
const uppy = new Uppy()
return <Dashboard uppy={uppy} />
}

View file

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>dashboard-react</title>
<script defer type="module" src="index.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>

View file

@ -0,0 +1,5 @@
/* eslint-disable react/react-in-jsx-scope */
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('app'))

View file

@ -0,0 +1,22 @@
import { Uppy } from '@uppy/core'
import Dashboard from '@uppy/dashboard'
import Transloadit from '@uppy/transloadit'
import '@uppy/core/dist/style.css'
import '@uppy/dashboard/dist/style.css'
// Environment variables:
// https://en.parceljs.org/env.html
const uppy = new Uppy()
.use(Dashboard, { target: '#app', inline: true })
.use(Transloadit, {
service: process.env.VITE_TRANSLOADIT_SERVICE_URL,
waitForEncoding: true,
params: {
auth: { key: process.env.VITE_TRANSLOADIT_KEY },
template_id: process.env.VITE_TRANSLOADIT_TEMPLATE,
},
})
// Keep this here to access uppy in tests
window.uppy = uppy

View file

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>dashboard-transloadit</title>
<script defer type="module" src="app.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>

View file

@ -0,0 +1,18 @@
import { Uppy } from '@uppy/core'
import Dashboard from '@uppy/dashboard'
import Tus from '@uppy/tus'
import Unsplash from '@uppy/unsplash'
import Url from '@uppy/url'
import '@uppy/core/dist/style.css'
import '@uppy/dashboard/dist/style.css'
const companionUrl = 'http://localhost:3020'
const uppy = new Uppy()
.use(Dashboard, { target: '#app', inline: true })
.use(Tus, { endpoint: 'https://tusd.tusdemo.net/files' })
.use(Url, { target: Dashboard, companionUrl })
.use(Unsplash, { target: Dashboard, companionUrl })
// Keep this here to access uppy in tests
window.uppy = uppy

View file

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>dashboard-tus</title>
<script defer type="module" src="app.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>

View file

@ -0,0 +1,13 @@
import Uppy from '@uppy/core'
import Dashboard from '@uppy/dashboard'
import ImageEditor from '@uppy/image-editor'
import '@uppy/core/dist/style.css'
import '@uppy/dashboard/dist/style.css'
const uppy = new Uppy()
.use(Dashboard, { target: '#app', inline: true })
.use(ImageEditor, { target: Dashboard })
// Keep this here to access uppy in tests
window.uppy = uppy

View file

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>dashboard-ui</title>
<script defer type="module" src="app.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>

View file

@ -0,0 +1,22 @@
<template>
<dashboard :uppy="uppy" />
</template>
<script>
import Vue from 'vue'
import Uppy from '@uppy/core'
import { Dashboard } from '@uppy/vue'
export default {
name: 'App',
components: {
Dashboard,
},
computed: {
uppy: () => new Uppy()
},
}
</script>
<style src='@uppy/core/dist/style.css'></style>
<style src='@uppy/dashboard/dist/style.css'></style>

View file

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>dashboard-vue</title>
<script defer type="module" src="index.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>

View file

@ -0,0 +1,4 @@
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

19
e2e/clients/index.html Normal file
View file

@ -0,0 +1,19 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>End-to-End test suite</title>
</head>
<body>
<h1>Test apps</h1>
<nav>
<ul>
<li><a href="dashboard-tus/index.html">dashboard-tus</a></li>
<li><a href="dashboard-transloadit/index.html">dashboard-transloadit</a></li>
<li><a href="dashboard-ui/index.html">dashboard-ui</a></li>
<li><a href="dashboard-react/index.html">dashboard-react</a></li>
<li><a href="dashboard-vue/index.html">dashboard-vue</a></li>
</ul>
<nav>
</body>
</html>

4
e2e/cypress.json Normal file
View file

@ -0,0 +1,4 @@
{
"baseUrl": "http://localhost:1234",
"screenshotOnRunFailure": false
}

View file

@ -1,3 +1,4 @@
// eslint-disable-next-line import/no-extraneous-dependencies
const deepFreeze = require('deep-freeze')
/* eslint-disable no-underscore-dangle */

View file

Before

Width:  |  Height:  |  Size: 636 KiB

After

Width:  |  Height:  |  Size: 636 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 544 KiB

After

Width:  |  Height:  |  Size: 544 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

View file

@ -0,0 +1,13 @@
describe('dashboard-react', () => {
beforeEach(() => {
cy.visit('/dashboard-react')
cy.get('.uppy-Dashboard-input').as('file-input')
})
it('should render in React and show thumbnails', () => {
cy.get('@file-input').attachFile(['images/cat.jpg', 'images/traffic.jpg'])
cy.get('.uppy-Dashboard-Item-previewImg')
.should('have.length', 2)
.each((element) => expect(element).attr('src').to.include('blob:'))
})
})

View file

@ -0,0 +1,18 @@
describe('Dashboard with Transloadit', () => {
beforeEach(() => {
cy.visit('/dashboard-transloadit')
cy.get('.uppy-Dashboard-input').as('file-input')
cy.intercept('/assemblies/*').as('assemblies')
cy.intercept('/resumable/*').as('resumable')
})
it('should upload cat image successfully', () => {
cy.get('@file-input').attachFile('images/cat.jpg')
cy.get('.uppy-StatusBar-actionBtn--upload').click()
cy.wait('@assemblies')
cy.wait('@resumable')
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
})
})

View file

@ -0,0 +1,78 @@
import type BaseTus from '@uppy/tus'
type Tus = BaseTus & {
requests: { isPaused: boolean }
}
describe('Dashboard with Tus', () => {
beforeEach(() => {
cy.visit('/dashboard-tus')
cy.get('.uppy-Dashboard-input').as('file-input')
cy.intercept('/files/*').as('tus')
cy.intercept('http://localhost:3020/url/*').as('url')
cy.intercept('http://localhost:3020/search/unsplash/*').as('unsplash')
})
it('should upload cat image successfully', () => {
cy.get('@file-input').attachFile('images/cat.jpg')
cy.get('.uppy-StatusBar-actionBtn--upload').click()
cy.wait('@tus')
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
})
// TODO: figure out why we send a PATCH request while another PATCH is still pending,
// resulting in a 423 upload is locked
// logs: https://gist.github.com/Acconut/d17315d2718d2944aabe2941f268530d
xit('should start exponential backoff when receiving HTTP 429', () => {
cy.get('@file-input').attachFile(['images/cat.jpg', 'images/traffic.jpg'])
cy.get('.uppy-StatusBar-actionBtn--upload').click()
cy.intercept(
{ method: 'PATCH', pathname: '/files/*', times: 1 },
{ statusCode: 429, body: {} },
).as('patch')
cy.wait('@patch')
cy.window().then(({ uppy }) => {
expect(uppy.getPlugin<Tus>('Tus').requests.isPaused).to.equal(true)
cy.wait('@tus')
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
})
})
it('should upload remote image with URL plugin', () => {
cy.get('[data-cy="Url"]').click()
cy.get('.uppy-Url-input').type('https://via.placeholder.com/600x400')
cy.get('.uppy-Url-importButton').click()
cy.get('.uppy-StatusBar-actionBtn--upload').click()
cy.wait('@url')
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
})
it('should upload remote image with Unsplash plugin', () => {
cy.get('[data-cy="Unsplash"]').click()
cy.get('.uppy-SearchProvider-input').type('book')
cy.get('.uppy-SearchProvider-searchButton').click()
cy.wait('@unsplash')
// Test that the author link is visible
cy.get('.uppy-ProviderBrowserItem')
.first()
.within(() => {
cy.root().click()
// We have hover states that show the author
// but we don't have hover in e2e, so we focus after the click
// to get the same effect. Also tests keyboard users this way.
cy.get('input[type="checkbox"]').focus()
cy.get('a').should('have.css', 'display', 'block')
})
cy.get('.uppy-c-btn-primary').click()
cy.get('.uppy-StatusBar-actionBtn--upload').click()
cy.wait('@unsplash')
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000) // bad practice, but the request is successful before the status bar updates here
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
})
})

View file

@ -0,0 +1,13 @@
describe('dashboard-ui', () => {
beforeEach(() => {
cy.visit('/dashboard-ui')
cy.get('.uppy-Dashboard-input').as('file-input')
})
it('should render thumbnails', () => {
cy.get('@file-input').attachFile(['images/cat.jpg', 'images/traffic.jpg'])
cy.get('.uppy-Dashboard-Item-previewImg')
.should('have.length', 2)
.each((element) => expect(element).attr('src').to.include('blob:'))
})
})

View file

@ -0,0 +1,14 @@
describe('dashboard-vue', () => {
beforeEach(() => {
cy.visit('/dashboard-vue')
})
// Only Vue 3 works in Parcel if you use SFC's but Vue 3 is broken in Uppy:
// https://github.com/transloadit/uppy/issues/2877
xit('should render in Vue 3 and show thumbnails', () => {
cy.get('@file-input').attachFile(['images/cat.jpg', 'images/traffic.jpg'])
cy.get('.uppy-Dashboard-Item-previewImg')
.should('have.length', 2)
.each((element) => expect(element).attr('src').to.include('blob:'))
})
})

View file

@ -0,0 +1,22 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

View file

@ -0,0 +1,33 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
/* eslint-disable-next-line import/no-extraneous-dependencies */
import 'cypress-file-upload'
import { createFakeFile } from './createFakeFile'
Cypress.Commands.add('createFakeFile', createFakeFile)

View file

@ -0,0 +1,49 @@
declare global {
namespace Cypress {
interface Chainable {
createFakeFile: typeof createFakeFile;
}
}
}
interface File {
source: string
name: string
type: string
data: Blob
}
export function createFakeFile (name?: string, type?: string, b64?: string): File {
if (!b64) b64 = 'PHN2ZyB2aWV3Qm94PSIwIDAgMTIwIDEyMCI+CiAgPGNpcmNsZSBjeD0iNjAiIGN5PSI2MCIgcj0iNTAiLz4KPC9zdmc+Cg=='
if (!type) type = 'image/svg+xml'
// https://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript
function base64toBlob (base64Data: string, contentType = '') {
const sliceSize = 1024
const byteCharacters = atob(base64Data)
const bytesLength = byteCharacters.length
const slicesCount = Math.ceil(bytesLength / sliceSize)
const byteArrays = new Array(slicesCount)
for (let sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
const begin = sliceIndex * sliceSize
const end = Math.min(begin + sliceSize, bytesLength)
const bytes = new Array(end - begin)
for (let offset = begin, i = 0; offset < end; ++i, ++offset) {
bytes[i] = byteCharacters[offset].charCodeAt(0)
}
byteArrays[sliceIndex] = new Uint8Array(bytes)
}
return new Blob(byteArrays, { type: contentType })
}
const blob = base64toBlob(b64, type)
return {
source: 'test',
name: name || 'test-file',
type: blob.type,
data: blob,
}
}

View file

@ -0,0 +1,24 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
import './commands.ts'
import type { Uppy } from '@uppy/core'
declare global {
interface Window {
uppy: Uppy
}
}

85
e2e/generate-test.mjs Executable file
View file

@ -0,0 +1,85 @@
#!/usr/bin/env node
/* eslint-disable no-console, import/no-extraneous-dependencies */
import prompts from 'prompts'
import fs from 'node:fs/promises'
import dedent from 'dedent'
const packageNames = await fs.readdir(new URL('../packages/@uppy', import.meta.url))
const unwantedPackages = ['core', 'companion', 'redux-dev-tools', 'utils']
const { name } = await prompts({
type: 'text',
name: 'name',
message: 'What should the name of the test be (e.g `dashboard-tus`)?',
validate: (value) => /^[a-z|-]+$/i.test(value),
})
const { packages } = await prompts({
type: 'multiselect',
name: 'packages',
message: 'What packages do you want to test?',
hint: '@uppy/core is automatically included',
choices: packageNames
.filter((pkg) => !unwantedPackages.includes(pkg))
.map((pkg) => ({ title: pkg, value: pkg })),
})
const camelcase = (str) => str
.toLowerCase()
.replace(/([-][a-z])/g, (group) => group.toUpperCase().replace('-', ''))
const testUrl = new URL(`cypress/integration/${name}.spec.ts`, import.meta.url)
const test = dedent`
describe('${name}', () => {
beforeEach(() => {
cy.visit('/${name}')
})
})
`
const htmlUrl = new URL(`clients/${name}/index.html`, import.meta.url)
const html = dedent`
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>${name}</title>
<script defer type="module" src="app.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
`
const appUrl = new URL(`clients/${name}/app.js`, import.meta.url)
// dedent is acting weird for this one but this formatting fixes it.
const app = dedent`
import Uppy from '@uppy/core'
${packages.map((pgk) => `import ${camelcase(pgk)} from '@uppy/${pgk}'`).join('\n')}
const uppy = new Uppy()
${packages.map((pkg) => `.use(${camelcase(pkg)})`).join('\n\t')}
// Keep this here to access uppy in tests
window.uppy = uppy
`
await fs.writeFile(testUrl, test)
await fs.mkdir(new URL(`clients/${name}`, import.meta.url))
await fs.writeFile(htmlUrl, html)
await fs.writeFile(appUrl, app)
const homeUrl = new URL('clients/index.html', import.meta.url)
const home = await fs.readFile(homeUrl, 'utf8')
const newHome = home.replace(
'</ul>',
`\t<li><a href="${name}/index.html">${name}</a></li>\n\t\t\t</ul>`,
)
await fs.writeFile(homeUrl, newHome)
const prettyPath = (url) => url.toString().split('uppy', 2)[1]
console.log(`✅ Generated ${prettyPath(testUrl)}`)
console.log(`✅ Generated ${prettyPath(htmlUrl)}`)
console.log(`✅ Generated ${prettyPath(appUrl)}`)
console.log(`✅ Updated ${prettyPath(homeUrl)}`)

59
e2e/package.json Normal file
View file

@ -0,0 +1,59 @@
{
"name": "e2e",
"private": true,
"author": "Merlijn Vos <merlijn@soverin.net>",
"description": "End-to-end test suite for Uppy",
"scripts": {
"client:start": "parcel clients/index.html",
"cypress:open": "cypress open",
"cypress:headless": "cypress run",
"generate-test": "yarn node generate-test.mjs"
},
"dependencies": {
"@uppy/audio": "workspace:^",
"@uppy/aws-s3": "workspace:^",
"@uppy/aws-s3-multipart": "workspace:^",
"@uppy/box": "workspace:^",
"@uppy/companion-client": "workspace:^",
"@uppy/core": "workspace:^",
"@uppy/dashboard": "workspace:^",
"@uppy/drag-drop": "workspace:^",
"@uppy/drop-target": "workspace:^",
"@uppy/dropbox": "workspace:^",
"@uppy/facebook": "workspace:^",
"@uppy/file-input": "workspace:^",
"@uppy/form": "workspace:^",
"@uppy/golden-retriever": "workspace:^",
"@uppy/google-drive": "workspace:^",
"@uppy/image-editor": "workspace:^",
"@uppy/informer": "workspace:^",
"@uppy/instagram": "workspace:^",
"@uppy/onedrive": "workspace:^",
"@uppy/progress-bar": "workspace:^",
"@uppy/provider-views": "workspace:^",
"@uppy/screen-capture": "workspace:^",
"@uppy/status-bar": "workspace:^",
"@uppy/store-default": "workspace:^",
"@uppy/store-redux": "workspace:^",
"@uppy/thumbnail-generator": "workspace:^",
"@uppy/transloadit": "workspace:^",
"@uppy/tus": "workspace:^",
"@uppy/unsplash": "workspace:^",
"@uppy/url": "workspace:^",
"@uppy/webcam": "workspace:^",
"@uppy/xhr-upload": "workspace:^",
"@uppy/zoom": "workspace:^"
},
"devDependencies": {
"cypress": "^9.0.0",
"cypress-file-upload": "^5.0.8",
"dedent": "^0.7.0",
"deep-freeze": "^0.0.1",
"parcel": "^2.0.1",
"prompts": "^2.4.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"typescript": "^4.5.4",
"vue": "next"
}
}

8
e2e/tsconfig.json Normal file
View file

@ -0,0 +1,8 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress", "cypress-file-upload"]
},
"include": ["cypress/**/*.ts"]
}

View file

@ -1,39 +0,0 @@
# Rename this file to env.sh, it will be kept out of Git.
# So suitable for adding secret keys and such
export NODE_ENV="${NODE_ENV:-development}"
export COMPANION_DROPBOX_KEY="***"
export COMPANION_DROPBOX_SECRET="***"
export COMPANION_BOX_KEY="***"
export COMPANION_BOX_SECRET="***"
export COMPANION_GOOGLE_KEY="***"
export COMPANION_GOOGLE_SECRET="***"
export COMPANION_INSTAGRAM_KEY="***"
export COMPANION_INSTAGRAM_SECRET="***"
export COMPANION_FACEBOOK_KEY="***"
export COMPANION_FACEBOOK_SECRET="***"
export EDGLY_KEY="***"
export EDGLY_SECRET="***"
export GITHUB_TOKEN="***"
export COMPANION_ZOOM_KEY="***"
export COMPANION_ZOOM_SECRET="***"
# Let's not set this by default, because that will make acceptance tests Always run on Saucelabs
## export SAUCE_ACCESS_KEY="***"
## export SAUCE_USERNAME="***"
# travis encrypt --add GHPAGES_URL=https://secret_access_token@github.com/transloadit/uppy.git
# travis encrypt --add env.global "COMPANION_DROPBOX_KEY=${COMPANION_DROPBOX_KEY}"
# travis encrypt --add env.global "COMPANION_DROPBOX_SECRET=${COMPANION_DROPBOX_SECRET}"
# travis encrypt --add env.global "COMPANION_BOX_KEY=${COMPANION_BOX_KEY}"
# travis encrypt --add env.global "COMPANION_BOX_SECRET=${COMPANION_BOX_SECRET}"
# travis encrypt --add env.global "COMPANION_GOOGLE_KEY=${COMPANION_GOOGLE_KEY}"
# travis encrypt --add env.global "COMPANION_GOOGLE_SECRET=${COMPANION_GOOGLE_SECRET}"
# travis encrypt --add env.global "COMPANION_INSTAGRAM_KEY=${COMPANION_INSTAGRAM_KEY}"
# travis encrypt --add env.global "COMPANION_INSTAGRAM_SECRET=${COMPANION_INSTAGRAM_SECRET}"
# The Travis Sauce Connect addon exports the SAUCE_USERNAME and SAUCE_ACCESS_KEY environment variables,
# and relays connections to the hub URL back to Sauce Labs.
# See: https://docs.travis-ci.com/user/gui-and-headless-browsers/#Using-Sauce-Labs
# travis encrypt --add addons.sauce_connect.username "${SAUCE_USERNAME}"
# travis encrypt --add addons.sauce_connect.access_key "${SAUCE_ACCESS_KEY}"

View file

@ -34,7 +34,8 @@
"packages/uppy",
"private/*",
"test/endtoend",
"website"
"website",
"e2e"
],
"devDependencies": {
"@babel/cli": "^7.14.5",
@ -47,6 +48,7 @@
"@babel/preset-env": "^7.14.7",
"@babel/register": "^7.10.5",
"@goto-bus-stop/envify": "^5.0.0",
"@parcel/transformer-vue": "^2.2.1",
"@size-limit/preset-big-lib": "7.0.5",
"@size-limit/webpack-why": "^7.0.5",
"@types/jasmine": "file:./private/@types/jasmine",
@ -64,12 +66,12 @@
"core-js": "~3.19.3",
"cssnano": "^5.0.6",
"dedent": "^0.7.0",
"deep-freeze": "^0.0.1",
"esbuild": "^0.14.1",
"esbuild-plugin-babel": "^0.2.3",
"eslint": "^8.0.0",
"eslint-config-transloadit": "^2.0.0",
"eslint-plugin-compat": "^4.0.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-jest": "^25.0.0",
"eslint-plugin-jsdoc": "^37.0.0",
@ -106,6 +108,7 @@
"resolve": "^1.17.0",
"sass": "^1.29.0",
"size-limit": "7.0.5",
"start-server-and-test": "^1.14.0",
"stylelint": "^13.13.1",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-standard": "^22.0.0",
@ -139,8 +142,14 @@
"lint": "eslint . --cache",
"release": "PACKAGES=$(yarn workspaces list --json) yarn workspace @uppy-dev/release interactive",
"size": "echo 'JS Bundle mingz:' && cat ./packages/uppy/dist/uppy.min.js | gzip | wc -c && echo 'CSS Bundle mingz:' && cat ./packages/uppy/dist/uppy.min.css | gzip | wc -c",
"start:companion": "bash ./bin/companion",
"start:companion": "bash bin/companion.sh",
"start": "npm-run-all --parallel watch start:companion web:start",
"e2e": "npm-run-all --parallel watch:js:lib e2e:client start:companion e2e:cypress",
"e2e:ci": "start-server-and-test 'npm-run-all --parallel e2e:client start:companion' '1234|3020' e2e:headless",
"e2e:client": "yarn workspace e2e client:start",
"e2e:cypress": "yarn workspace e2e cypress:open",
"e2e:headless": "yarn workspace e2e cypress:headless",
"e2e:generate": "yarn workspace e2e generate-test",
"test:companion": "yarn workspace @uppy/companion test",
"test:endtoend:local": "yarn workspace @uppy-tests/end2end test:endtoend:local",
"test:endtoend": "yarn workspace @uppy-tests/end2end test:endtoend",

View file

@ -1,3 +1,4 @@
/* eslint-disable max-classes-per-file */
/* global AggregateError */
'use strict'

View file

@ -4,12 +4,12 @@ const path = require('path')
const prettierBytes = require('@transloadit/prettier-bytes')
const Core = require('./index')
const UIPlugin = require('./UIPlugin')
const AcquirerPlugin1 = require('../../../../test/mocks/acquirerPlugin1')
const AcquirerPlugin2 = require('../../../../test/mocks/acquirerPlugin2')
const InvalidPlugin = require('../../../../test/mocks/invalidPlugin')
const InvalidPluginWithoutId = require('../../../../test/mocks/invalidPluginWithoutId')
const InvalidPluginWithoutType = require('../../../../test/mocks/invalidPluginWithoutType')
const DeepFrozenStore = require('../../../../test/resources/DeepFrozenStore.js')
const AcquirerPlugin1 = require('./mocks/acquirerPlugin1')
const AcquirerPlugin2 = require('./mocks/acquirerPlugin2')
const InvalidPlugin = require('./mocks/invalidPlugin')
const InvalidPluginWithoutId = require('./mocks/invalidPluginWithoutId')
const InvalidPluginWithoutType = require('./mocks/invalidPluginWithoutType')
const DeepFrozenStore = require('../../../../e2e/cypress/fixtures/DeepFrozenStore.js')
jest.mock('nanoid/non-secure', () => {
return { nanoid: () => 'cjd09qwxb000dlql4tp4doz8h' }
@ -18,7 +18,7 @@ jest.mock('@uppy/utils/lib/findDOMElement', () => {
return () => null
})
const sampleImage = fs.readFileSync(path.join(__dirname, '../../../../test/resources/image.jpg'))
const sampleImage = fs.readFileSync(path.join(__dirname, '../../../../e2e/cypress/fixtures/images/image.jpg'))
describe('src/Core', () => {
const RealCreateObjectUrl = global.URL.createObjectURL

View file

@ -1,4 +1,4 @@
const { UIPlugin } = require('../../packages/@uppy/core')
const UIPlugin = require('../UIPlugin')
module.exports = class TestSelector1 extends UIPlugin {
constructor (uppy, opts) {

View file

@ -1,4 +1,4 @@
const { UIPlugin } = require('../../packages/@uppy/core')
const UIPlugin = require('../UIPlugin')
module.exports = class TestSelector2 extends UIPlugin {
constructor (uppy, opts) {

View file

@ -1,4 +1,4 @@
const { UIPlugin } = require('../../packages/@uppy/core')
const UIPlugin = require('../UIPlugin')
module.exports = class InvalidPluginWithoutName extends UIPlugin {
constructor (uppy, opts) {

View file

@ -1,4 +1,4 @@
const { UIPlugin } = require('../../packages/@uppy/core')
const UIPlugin = require('../UIPlugin')
module.exports = class InvalidPluginWithoutType extends UIPlugin {
constructor (uppy, opts) {

View file

@ -125,6 +125,7 @@ class AddFiles extends Component {
className="uppy-u-reset uppy-c-btn uppy-DashboardTab-btn"
role="tab"
tabIndex={0}
data-cy={acquirer.id}
aria-controls={`uppy-DashboardContent-panel--${acquirer.id}`}
aria-selected={this.props.activePickerPanel.id === acquirer.id}
data-uppy-super-focusable

View file

@ -1,24 +0,0 @@
VITE_UPLOADER=tus
#### To customize this, create a .env.local file that overrides those values
# VITE_UPLOADER=tus
# VITE_UPLOADER=s3
# VITE_UPLOADER=s3-multipart
# xhr will use protocol 'multipart' in companion, if used with a remote service, e.g. google drive.
# If local upload will use browser XHR
# VITE_UPLOADER=xhr
# VITE_UPLOADER=transloadit
# VITE_UPLOADER=transloadit-s3
# VITE_UPLOADER=transloadit-xhr
### DEV CONFIG: Endpoint URLs
VITE_COMPANION_URL=http://localhost:3020
VITE_TUS_ENDPOINT=https://tusd.tusdemo.net/files/
VITE_XHR_ENDPOINT=https://xhr-server.herokuapp.com/upload
### DEV CONFIG: Transloadit keys
VITE_TRANSLOADIT_KEY=...
VITE_TRANSLOADIT_TEMPLATE=...
VITE_TRANSLOADIT_SERVICE_URL=https://api2.transloadit.com

View file

@ -6,6 +6,9 @@
"@uppy": "../../packages/@uppy"
}
},
"dependencies": {
"@uppy/companion": "workspace:^"
},
"devDependencies": {
"@babel/core": "^7.4.4",
"@babel/plugin-transform-react-jsx": "^7.10.4",

View file

@ -16,6 +16,7 @@ let counter = 0
* @type {import('vite').UserConfig}
*/
const config = {
envDir: fileURLToPath(ROOT),
build: {
commonjsOptions: {
defaultIsModuleExports: true,

View file

@ -1,37 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Uppy test page</title>
</head>
<body>
<style>
#main { display: flex; } /* display side by side */
</style>
<main>
<h2>Chaos monkey</h2>
<div id="main">
<div id="dash"></div>
<div>
<ol id="log"></ol>
</div>
</div>
</main>
<link href="uppy.min.css" rel="stylesheet">
<script>
function addLogMessage (message) {
var log = document.querySelector('#log')
while (log.childNodes.length >= 15) {
log.removeChild(log.firstChild)
log.start = (parseInt(log.start, 10) || 0) + 1
}
var li = document.createElement('li')
li.appendChild(document.createTextNode(message))
log.appendChild(li)
}
</script>
<script src="bundle.js"></script>
</body>
</html>

View file

@ -1,40 +0,0 @@
import Uppy from '@uppy/core'
import Dashboard from '@uppy/dashboard'
import Tus from '@uppy/tus'
import canvasToBlob from '@uppy/utils/lib/canvasToBlob'
const endpoint = 'http://localhost:1081'
let id = 0
window.setup = function setup (options) {
id += 1
// Initialise Uppy with Drag & Drop
const uppy = new Uppy({ id: `uppy${id}`, debug: true })
uppy.use(Dashboard, { inline: true, target: '#dash' })
uppy.use(Tus, {
endpoint: `${endpoint}/files/`,
limit: options.limit,
})
uppy.on('file-added', (file) => {
randomColorImage().then((blob) => {
uppy.setFileState(file.id, {
// eslint-disable-next-line compat/compat
preview: URL.createObjectURL(blob),
})
})
})
return uppy
}
function randomColorImage () {
const canvas = document.createElement('canvas')
canvas.width = 140
canvas.height = 140
const context = canvas.getContext('2d')
context.fillStyle = '#xxxxxx'.replace(/x/g, () => '0123456789ABCDEF'[Math.floor(Math.random() * 16)])
context.fillRect(0, 0, 140, 140)
return canvasToBlob(canvas)
}

View file

@ -1,102 +0,0 @@
/* global browser */
const crypto = require('crypto')
const { selectFakeFile } = require('../utils')
const testURL = 'http://localhost:4567/chaos-monkey'
describe('Chaos monkey', function test () {
this.timeout(5 * 60 * 1000) // 5 minutes
beforeEach(async () => {
await browser.url(testURL)
})
it('Add and cancel a bunch', async () => {
await browser.execute(() => {
window.currentUppy = window.setup({ limit: 3 })
window.onerror = (message) => {
window.anyError = message
}
})
const types = ['application/octet-stream', 'text/plain']
const generate = {
'application/octet-stream' () {
const len = Math.round(Math.random() * 5_000_000)
return crypto.randomBytes(len)
},
'text/plain' () {
const len = Math.round(Math.random() * 5_000_000 / 'Lorem ipsum'.length)
return Buffer.from('Lorem ipsum'.repeat(len))
},
}
async function addFile () {
await browser.execute(() => {
window.addLogMessage('Adding a file')
})
const type = types[Math.floor(Math.random() * types.length)]
const data = generate[type]().toString('base64')
const name = `${Math.random().toString(32).slice(2)}-file`
await browser.execute(selectFakeFile, 'currentUppy', name, type, data)
}
function cancelFile () {
return browser.execute(() => {
window.addLogMessage('Cancelling a file')
// prefer deleting a file that is uploading right now
const selector = Math.random() <= 0.7
? '.is-inprogress .uppy-Dashboard-Item-action--remove'
: '.uppy-Dashboard-Item-action--remove'
const buttons = document.querySelectorAll(selector)
const del = buttons[Math.floor(Math.random() * buttons.length)]
if (del) del.click()
})
}
function startUploadIfAnyWaitingFiles () {
return browser.execute(() => {
window.addLogMessage('Starting upload')
const start = document.querySelector('.uppy-StatusBar-actionBtn--upload')
if (start) start.click()
})
}
function cancelAll () {
return browser.execute(() => {
window.addLogMessage('Cancelling everything')
const button = document.querySelector('.uppy-DashboardContent-back')
if (button) button.click()
})
}
await addFile()
await addFile()
await addFile()
for (let i = 0; i < 300; i++) {
await browser.pause(50 + Math.floor(Math.random() * 300))
const v = Math.floor(Math.random() * 100)
if (v < 45) {
await addFile()
} else if (v < 55) {
await cancelFile()
} else if (v === 55) {
await cancelAll()
} else if (v < 75) {
await startUploadIfAnyWaitingFiles()
} else {
// wait
}
}
await cancelAll()
const errorMessage = await browser.execute(() => {
return window.anyError
})
// yikes chai, why can this not be a function call
expect(errorMessage).to.not.exist // eslint-disable-line no-unused-expressions
})
})

View file

@ -1,2 +0,0 @@
SKIP_PREFLIGHT_CHECK=true
DISABLE_ESLINT_PLUGIN=true

View file

@ -1,7 +0,0 @@
{
"settings": {
"react": {
"pragma": "React"
}
}
}

View file

@ -1,21 +0,0 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

File diff suppressed because it is too large Load diff

View file

@ -1,17 +0,0 @@
{
"name": "create-react-app",
"version": "0.1.0",
"private": true,
"homepage": "/create-react-app/",
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -1,40 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View file

@ -1,15 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View file

@ -1,28 +0,0 @@
.App {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 80px;
}
.App-header {
background-color: #222;
height: 150px;
padding: 20px;
color: white;
}
.App-title {
font-size: 1.5em;
}
.App-intro {
font-size: large;
}
@keyframes App-logo-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

View file

@ -1,94 +0,0 @@
import React, { Component } from 'react'
// These are resolved from the root instead of from the local package.json in
// the create-react-app e2e test code.
/* eslint-disable import/no-extraneous-dependencies */
import Uppy from '@uppy/core'
import Tus from '@uppy/tus'
import GoogleDrive from '@uppy/google-drive'
import { Dashboard, DashboardModal } from '@uppy/react'
import '@uppy/core/dist/style.css'
import '@uppy/dashboard/dist/style.css'
/* eslint-enable import/no-extraneous-dependencies */
const endpoint = 'http://localhost:1080'
class App extends Component {
constructor (props) {
super(props)
this.uppy = new Uppy({ id: 'uppy1', autoProceed: true, debug: true })
.use(Tus, { endpoint: `${endpoint}/files/` })
.use(GoogleDrive, { companionUrl: 'https://companion.uppy.io' })
this.uppy2 = new Uppy({ id: 'uppy2', autoProceed: false, debug: true })
.use(Tus, { endpoint: `${endpoint}/files/` })
this.state = {
showInlineDashboard: true,
open: false,
}
this.handleModalClick = this.handleModalClick.bind(this)
}
componentWillUnmount () {
this.uppy.close()
this.uppy2.close()
}
handleModalClick () {
this.setState(({ open }) => ({
open: !open,
}))
}
render () {
const { showInlineDashboard, open } = this.state
return (
<div>
<h1>React Examples</h1>
<h2>Inline Dashboard</h2>
<div id="inline-dashboard">
<label htmlFor="inline-dashboard-toggle">
<input
id="inline-dashboard-toggle"
type="checkbox"
checked={showInlineDashboard}
onChange={(event) => {
this.setState({
showInlineDashboard: event.target.checked,
})
}}
/>
Show Dashboard
</label>
{showInlineDashboard && (
<Dashboard
uppy={this.uppy}
plugins={['GoogleDrive']}
metaFields={[
{ id: 'name', name: 'Name', placeholder: 'File name' },
]}
/>
)}
</div>
<h2>Modal Dashboard</h2>
<div id="modal-dashboard">
<button onClick={this.handleModalClick} id="modal-dashboard-toggle" type="button">
{open ? 'Close dashboard' : 'Open dashboard'}
</button>
<DashboardModal
uppy={this.uppy2}
open={open}
target="#modal-dashboard"
onRequestClose={() => this.setState({ open: false })}
/>
</div>
</div>
)
}
}
export default App

View file

@ -1,5 +0,0 @@
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}

View file

@ -1,11 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
const errors = []
window.onerror = (err) => {
errors.push(err)
}
window.errors = errors
ReactDOM.render(<App />, document.getElementById('root'))

View file

@ -1,7 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -1,99 +0,0 @@
/* global capabilities, browser, $, $$ */
const testURL = 'http://localhost:4567/create-react-app'
describe('webpack build', () => {
beforeEach(async () => {
await browser.url(testURL)
})
it('should include CSS', async () => {
const el = await $('#inline-dashboard .uppy-Dashboard-inner')
await el.waitForExist()
const bgColor = await el.getCSSProperty('background-color')
// computed value is rgb() or rgba(), not hex (but listing it here to show the expected value too)
expect(/^rgba?\(250, ?250, ?250(?:, ?1)?\)$|^#fafafa$/.test(bgColor.value)).to.equal(true)
})
})
describe('React: Dashboard', () => {
beforeEach(async () => {
await browser.url(testURL)
})
it('should have Google Drive panel', async () => {
const el = await $('#inline-dashboard .uppy-Dashboard-inner')
await el.waitForExist()
const tabs = await $$('.uppy-DashboardTab-name')
let hasGDrive = false
for (const name of tabs) {
hasGDrive = (await name.getText()) === 'Google Drive'
if (hasGDrive) break
}
expect(hasGDrive).to.equal(true)
})
it('should survive being mounted and unmounted', async () => {
const el = await $('#inline-dashboard .uppy-Dashboard-inner')
await el.waitForExist()
async function toggle () {
const button = await $('#inline-dashboard-toggle')
await button.click()
await browser.pause(250)
}
// close
await toggle()
// open
await toggle()
// close
await toggle()
// open
await toggle()
// open GDrive panel
const gdriveButton = await $('.uppy-DashboardTab[data-uppy-acquirer-id="GoogleDrive"] button')
await gdriveButton.click()
await browser.pause(500)
// side effecting property access, not a function!
// eslint-disable-next-line no-unused-expressions
expect(await $('.uppy-Provider-authBtn')).to.exist
})
})
describe('React: DashboardModal', () => {
beforeEach(async () => {
await browser.url(testURL)
})
it('should have controlled open and close', async function test () {
// Does not work on Safari 13.1 right now
if (capabilities.browserName === 'safari' && capabilities.browserVersion === '13.1') {
this.skip()
return
}
const modalToggle = await $('#modal-dashboard-toggle')
const modalWrapper = await $('#modal-dashboard .uppy-Dashboard--modal')
const modalClose = await $('#modal-dashboard .uppy-Dashboard-close')
await modalToggle.waitForExist()
expect(await modalWrapper.getAttribute('aria-hidden')).to.equal('true')
await modalToggle.click()
await browser.pause(50) // wait for the animation to start
// Edge appears to report empty string while others report null
expect(await modalWrapper.getAttribute('aria-hidden')).to.be.oneOf([null, 'false'])
await browser.pause(500) // wait for the animation to complete
await modalClose.click()
await browser.pause(500) // wait for the animation to complete
expect(await modalWrapper.getAttribute('aria-hidden')).to.equal('true')
})
})

View file

@ -1,31 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Uppy test page</title>
</head>
<body>
<style>
main {
max-width: 700px;
margin: auto;
}
#uppyi18n-progress {
position: relative;
}
</style>
<main>
<h2>Uppy DragDrop i18n + XHRUpload</h2>
<div>
<div id="uppyi18n"></div>
<div id="uppyi18n-progress"></div>
</div>
</main>
<link href="uppy.min.css" rel="stylesheet">
<script src="bundle.js"></script>
</body>
</html>

View file

@ -1,26 +0,0 @@
import Uppy from '@uppy/core'
import DragDrop from '@uppy/drag-drop'
import XHRUpload from '@uppy/xhr-upload'
import ProgressBar from '@uppy/progress-bar'
import DeepFrozenStore from '../../resources/DeepFrozenStore.js'
const uppyi18n = new Uppy({
id: 'uppyi18n',
debug: true,
autoProceed: true,
store: DeepFrozenStore(),
})
uppyi18n
.use(DragDrop, {
target: '#uppyi18n',
locale: {
strings: {
dropHereOr: 'Перенесите файлы сюда или %{browse}',
browse: 'выберите',
},
},
})
.use(ProgressBar, { target: '#uppyi18n-progress' })
.use(XHRUpload, { endpoint: 'https://xhr-server.herokuapp.com/upload' })

View file

@ -1,35 +0,0 @@
/* global browser */
// const path = require('path')
const { selectFakeFile, ensureInputVisible } = require('../utils')
const testURL = 'http://localhost:4567/i18n-drag-drop'
describe('File upload with DragDrop + XHRUpload, i18n translated string', function test () {
this.retries(2)
beforeEach(async () => {
await browser.url(testURL)
await browser.execute(ensureInputVisible, '#uppyi18n .uppy-DragDrop-input')
})
it('should upload a file with XHRUpload and set progressbar to 100%', async () => {
// const testImage = path.join(__dirname, '../../resources/image.jpg')
// if (supportsChooseFile(capabilities)) {
// const input = await browser.$('#uppyi18n .uppy-DragDrop-input')
// await input.setValue(testImage)
// } else {
// await browser.execute(selectFakeFile, 'uppyi18n')
// }
await browser.execute(selectFakeFile, 'uppyi18n')
await browser.pause(5000)
const percent = await browser.$('#uppyi18n-progress .uppy-ProgressBar-percentage')
const html = await percent.getHTML(false)
expect(Number(html)).to.be.equal(100)
})
it('should translate text strings into Russian', async () => {
const label = await browser.$('#uppyi18n .uppy-DragDrop-label')
const text = await label.getText()
expect(text.trim()).to.be.equal('Перенесите файлы сюда или выберите')
})
})

View file

@ -1,32 +0,0 @@
{
"name": "@uppy-tests/end2end",
"version": "0.0.0",
"private": true,
"devDependencies": {
"@wdio/cli": "^7.7.7",
"@wdio/local-runner": "^7.7.7",
"@wdio/mocha-framework": "^7.7.7",
"@wdio/sauce-service": "^7.7.7",
"brake": "^1.0.1",
"chai": "^4.2.0",
"esbuild": "^0.14.1",
"express": "^4.17.1",
"glob": "^7.2.0",
"http-proxy": "^1.18.1",
"minimist": "^1.2.5",
"tus-node-server": "^0.3.2",
"webdriverio": "^7.7.7"
},
"engines": {
"node": "^14.17.0 || >=16.0.0"
},
"scripts": {
"test:endtoend:build-ci": "cd ../.. && bash ./bin/endtoend-build-ci",
"test:endtoend:build": "cd ../.. && bash ./bin/endtoend-build",
"test:endtoend:local": "yarn run test:endtoend:build && wdio wdio.local.conf.js",
"test:endtoend:providers": "yarn run test:endtoend:build && . ./providers/env.sh && wdio wdio.local.conf.js --spec providers/provider.*.test.js",
"test:endtoend:prepare-ci": "npm-run-all --parallel --race test:endtoend:registry test:endtoend:build-ci",
"test:endtoend:registry": "verdaccio --listen 4002 --config verdaccio.yaml",
"test:endtoend": "yarn run test:endtoend:prepare-ci && wdio wdio.remote.conf.js"
}
}

View file

@ -1,4 +0,0 @@
export UPPY_INSTAGRAM_USERNAME="***"
export UPPY_INSTAGRAM_PASSWORD="***"
export UPPY_GOOGLE_EMAIL="***"
export UPPY_GOOGLE_PASSWORD="****"

View file

@ -1,49 +0,0 @@
exports.finishUploadTest = async (browser) => {
// switch back to uppy tab
await browser.switchWindow(/localhost/)
const fileItem = await browser.$('.uppy-ProviderBrowser-list li.uppy-ProviderBrowserItem:last-child button')
await fileItem.waitForDisplayed()
await fileItem.click()
const uploadButton = await browser.$('.uppy-ProviderBrowser-footer .uppy-u-reset.uppy-c-btn.uppy-c-btn-primary')
await uploadButton.click()
const completeBar = await browser.$('.uppy-StatusBar-content[title="Complete"]')
await completeBar.waitForDisplayed(20000)
}
exports.startUploadTest = async (browser, providerName, tabMatch) => {
const providerButton = await browser.$(
`.uppy-DashboardTab-btn[aria-controls=uppy-DashboardContent-panel--${providerName}]`,
)
await providerButton.click()
await browser.pause(2000)
const authButton = await browser.$('.uppy-Provider-authBtn')
await authButton.waitForDisplayed()
await authButton.click()
await browser.pause(5000)
// move control to provider oauth tab
await browser.switchWindow(tabMatch)
}
exports.uploadWithRetry = async (browser, providerName, testURL) => {
await browser.url(`${testURL}?socketerr=true`)
const providerButton = await browser.$(
`.uppy-DashboardTab-btn[aria-controls=uppy-DashboardContent-panel--${providerName}]`,
)
await providerButton.click()
await browser.pause(2000)
const fileItem = await browser.$('.uppy-ProviderBrowser-list li.uppy-ProviderBrowserItem:last-child button')
await fileItem.waitForDisplayed()
await fileItem.click()
const uploadButton = await browser.$('.uppy-ProviderBrowser-footer .uppy-u-reset.uppy-c-btn.uppy-c-btn-primary')
await uploadButton.click()
const retryButton = await browser.$('.uppy-StatusBar-actionBtn--retry')
await retryButton.waitForDisplayed(10000)
await retryButton.click()
const completeBar = await browser.$('.uppy-StatusBar-content[title="Complete"]')
await completeBar.waitForDisplayed(20000)
}

View file

@ -1,28 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Uppy test page</title>
</head>
<body>
<style>
main {
max-width: 700px;
margin: auto;
}
</style>
<main>
<h2>Uppy Providers</h2>
<div>
<div id="uppyDashboard"></div>
</div>
</main>
<link href="uppy.min.css" rel="stylesheet">
<script src="bundle.js"></script>
</body>
</html>

View file

@ -1,35 +0,0 @@
import Uppy from '@uppy/core'
import Dashboard from '@uppy/dashboard'
import GoogleDrive from '@uppy/google-drive'
import Instagram from '@uppy/instagram'
import Dropbox from '@uppy/dropbox'
import Box from '@uppy/box'
import Tus from '@uppy/tus'
const companionUrl = 'http://localhost:3020'
window.uppy = new Uppy({
id: 'uppyProvider',
debug: true,
autoProceed: true,
})
.use(Dashboard, {
target: '#uppyDashboard',
inline: true,
})
.use(GoogleDrive, { target: Dashboard, companionUrl })
.use(Instagram, { target: Dashboard, companionUrl })
.use(Dropbox, { target: Dashboard, companionUrl })
.use(Box, { target: Dashboard, companionUrl })
.use(Tus, { endpoint: 'https://tusd.tusdemo.net/files/' })
if (window.location.search === '?socketerr=true') {
const emitError = (file, data) => {
// trigger fake socket error
data.uploader.uploaderSockets[file.id].emit(
'error', { error: { message: 'nobody likes me, thats ok' } },
)
window.uppy.off('upload-progress', emitError)
}
window.uppy.on('upload-progress', emitError)
}

View file

@ -1,90 +0,0 @@
/* global browser */
/*
WARNING! PLEASE READ THIS BEFORE ENABLING THIS TEST ON TRAVIS.
Before enabling this test on travis, please keep in mind that with this "no_ssl_bump_domains" option set
here https://github.com/transloadit/uppy/blob/998c7b1805acb8d305a562dd9726ebae98575e07/.travis.yml#L33
SSL encryption may not be enabled between the running companion and the testing browser client.
Hence, provider tokens (Google, Instagram, Dropbox) may be at risk of getting hijacked.
*/
const { finishUploadTest, startUploadTest, uploadWithRetry } = require('./helper')
const testURL = 'http://localhost:4567/providers'
describe('File upload with Dropbox Provider', () => {
beforeEach(async () => {
await browser.url(testURL)
})
// not using arrow functions as cb so to keep mocha in the 'this' context
it('should upload a file completely with Dropbox', async function test () {
if (!process.env.UPPY_GOOGLE_EMAIL) {
console.log('skipping Dropbox integration test')
return this.skip()
}
// ensure session is cleared
await startUploadTest(browser, 'Dropbox', /dropbox/)
// do oauth authentication
const authButton = await browser.$('button.auth-google')
await authButton.waitForDisplayed()
await authButton.click()
await browser.pause(3000)
// we login with google to avoid captcha
await signIntoGoogle(browser)
await browser.pause(5000)
// if we dropbox displays a warning about trusting the companion app
// we allow it because we trust companion. Companion is our friend.
const acceptWarning = await browser.$('#warning-button-continue')
if (await acceptWarning.isExisting()) {
await acceptWarning.click()
}
await browser.pause(3000)
// finish oauth
const allowAccessButton = await browser.$('button[name=allow_access]')
await allowAccessButton.waitForDisplayed()
await allowAccessButton.click()
await finishUploadTest(browser)
})
// not using arrow functions as cb so to keep mocha in the 'this' context
it('should resume uploads when retry is triggered with Dropbox', async function test () {
if (!process.env.UPPY_GOOGLE_EMAIL) {
console.log('skipping Dropbox integration test')
return this.skip()
}
await uploadWithRetry(browser, 'Dropbox', testURL)
})
})
const signIntoGoogle = async (browser) => {
const emailInput = await browser.$('#identifierId')
await emailInput.waitForExist(30000)
await emailInput.setValue(process.env.UPPY_GOOGLE_EMAIL)
let nextButton = await browser.$('#identifierNext')
await nextButton.click()
const passwordInput = await browser.$('input[name=password]')
await passwordInput.waitForDisplayed(30000)
await passwordInput.setValue(process.env.UPPY_GOOGLE_PASSWORD)
nextButton = await browser.$('#passwordNext')
await nextButton.click()
await browser.pause(3000)
const emailListItem = await browser.$(`li div[data-identifier="${process.env.UPPY_GOOGLE_EMAIL}"]`)
if (await emailListItem.isExisting()) {
// if user is already signed in, just select user
await emailListItem.click()
}
const allowDropboxButton = await browser.$('#submit_approve_access')
if (await allowDropboxButton.isExisting()) {
// if dropbox has never been allowed, allow it
await allowDropboxButton.click()
}
}

View file

@ -1,57 +0,0 @@
/* global browser */
/*
WARNING! PLEASE READ THIS BEFORE ENABLING THIS TEST ON TRAVIS.
Before enabling this test on travis, please keep in mind that with this "no_ssl_bump_domains" option set
here https://github.com/transloadit/uppy/blob/998c7b1805acb8d305a562dd9726ebae98575e07/.travis.yml#L33
SSL encryption may not be enabled between the running companion and the testing browser client.
Hence, provider tokens (Google, Instagram, Dropbox) may be at risk of getting hijacked.
*/
const { finishUploadTest, startUploadTest, uploadWithRetry } = require('./helper')
const testURL = 'http://localhost:4567/providers'
describe('File upload with Google Drive Provider', () => {
beforeEach(async () => {
await browser.url(testURL)
})
// not using arrow functions as cb so to keep mocha in the 'this' context
it('should upload a file completely with Google Drive', async function test () {
if (!process.env.UPPY_GOOGLE_EMAIL) {
console.log('skipping Google Drive integration test')
return this.skip()
}
// ensure session is cleared
await startUploadTest(browser, 'GoogleDrive', /google/)
await signIntoGoogle(browser)
await finishUploadTest(browser)
})
// not using arrow functions as cb so to keep mocha in the 'this' context
it('should resume uploads when retry is triggered with Google Drive', async function test () {
if (!process.env.UPPY_GOOGLE_EMAIL) {
console.log('skipping Google Drive integration test')
return this.skip()
}
await uploadWithRetry(browser, 'GoogleDrive', testURL)
})
})
const signIntoGoogle = async (browser) => {
const emailInput = await browser.$('#identifierId')
await emailInput.waitForExist(30000)
await emailInput.setValue(process.env.UPPY_GOOGLE_EMAIL)
let nextButton = await browser.$('#identifierNext')
await nextButton.click()
const passwordInput = await browser.$('input[name=password]')
await passwordInput.waitForDisplayed(30000)
await passwordInput.setValue(process.env.UPPY_GOOGLE_PASSWORD)
nextButton = await browser.$('#passwordNext')
await nextButton.click()
}

View file

@ -1,78 +0,0 @@
/* global browser */
/*
WARNING! PLEASE READ THIS BEFORE ENABLING THIS TEST ON TRAVIS.
Before enabling this test on travis, please keep in mind that with this "no_ssl_bump_domains" option set
here https://github.com/transloadit/uppy/blob/998c7b1805acb8d305a562dd9726ebae98575e07/.travis.yml#L33
SSL encryption may not be enabled between the running companion and the testing browser client.
Hence, provider tokens (Google, Instagram, Dropbox) may be at risk of getting hijacked.
*/
const { finishUploadTest, startUploadTest, uploadWithRetry } = require('./helper')
const testURL = 'http://localhost:4567/providers'
describe('File upload with Instagram Provider', () => {
beforeEach(async () => {
await browser.url(testURL)
})
// not using arrow functions as cb so to keep mocha in the 'this' context
it('should upload a file completely with Instagram', async function test () {
if (!process.env.UPPY_INSTAGRAM_USERNAME) {
console.log('skipping Instagram integration test')
return this.skip()
}
// ensure session is cleared
await startUploadTest(browser, 'Instagram', /instagram/)
// do oauth authentication
const usernameInput = await browser.$('input[name=username]')
await usernameInput.waitForExist(20000)
await usernameInput.setValue(process.env.UPPY_INSTAGRAM_USERNAME)
const passwordInput = await browser.$('input[name=password]')
await passwordInput.setValue(process.env.UPPY_INSTAGRAM_PASSWORD)
const submit = await browser.$('form button[type=submit]')
await submit.click()
// wait a bit for submission
await browser.pause(5000)
if ((await browser.getWindowHandles()).length > 1) {
// if suspicious login was detected, the window will remain unclosed
// so we have to input the security code sent
const challengeChoice = await browser.$('input[name="choice"]')
if (await challengeChoice.isExisting()) {
const acceptChallengButton = await browser.$('form button')
await acceptChallengButton.click()
const securityCodeInput = await browser.$('input[name=security_code]')
await securityCodeInput.waitForExist()
// we can't automate the submission of security code
// because it is sent to the email. So we wait till it is filled manually
await securityCodeInput.waitUntil(
() => securityCodeInput.getValue(),
30_000,
'expected security code to be manually entered',
)
}
// instagram may ask for auth confirmation to allow companion
const allowButton = await browser.$('button[value="Authorize"]')
if (await allowButton.isExisting()) {
await allowButton.click()
}
}
await finishUploadTest(browser)
})
// not using arrow functions as cb so to keep mocha in the 'this' context
it('should resume uploads when retry is triggered Instagram', async function test () {
if (!process.env.UPPY_INSTAGRAM_USERNAME) {
console.log('skipping Instagram integration test')
return this.skip()
}
await uploadWithRetry(browser, 'Instagram', testURL)
})
})

View file

@ -1,25 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Uppy test page</title>
</head>
<body>
<style>
main {
max-width: 700px;
margin: auto;
}
</style>
<main>
<h2>Thumbnails</h2>
<div>
<div id="uppyThumbnails"></div>
</div>
</main>
<link href="uppy.min.css" rel="stylesheet">
<script src="bundle.js"></script>
</body>
</html>

View file

@ -1,36 +0,0 @@
import Uppy from '@uppy/core'
import ThumbnailGenerator from '@uppy/thumbnail-generator'
import FileInput from '@uppy/file-input'
const uppyThumbnails = new Uppy({
id: 'uppyThumbnails',
autoProceed: false,
debug: true,
})
uppyThumbnails.use(ThumbnailGenerator, {})
uppyThumbnails.use(FileInput, { target: '#uppyThumbnails', pretty: false })
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) => {
const img = new Image()
img.src = file.preview
img.className = 'file-preview'
img.style.display = 'block'
document.body.appendChild(img)
})

View file

@ -1,102 +0,0 @@
/* global browser, capabilities, $, $$ */
const path = require('path')
const fs = require('fs')
const { selectFakeFile, supportsChooseFile } = require('../utils')
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/invalid.png'),
]
const notImages = [
{ type: 'text/javascript', file: __filename },
]
describe('ThumbnailGenerator', () => {
beforeEach(async () => {
await browser.url(testURL)
})
it('should generate thumbnails for images', async function test () {
// Does not work on IE right now
if (capabilities.browserName === 'internet explorer') {
this.skip()
return
}
const input = await $('#uppyThumbnails .uppy-FileInput-input')
await input.waitForExist()
await browser.execute(/* must be valid ES5 for IE */ () => {
window.thumbnailsReady = new Promise((resolve) => {
window.uppyThumbnails.on('thumbnail:all-generated', resolve)
})
})
if (supportsChooseFile()) {
for (const file of images) {
await input.setValue(file)
}
for (const { file } of notImages) {
await input.setValue(file)
}
} else {
for (const img of images) {
await browser.execute(
selectFakeFile,
'uppyThumbnails',
path.basename(img), // name
`image/${path.extname(img).slice(1)}`, // type
fs.readFileSync(img, 'base64'), // b64
)
}
for (const { type, file } of notImages) {
await browser.execute(
selectFakeFile,
'uppyThumbnails',
path.basename(file), // name
type, // type
fs.readFileSync(file, 'base64'), // b64
)
}
}
await browser.executeAsync(/* must be valid ES5 for IE */ (done) => {
window.thumbnailsReady.then(done)
})
// const names = $$('p.file-name')
const previews = await $$('img.file-preview')
// Names should all be listed before previews--indicates that previews were generated asynchronously.
/* Nevermind this, setValue() 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')
}
}
*/
expect(previews).to.have.lengthOf(3) // ex. the invalid image
for (const p of previews) {
expect(await p.getAttribute('src')).to.match(/^blob:/)
expect(await getWidth(p)).to.equal(200)
}
})
})
async function getWidth (ref) {
try {
return await ref.getSize('width')
} catch (err) {
return browser.execute((el) => {
return el.getBoundingClientRect().width
}, ref)
}
}

View file

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Uppy test page</title>
</head>
<body>
<style>
main {
max-width: 700px;
margin: auto;
}
</style>
<main>
<h2>Uppy Transloadit</h2>
<div id="uppy-transloadit"></div>
</main>
<link href="uppy.min.css" rel="stylesheet">
<script src="bundle.js"></script>
</body>
</html>

View file

@ -1,50 +0,0 @@
import Uppy from '@uppy/core'
import Dashboard from '@uppy/dashboard'
import Transloadit from '@uppy/transloadit'
function initUppyTransloadit (transloaditKey) {
const uppyTransloadit = new Uppy({
id: 'uppyTransloadit',
debug: true,
autoProceed: true,
})
uppyTransloadit
.use(Dashboard, {
target: '#uppy-transloadit',
inline: true,
})
.use(Transloadit, {
service: 'https://api2-ap-southeast-1.transloadit.com',
getAssemblyOptions () {
return {
params: {
auth: { key: transloaditKey },
steps: {
crop_thumbed: {
use: [':original'],
robot: '/image/resize',
height: 100,
resize_strategy: 'crop',
width: 100,
},
},
},
fields: {
message: 'test',
},
}
},
waitForEncoding: true,
})
uppyTransloadit.on('transloadit:result', (_, __, assembly) => {
// use transloadit encoding result here.
const result = document.createElement('div')
result.setAttribute('id', 'uppy-result')
result.textContent = assembly.fields.message === 'test' ? 'ok' : 'fail'
document.body.appendChild(result)
})
}
window.initUppyTransloadit = initUppyTransloadit

View file

@ -1,51 +0,0 @@
/* global browser, capabilities, $ */
const path = require('path')
const fs = require('fs')
const { selectFakeFile, supportsChooseFile, ensureInputVisible } = require('../utils')
const testURL = 'http://localhost:4567/transloadit-assembly-options'
function setTransloaditKeyAndInit (transloaditKey) {
window.initUppyTransloadit(transloaditKey)
}
describe('Transloadit getAssemblyOptions', () => {
beforeEach(async () => {
await browser.url(testURL)
})
it('should pass `fields` to `assembly:result`', async function test () {
const transloaditKey = process.env.TRANSLOADIT_KEY
if (transloaditKey === undefined) {
console.log('skipping Transloadit integration test: process.env.TRANSLOADIT_KEY is not set')
return this.skip()
}
const wrapper = await $('#uppy-transloadit')
await wrapper.waitForExist()
await browser.execute(setTransloaditKeyAndInit, transloaditKey)
const input = await $('#uppy-transloadit .uppy-Dashboard-input')
const result = await $('#uppy-result')
await input.waitForExist()
await browser.execute(ensureInputVisible, '#uppy-transloadit .uppy-Dashboard-input')
if (supportsChooseFile(capabilities)) {
await input.setValue(path.join(__dirname, '../../resources/image.jpg'))
} else {
const img = path.join(__dirname, '../../resources/image.jpg')
await browser.execute(
selectFakeFile,
'uppyTransloadit',
path.basename(img), // name
'image/jpeg', // type
fs.readFileSync(img, 'base64'), // b64
)
}
await result.waitForExist({ timeout: 25000 })
const text = await result.getText()
expect(text).to.be.equal('ok')
})
})

View file

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Uppy test page</title>
</head>
<body>
<style>
main {
max-width: 700px;
margin: auto;
}
</style>
<main>
<h2>Uppy Transloadit</h2>
<div id="uppy-transloadit"></div>
</main>
<link href="uppy.min.css" rel="stylesheet">
<script src="bundle.js"></script>
</body>
</html>

View file

@ -1,47 +0,0 @@
import Uppy from '@uppy/core'
import Dashboard from '@uppy/dashboard'
import Transloadit from '@uppy/transloadit'
function initUppyTransloadit (transloaditKey) {
const uppyTransloadit = new Uppy({
id: 'uppyTransloadit',
debug: true,
autoProceed: true,
})
uppyTransloadit
.use(Dashboard, {
target: '#uppy-transloadit',
inline: true,
})
.use(Transloadit, {
service: 'https://api2-ap-southeast-1.transloadit.com',
params: {
auth: { key: transloaditKey },
steps: {
crop_thumbed: {
use: [':original'],
robot: '/image/resize',
height: 100,
resize_strategy: 'crop',
width: 100,
},
},
},
waitForEncoding: true,
})
uppyTransloadit.on('transloadit:result', (stepName, result) => {
// use transloadit encoding result here.
const img = new Image()
img.onload = function onload () {
const resultDiv = document.createElement('div')
resultDiv.setAttribute('id', 'uppy-result')
resultDiv.textContent = 'ok'
document.body.appendChild(resultDiv)
}
img.src = result.url
})
}
window.initUppyTransloadit = initUppyTransloadit

View file

@ -1,51 +0,0 @@
/* global browser, capabilities, $ */
const path = require('path')
const fs = require('fs')
const { selectFakeFile, supportsChooseFile, ensureInputVisible } = require('../utils')
const testURL = 'http://localhost:4567/transloadit'
function setTransloaditKeyAndInit (transloaditKey) {
window.initUppyTransloadit(transloaditKey)
}
describe('Transloadit file processing', () => {
beforeEach(async () => {
await browser.url(testURL)
})
it('should upload a file to Transloadit and crop it', async function test () {
const transloaditKey = process.env.TRANSLOADIT_KEY
if (transloaditKey === undefined) {
console.log('skipping Transloadit integration test: process.env.TRANSLOADIT_KEY is not set')
return this.skip()
}
const wrapper = await $('#uppy-transloadit')
await wrapper.waitForExist()
await browser.execute(setTransloaditKeyAndInit, transloaditKey)
const input = await $('#uppy-transloadit .uppy-Dashboard-input')
const result = await $('#uppy-result')
await input.waitForExist()
await browser.execute(ensureInputVisible, '#uppy-transloadit .uppy-Dashboard-input')
if (supportsChooseFile(capabilities)) {
await input.setValue(path.join(__dirname, '../../resources/image.jpg'))
} else {
const img = path.join(__dirname, '../../resources/image.jpg')
await browser.execute(
selectFakeFile,
'uppyTransloadit',
path.basename(img), // name
'image/jpeg', // type
fs.readFileSync(img, 'base64'), // b64
)
}
await result.waitForExist({ timeout: 25000 })
const text = await result.getText()
expect(text).to.be.equal('ok')
})
})

View file

@ -1,25 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Uppy test page</title>
</head>
<body>
<style>
main {
max-width: 700px;
margin: auto;
}
</style>
<main>
<h2>Uppy Dashboard + Tus</h2>
<div>
<div id="uppyDashboard"></div>
</div>
</main>
<link href="uppy.min.css" rel="stylesheet">
<script src="bundle.js"></script>
</body>
</html>

View file

@ -1,17 +0,0 @@
import Uppy from '@uppy/core'
import Dashboard from '@uppy/dashboard'
import Tus from '@uppy/tus'
const endpoint = 'http://localhost:1080'
const uppyDashboard = new Uppy({
id: 'uppyDashboard',
debug: true,
})
uppyDashboard
.use(Dashboard, {
target: '#uppyDashboard',
inline: true,
})
.use(Tus, { endpoint: `${endpoint}/files/` })

View file

@ -1,35 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Uppy test page</title>
</head>
<body>
<script>onerror = function (message, source, line, col, error) {
document.body.appendChild(document.createTextNode(message))
document.body.appendChild(document.createTextNode(source))
document.body.appendChild(document.createTextNode(line + ':' + col))
}</script>
<style>
main {
max-width: 700px;
margin: auto;
}
#uppyDragDrop-progress {
position: relative;
}
</style>
<main>
<h2>Uppy DragDrop + Tus</h2>
<div>
<div id="uppyDragDrop"></div>
<div id="uppyDragDrop-progress"></div>
</div>
</main>
<link href="uppy.min.css" rel="stylesheet">
<script src="bundle.js"></script>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show more