Migrate from Cypress to Vitest Browser Mode (#5828)
- Remove `e2e` folder entirely - Remove all hacky resolutions and yarn patches - Remove `@types/jasmine`, `js2ts` (convert a JS file to TS), and `vue-template-compiler` from `private/` - Remove e2e CI job - Add browsers tests for vue, svelte, and react headless components and hooks. - Add new (browser) tests for transloadit, aws-s3, and dashboard. - Remove final useless scripts from `package.json`, use direct references in CI. - Fix Dropzone component accessibility discovered during testing - Clean up github workflows (move linters.yml into ci.yml, update e2e.yml) **Why Vitest Browser Mode?** We could have used playwright but vitest browser mode uses it under the hood and we get the use the vitest we know a love. No two entirely different setups, no different assertions to relearn, write e2e tests as if you're writing unit tests. Easy, fast, beautiful. https://vitest.dev/guide/browser/ **Has every single e2e test been rewritten?** No there were quite a few tests that have a lot overlap with existing or newly added tests. There were also some tests that were so heavily mocked inside and out you start to wonder what the value still is. Open to discuss which tests still need to be added.
36
.github/workflows/ci.yml
vendored
|
|
@ -30,7 +30,7 @@ env:
|
|||
|
||||
jobs:
|
||||
unit_tests:
|
||||
name: Unit tests
|
||||
name: Tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
|
|
@ -57,16 +57,20 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run:
|
||||
corepack yarn install
|
||||
env:
|
||||
# https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
- name: Install Playwright Browsers
|
||||
run: corepack yarn dlx playwright install --with-deps
|
||||
- name: Build
|
||||
run: corepack yarn run build
|
||||
- name: Run tests
|
||||
run: corepack yarn run test
|
||||
env:
|
||||
COMPANION_DATADIR: ./output
|
||||
COMPANION_DOMAIN: localhost:3020
|
||||
COMPANION_PROTOCOL: http
|
||||
COMPANION_REDIS_URL: redis://localhost:6379
|
||||
|
||||
types:
|
||||
name: Type tests
|
||||
name: Types
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
|
|
@ -90,7 +94,23 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run:
|
||||
corepack yarn install
|
||||
env:
|
||||
# https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
- run: corepack yarn run typecheck
|
||||
|
||||
lint_js:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SKIP_YARN_COREPACK_CHECK: true
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
run:
|
||||
corepack yarn workspaces focus @uppy-dev/build
|
||||
- name: Run linter
|
||||
run: corepack yarn run check:ci
|
||||
|
|
|
|||
52
.github/workflows/companion.yml
vendored
|
|
@ -1,52 +0,0 @@
|
|||
name: Companion
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- yarn.lock
|
||||
- 'packages/@uppy/companion/**'
|
||||
- '.github/workflows/companion.yml'
|
||||
pull_request:
|
||||
# We want all branches so we configure types to be the GH default again
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- yarn.lock
|
||||
- 'packages/@uppy/companion/**'
|
||||
- '.github/workflows/companion.yml'
|
||||
|
||||
env:
|
||||
YARN_ENABLE_GLOBAL_CACHE: false
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Unit tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
# fix node versions so we don't get sudden unrelated CI breakage
|
||||
node-version: [18.20.8, 20.19.1]
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{matrix.node-version}}
|
||||
- name: Install dependencies
|
||||
run: corepack yarn workspaces focus @uppy/companion
|
||||
- name: Run tests
|
||||
run: corepack yarn workspace @uppy/companion test
|
||||
- name: Run type checks in focused workspace
|
||||
run: corepack yarn workspace @uppy/companion typecheck
|
||||
112
.github/workflows/e2e.yml
vendored
|
|
@ -1,12 +1,5 @@
|
|||
name: End-to-end tests
|
||||
# SECURITY NOTE: This workflow uses pull_request_target which has access to secrets.
|
||||
# This is needed because we want to be able to run e2e tests on someone else's code, but with our own credentials (which is needed for the tests).
|
||||
# `pull_request_target` will always run without manual approval, even if "Require approval for all external contributors" is enabled in the repo settings.
|
||||
# Therefore we implement a "safe to test" label that must be manually added once we have checked that the diff is safe.
|
||||
# Example of how this could be exploited before we implemented this label: https://github.com/transloadit/uppy/pull/5798/commits/d214a893355bd72ae771fb1c52ebe87948a98440
|
||||
# For PRs from forks, secrets are only provided when the "safe to test" label is present.
|
||||
# This allows maintainers to safely test external contributions while preventing
|
||||
# malicious actors from accessing secrets.
|
||||
name: Output
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
|
@ -18,8 +11,8 @@ on:
|
|||
- 'website/**'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/e2e.yml'
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.d.ts'
|
||||
|
|
@ -27,10 +20,6 @@ on:
|
|||
- 'private/**'
|
||||
- 'website/**'
|
||||
- '.github/**'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- .github/workflows/e2e.yml
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
|
@ -39,12 +28,8 @@ env:
|
|||
|
||||
jobs:
|
||||
compare_diff:
|
||||
name: Diff lib folders
|
||||
runs-on: ubuntu-latest
|
||||
# Apply same security condition as e2e job
|
||||
if: |
|
||||
github.event_name == 'push' ||
|
||||
github.event.pull_request.head.repo.full_name == github.repository ||
|
||||
contains(github.event.pull_request.labels.*.name, 'safe to test')
|
||||
env:
|
||||
DIFF_BUILDER: true
|
||||
outputs:
|
||||
|
|
@ -54,9 +39,7 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
ref:
|
||||
${{ github.event.pull_request && format('refs/pull/{0}/merge',
|
||||
github.event.pull_request.number) || github.sha }}
|
||||
ref: ${{ github.event.pull_request && format('refs/pull/{0}/merge', github.event.pull_request.number) || github.sha }}
|
||||
- run: git reset HEAD^ --hard
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
|
|
@ -130,86 +113,3 @@ jobs:
|
|||
```
|
||||
|
||||
</details>
|
||||
|
||||
e2e:
|
||||
name: Browser tests
|
||||
runs-on: ubuntu-latest
|
||||
# Only run E2E tests with secrets if:
|
||||
# 1. PR is from the same repository (trusted), OR
|
||||
# 2. PR has the "safe to test" label (maintainer approved)
|
||||
if: |
|
||||
github.event_name == 'push' ||
|
||||
github.event.pull_request.head.repo.full_name == github.repository ||
|
||||
contains(github.event.pull_request.labels.*.name, 'safe to test')
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Create cache folder for Cypress
|
||||
id: cypress-cache-dir-path
|
||||
run: echo "dir=$(mktemp -d)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.cypress-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-cypress
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Start Redis
|
||||
uses: supercharge/redis-github-action@ea9b21c6ecece47bd99595c532e481390ea0f044 # 1.8.0
|
||||
with:
|
||||
redis-version: 7
|
||||
|
||||
- name: Install dependencies
|
||||
run: corepack yarn install --immutable
|
||||
env:
|
||||
# https://docs.cypress.io/guides/references/advanced-installation#Binary-cache
|
||||
CYPRESS_CACHE_FOLDER: ${{ steps.cypress-cache-dir-path.outputs.dir }}
|
||||
- name: Build Uppy packages
|
||||
run: corepack yarn build
|
||||
- name: Run end-to-end browser tests
|
||||
run: corepack yarn run e2e:ci
|
||||
env:
|
||||
COMPANION_DATADIR: ./output
|
||||
COMPANION_DOMAIN: localhost:3020
|
||||
COMPANION_PROTOCOL: http
|
||||
COMPANION_REDIS_URL: redis://localhost:6379
|
||||
# Secrets are safe to provide here because job-level condition ensures
|
||||
# this only runs for trusted PRs or PRs with "safe to test" label
|
||||
COMPANION_UNSPLASH_KEY: ${{ secrets.COMPANION_UNSPLASH_KEY }}
|
||||
COMPANION_UNSPLASH_SECRET: ${{ secrets.COMPANION_UNSPLASH_SECRET }}
|
||||
COMPANION_AWS_KEY: ${{ secrets.COMPANION_AWS_KEY }}
|
||||
COMPANION_AWS_SECRET: ${{ secrets.COMPANION_AWS_SECRET }}
|
||||
COMPANION_AWS_BUCKET: ${{ secrets.COMPANION_AWS_BUCKET }}
|
||||
COMPANION_AWS_REGION: ${{ secrets.COMPANION_AWS_REGION }}
|
||||
VITE_COMPANION_URL: http://localhost:3020
|
||||
VITE_TRANSLOADIT_KEY: ${{ secrets.TRANSLOADIT_KEY }}
|
||||
VITE_TRANSLOADIT_SECRET: ${{ secrets.TRANSLOADIT_SECRET }}
|
||||
VITE_TRANSLOADIT_TEMPLATE: ${{ secrets.TRANSLOADIT_TEMPLATE }}
|
||||
VITE_TRANSLOADIT_SERVICE_URL: ${{ secrets.TRANSLOADIT_SERVICE_URL }}
|
||||
# https://docs.cypress.io/guides/references/advanced-installation#Binary-cache
|
||||
CYPRESS_CACHE_FOLDER: ${{ steps.cypress-cache-dir-path.outputs.dir }}
|
||||
- name: Upload videos in case of failure
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: videos-and-screenshots
|
||||
path: |
|
||||
e2e/cypress/videos/
|
||||
e2e/cypress/screenshots/
|
||||
|
|
|
|||
39
.github/workflows/linters.yml
vendored
|
|
@ -1,39 +0,0 @@
|
|||
name: Linters
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
- '!.github/workflows/linters.yml'
|
||||
- '!.github/CONTRIBUTING.md'
|
||||
pull_request:
|
||||
# We want all branches so we configure types to be the GH default again
|
||||
types: [opened, synchronize, reopened]
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
- '!.github/workflows/linters.yml'
|
||||
- '!.github/CONTRIBUTING.md'
|
||||
|
||||
env:
|
||||
YARN_ENABLE_GLOBAL_CACHE: false
|
||||
SKIP_YARN_COREPACK_CHECK: true
|
||||
|
||||
jobs:
|
||||
lint_js:
|
||||
name: Lint JavaScript/TypeScript
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run:
|
||||
corepack yarn workspaces focus @uppy-dev/build
|
||||
- name: Run linter
|
||||
run: corepack yarn run check:ci
|
||||
4
.github/workflows/manual-cdn.yml
vendored
|
|
@ -59,7 +59,7 @@ jobs:
|
|||
run: corepack yarn run build
|
||||
- name: Upload "${{ inputs.package }}" to CDN
|
||||
if: ${{ !inputs.force }}
|
||||
run: corepack yarn run uploadcdn "$PACKAGE" "$VERSION"
|
||||
run: corepack yarn workspace uppy node upload-to-cdn.js "$PACKAGE" "$VERSION"
|
||||
env:
|
||||
PACKAGE: ${{inputs.package}}
|
||||
VERSION: ${{inputs.version}}
|
||||
|
|
@ -67,7 +67,7 @@ jobs:
|
|||
EDGLY_SECRET: ${{secrets.EDGLY_SECRET}}
|
||||
- name: Upload "${{ inputs.package }}" to CDN
|
||||
if: ${{ inputs.force }}
|
||||
run: corepack yarn run uploadcdn "$PACKAGE" "$VERSION" -- --force
|
||||
run: corepack yarn workspace uppy node upload-to-cdn.js "$PACKAGE" "$VERSION" --force
|
||||
env:
|
||||
PACKAGE: ${{inputs.package}}
|
||||
VERSION: ${{inputs.version}}
|
||||
|
|
|
|||
1
.gitignore
vendored
|
|
@ -18,6 +18,7 @@ tsconfig.tsbuildinfo
|
|||
tsconfig.build.tsbuildinfo
|
||||
.svelte-kit
|
||||
.turbo
|
||||
__screenshots__
|
||||
|
||||
dist/
|
||||
lib/
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"extends": "@parcel/config-default",
|
||||
"transformers": {
|
||||
"*.{js,mjs,jsx,cjs,ts,tsx}": [
|
||||
"@parcel/transformer-js",
|
||||
"@parcel/transformer-react-refresh-wrap"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import AwsS3Multipart from '@uppy/aws-s3'
|
||||
import { Uppy } from '@uppy/core'
|
||||
import Dashboard from '@uppy/dashboard'
|
||||
|
||||
import '@uppy/core/dist/style.css'
|
||||
import '@uppy/dashboard/dist/style.css'
|
||||
|
||||
const uppy = new Uppy()
|
||||
.use(Dashboard, { target: '#app', inline: true })
|
||||
.use(AwsS3Multipart, {
|
||||
limit: 2,
|
||||
endpoint: process.env.VITE_COMPANION_URL,
|
||||
shouldUseMultipart: true,
|
||||
})
|
||||
|
||||
// Keep this here to access uppy in tests
|
||||
window.uppy = uppy
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>dashboard-aws-multipart</title>
|
||||
<script defer type="module" src="app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import AwsS3 from '@uppy/aws-s3'
|
||||
import { Uppy } from '@uppy/core'
|
||||
import Dashboard from '@uppy/dashboard'
|
||||
|
||||
import '@uppy/core/dist/style.css'
|
||||
import '@uppy/dashboard/dist/style.css'
|
||||
|
||||
const uppy = new Uppy()
|
||||
.use(Dashboard, { target: '#app', inline: true })
|
||||
.use(AwsS3, {
|
||||
limit: 2,
|
||||
endpoint: process.env.VITE_COMPANION_URL,
|
||||
shouldUseMultipart: false,
|
||||
})
|
||||
|
||||
// Keep this here to access uppy in tests
|
||||
window.uppy = uppy
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>dashboard-aws</title>
|
||||
<script defer type="module" src="app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import Compressor from '@uppy/compressor'
|
||||
import Uppy from '@uppy/core'
|
||||
import Dashboard from '@uppy/dashboard'
|
||||
|
||||
import '@uppy/core/dist/style.css'
|
||||
import '@uppy/dashboard/dist/style.css'
|
||||
|
||||
const uppy = new Uppy()
|
||||
.use(Dashboard, {
|
||||
target: document.body,
|
||||
inline: true,
|
||||
})
|
||||
.use(Compressor, {
|
||||
mimeType: 'image/webp',
|
||||
})
|
||||
|
||||
// Keep this here to access uppy in tests
|
||||
window.uppy = uppy
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>dashboard-compressor</title>
|
||||
<script defer type="module" src="app.js"></script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import { Uppy } from '@uppy/core'
|
||||
import Dashboard from '@uppy/dashboard'
|
||||
import Transloadit from '@uppy/transloadit'
|
||||
|
||||
import generateSignatureIfSecret from './generateSignatureIfSecret.js'
|
||||
|
||||
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,
|
||||
assemblyOptions: () =>
|
||||
generateSignatureIfSecret(process.env.VITE_TRANSLOADIT_SECRET, {
|
||||
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
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
const enc = new TextEncoder('utf-8')
|
||||
async function sign(secret, body) {
|
||||
const algorithm = { name: 'HMAC', hash: 'SHA-384' }
|
||||
|
||||
const key = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
enc.encode(secret),
|
||||
algorithm,
|
||||
false,
|
||||
['sign', 'verify'],
|
||||
)
|
||||
const signature = await crypto.subtle.sign(
|
||||
algorithm.name,
|
||||
key,
|
||||
enc.encode(body),
|
||||
)
|
||||
return `sha384:${Array.from(new Uint8Array(signature), (x) => x.toString(16).padStart(2, '0')).join('')}`
|
||||
}
|
||||
function getExpiration(future) {
|
||||
return new Date(Date.now() + future)
|
||||
.toISOString()
|
||||
.replace('T', ' ')
|
||||
.replace(/\.\d+Z$/, '+00:00')
|
||||
}
|
||||
/**
|
||||
* Adds an expiration date and signs the params object if a secret is passed to
|
||||
* it. If no secret is given, it returns the same object.
|
||||
*
|
||||
* @param {string | undefined} secret
|
||||
* @param {object} params
|
||||
* @returns {{ params: string, signature?: string }}
|
||||
*/
|
||||
export default async function generateSignatureIfSecret(secret, params) {
|
||||
let signature
|
||||
if (secret) {
|
||||
params.auth.expires = getExpiration(5 * 60 * 1000)
|
||||
params = JSON.stringify(params)
|
||||
signature = await sign(secret, params)
|
||||
}
|
||||
|
||||
return { params, signature }
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<!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>
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
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'
|
||||
|
||||
function onShouldRetry(err, retryAttempt, options, next) {
|
||||
if (err?.originalResponse?.getStatus() === 418) {
|
||||
return true
|
||||
}
|
||||
return next(err)
|
||||
}
|
||||
|
||||
const companionUrl = 'http://localhost:3020'
|
||||
const uppy = new Uppy()
|
||||
.use(Dashboard, { target: '#app', inline: true })
|
||||
.use(Tus, { endpoint: 'https://tusd.tusdemo.net/files', onShouldRetry })
|
||||
.use(Url, { target: Dashboard, companionUrl })
|
||||
.use(Unsplash, { target: Dashboard, companionUrl })
|
||||
|
||||
// Keep this here to access uppy in tests
|
||||
window.uppy = uppy
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<!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>
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
import Audio from '@uppy/audio'
|
||||
import Compressor from '@uppy/compressor'
|
||||
import Uppy from '@uppy/core'
|
||||
import Dashboard from '@uppy/dashboard'
|
||||
import DropTarget from '@uppy/drop-target'
|
||||
import GoldenRetriever from '@uppy/golden-retriever'
|
||||
import ImageEditor from '@uppy/image-editor'
|
||||
import RemoteSources from '@uppy/remote-sources'
|
||||
import ScreenCapture from '@uppy/screen-capture'
|
||||
import Webcam from '@uppy/webcam'
|
||||
|
||||
import '@uppy/core/dist/style.css'
|
||||
import '@uppy/dashboard/dist/style.css'
|
||||
|
||||
const COMPANION_URL = 'http://companion.uppy.io'
|
||||
|
||||
const uppy = new Uppy()
|
||||
.use(Dashboard, { target: '#app', inline: true })
|
||||
.use(RemoteSources, { companionUrl: COMPANION_URL })
|
||||
.use(Webcam, {
|
||||
target: Dashboard,
|
||||
showVideoSourceDropdown: true,
|
||||
showRecordingLength: true,
|
||||
})
|
||||
.use(Audio, {
|
||||
target: Dashboard,
|
||||
showRecordingLength: true,
|
||||
})
|
||||
.use(ScreenCapture, { target: Dashboard })
|
||||
.use(ImageEditor, { target: Dashboard })
|
||||
.use(DropTarget, { target: document.body })
|
||||
.use(Compressor)
|
||||
.use(GoldenRetriever, { serviceWorker: true })
|
||||
|
||||
// Keep this here to access uppy in tests
|
||||
window.uppy = uppy
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<!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>
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
<template>
|
||||
<UppyContextProvider :uppy="uppy">
|
||||
<main>
|
||||
<h1>Vue Headless Components Test</h1>
|
||||
|
||||
<article id="files-list">
|
||||
<h2>With list</h2>
|
||||
<Dropzone />
|
||||
<FilesList />
|
||||
<UploadButton />
|
||||
</article>
|
||||
|
||||
<article id="files-grid">
|
||||
<h2>With grid</h2>
|
||||
<Dropzone />
|
||||
<FilesGrid :columns="2" />
|
||||
<UploadButton />
|
||||
</article>
|
||||
</main>
|
||||
</UppyContextProvider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Uppy from '@uppy/core'
|
||||
import Tus from '@uppy/tus'
|
||||
import {
|
||||
Dropzone,
|
||||
FilesGrid,
|
||||
FilesList,
|
||||
UploadButton,
|
||||
UppyContextProvider,
|
||||
} from '@uppy/vue'
|
||||
import Vue from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
UppyContextProvider,
|
||||
Dropzone,
|
||||
FilesList,
|
||||
FilesGrid,
|
||||
UploadButton,
|
||||
},
|
||||
computed: {
|
||||
uppy: () =>
|
||||
new Uppy().use(Tus, {
|
||||
endpoint: 'https://tusd.tusdemo.net/files/',
|
||||
}),
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style src="@uppy/vue/dist/styles.css"></style>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<!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>
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import { Uppy } from '@uppy/core'
|
||||
import Dashboard from '@uppy/dashboard'
|
||||
import Unsplash from '@uppy/unsplash'
|
||||
import Url from '@uppy/url'
|
||||
import XHRUpload from '@uppy/xhr-upload'
|
||||
|
||||
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(XHRUpload, {
|
||||
endpoint: 'https://xhr-server.herokuapp.com/upload',
|
||||
limit: 6,
|
||||
})
|
||||
.use(Url, { target: Dashboard, companionUrl })
|
||||
.use(Unsplash, { target: Dashboard, companionUrl })
|
||||
|
||||
// Keep this here to access uppy in tests
|
||||
window.uppy = uppy
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>dashboard-xhr</title>
|
||||
<script defer type="module" src="app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
<!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-aws/index.html">dashboard-aws</a></li>
|
||||
<li>
|
||||
<a href="dashboard-aws-multipart/index.html"
|
||||
>dashboard-aws-multipart</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="dashboard-compressor/index.html">dashboard-compressor</a>
|
||||
</li>
|
||||
<li><a href="react/index.html">react</a></li>
|
||||
<li>
|
||||
<a href="dashboard-transloadit/index.html">dashboard-transloadit</a>
|
||||
</li>
|
||||
<li><a href="dashboard-tus/index.html">dashboard-tus</a></li>
|
||||
<li><a href="dashboard-xhr/index.html">dashboard-xhr</a></li>
|
||||
<li><a href="dashboard-ui/index.html">dashboard-ui</a></li>
|
||||
<li><a href="dashboard-vue/index.html">dashboard-vue</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
/** biome-ignore-all lint/nursery/useUniqueElementIds: it's fine */
|
||||
import Uppy from '@uppy/core'
|
||||
import { Dashboard, DashboardModal, DragDrop } from '@uppy/react'
|
||||
import RemoteSources from '@uppy/remote-sources'
|
||||
import ThumbnailGenerator from '@uppy/thumbnail-generator'
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import '@uppy/core/dist/style.css'
|
||||
import '@uppy/dashboard/dist/style.css'
|
||||
import '@uppy/drag-drop/dist/style.css'
|
||||
|
||||
const uppyDashboard = new Uppy({ id: 'dashboard' }).use(RemoteSources, {
|
||||
companionUrl: 'http://companion.uppy.io',
|
||||
sources: ['GoogleDrive', 'OneDrive', 'Unsplash', 'Zoom', 'Url'],
|
||||
})
|
||||
const uppyModal = new Uppy({ id: 'modal' })
|
||||
const uppyDragDrop = new Uppy({ id: 'drag-drop' }).use(ThumbnailGenerator)
|
||||
|
||||
export default function App() {
|
||||
const [open, setOpen] = useState(false)
|
||||
// TODO: Parcel is having a bad time resolving React inside @uppy/react for some reason.
|
||||
// We are using Parcel in an odd way and I don't think there is an easy fix.
|
||||
// const files = useUppyState(uppyDashboard, (state) => state.files)
|
||||
|
||||
// drag-drop has no visual output so we test it via the uppy instance
|
||||
window.uppy = uppyDragDrop
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
maxWidth: '30em',
|
||||
margin: '5em 0',
|
||||
display: 'grid',
|
||||
gridGap: '2em',
|
||||
}}
|
||||
>
|
||||
<button type="button" id="open" onClick={() => setOpen(!open)}>
|
||||
Open Modal
|
||||
</button>
|
||||
{/* <p>Dashboard file count: {Object.keys(files).length}</p> */}
|
||||
|
||||
<Dashboard id="dashboard" uppy={uppyDashboard} />
|
||||
<DashboardModal id="modal" open={open} uppy={uppyModal} />
|
||||
<DragDrop id="drag-drop" uppy={uppyDragDrop} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>dashboard-react</title>
|
||||
<script defer type="module" src="index.jsx"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { createRoot } from 'react-dom/client'
|
||||
import App from './App.jsx'
|
||||
|
||||
const container = document.getElementById('app')
|
||||
const root = createRoot(container)
|
||||
|
||||
root.render(<App />)
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import { defineConfig } from 'cypress'
|
||||
import installLogsPrinter from 'cypress-terminal-report/src/installLogsPrinter.js'
|
||||
import startMockServer from './mock-server.mjs'
|
||||
|
||||
export default defineConfig({
|
||||
defaultCommandTimeout: 16_000,
|
||||
requestTimeout: 16_000,
|
||||
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:1234',
|
||||
specPattern: 'cypress/integration/*.spec.ts',
|
||||
|
||||
setupNodeEvents(on) {
|
||||
// implement node event listeners here
|
||||
installLogsPrinter(on)
|
||||
|
||||
startMockServer('localhost', 4678)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
@ -1,983 +0,0 @@
|
|||
{
|
||||
"plugins": {
|
||||
"Dashboard": {
|
||||
"isHidden": false,
|
||||
"fileCardFor": null,
|
||||
"activeOverlayType": null,
|
||||
"showAddFilesPanel": false,
|
||||
"activePickerPanel": false,
|
||||
"metaFields": [
|
||||
{
|
||||
"id": "license",
|
||||
"name": "License",
|
||||
"placeholder": "specify license"
|
||||
},
|
||||
{
|
||||
"id": "caption",
|
||||
"name": "Caption",
|
||||
"placeholder": "add caption"
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"id": "Dashboard:StatusBar",
|
||||
"name": "StatusBar",
|
||||
"type": "progressindicator"
|
||||
},
|
||||
{
|
||||
"id": "Dashboard:Informer",
|
||||
"name": "Informer",
|
||||
"type": "progressindicator"
|
||||
},
|
||||
{
|
||||
"id": "GoogleDrive",
|
||||
"name": "Google Drive",
|
||||
"type": "acquirer"
|
||||
},
|
||||
{
|
||||
"id": "Instagram",
|
||||
"name": "Instagram",
|
||||
"type": "acquirer"
|
||||
},
|
||||
{
|
||||
"id": "Dropbox",
|
||||
"name": "Dropbox",
|
||||
"type": "acquirer"
|
||||
},
|
||||
{
|
||||
"id": "Url",
|
||||
"name": "Link",
|
||||
"type": "acquirer"
|
||||
},
|
||||
{
|
||||
"id": "Webcam",
|
||||
"name": "Camera",
|
||||
"type": "acquirer"
|
||||
}
|
||||
],
|
||||
"areInsidesReadyToBeVisible": true,
|
||||
"containerWidth": 750,
|
||||
"containerHeight": 490
|
||||
},
|
||||
"GoogleDrive": {
|
||||
"currentSelection": [],
|
||||
"authenticated": false,
|
||||
"files": [],
|
||||
"folders": [],
|
||||
"directories": [],
|
||||
"activeRow": -1,
|
||||
"filterInput": "",
|
||||
"isSearchVisible": false,
|
||||
"hasTeamDrives": false,
|
||||
"teamDrives": [],
|
||||
"teamDriveId": ""
|
||||
},
|
||||
"Instagram": {
|
||||
"currentSelection": [],
|
||||
"authenticated": true,
|
||||
"files": [
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/420d2bdc2cb30251d7ecf8e516f7fa7d/5D55A291/t51.2885-15/e35/s320x320/50692753_2474466385958388_3994336603663016042_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Feb 11, 2019, 3:34 PM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1976930126949922734_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/ff2400b726bbd3415c4a07b723615578/5D3C08E9/t51.2885-15/e35/s150x150/50692753_2474466385958388_3994336603663016042_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1976930126949922734_104680",
|
||||
"modifiedDate": "1549888457"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/78aa49ad8ccbe12b07fb20f07c1b9a1d/5D520E84/t51.2885-15/e35/s320x320/49858772_2238267119827712_38852393303322952_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/b3eda9daaa214951c1fb1a0f7000d8de/5D3F3F89/t51.2885-15/e35/s150x150/49858772_2238267119827712_38852393303322952_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=0",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/ec36a8b8b7db546abeb3cdcf12b52ba5/5D77DC5F/t51.2885-15/e35/s320x320/49858316_1974473399524283_2231924729468134373_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 1.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_1046801",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/d8ef3dd687b6aab396cae9284226c7df/5D711727/t51.2885-15/e35/s150x150/49858316_1974473399524283_2231924729468134373_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=1",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/3055f2ae78d775fc031654d20e791ebc/5D51235E/t51.2885-15/e35/s320x320/49933915_184580175831450_4288362971970794931_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 2.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_1046802",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/3d1b5aa16b8d64bc7c14ae1e3b13dfbe/5D5DD553/t51.2885-15/e35/c0.0.1079.1079a/s150x150/49933915_184580175831450_4288362971970794931_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=2",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/3da7cd1f79dcecde1e871ea09cf65a87/5D737FDF/t51.2885-15/e35/s320x320/50515172_1138232866338235_1751853475314282763_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 3.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_1046803",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/5c51a2dac516e575a66cf10c39615faf/5D6922A7/t51.2885-15/e35/s150x150/50515172_1138232866338235_1751853475314282763_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=3",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/9b869a8590154eb01bdb8c4d834dbc96/5D68F919/t51.2885-15/e35/s320x320/49536508_2715697688455532_3941725382632324585_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 4.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_1046804",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/9d5ef7d80443c6e606dd2032bd9bbef3/5D736861/t51.2885-15/e35/s150x150/49536508_2715697688455532_3941725382632324585_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=4",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/e93879efc4dad02ed95f28c845b30c1b/5D6EE4D2/t51.2885-15/e35/s320x320/50024282_2330888297144674_8558997522361102927_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 5.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_1046805",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/c173758a1dbcdc033973a4d9b77061fa/5D3FD13A/t51.2885-15/e35/c0.0.1079.1079a/s150x150/50024282_2330888297144674_8558997522361102927_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=5",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/385551f9d659254e79e52e2069962abe/5D3DB99B/t51.2885-15/e35/s320x320/49541690_1326265230849388_780299101204315442_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 6.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_1046806",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/39449810cc5a750d7da2ff0311986e5f/5D6F4796/t51.2885-15/e35/c0.0.1079.1079a/s150x150/49541690_1326265230849388_780299101204315442_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=6",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/6dbe8e2193c6f35cc9a4b3681331281d/5D6B8838/t51.2885-15/e35/s320x320/47693310_2066019920179632_9021194778493613415_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 7.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_1046807",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/d82f97e1cd037d3b5d8a9d3be1620725/5D54D1D0/t51.2885-15/e35/c0.0.1079.1079a/s150x150/47693310_2066019920179632_9021194778493613415_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=7",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/ab34b879e64b6b74d8cedfc7193dbc7f/5D5846E9/t51.2885-15/e35/s320x320/49306549_310052152966468_747321250340934181_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 8.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_1046808",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/4899a15cbcf805e4fbb18197322b4187/5D5118EF/t51.2885-15/e35/c0.0.1079.1079a/s150x150/49306549_310052152966468_747321250340934181_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=8",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/9898c850ebf16fb48c7c67f40fa768aa/5D5D74F8/t51.2885-15/e35/s320x320/49766698_1079661332208739_5393337093402545472_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 9.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_1046809",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/24ff0de8fd62247a5b4d46657e078504/5D56FE10/t51.2885-15/e35/c0.0.1079.1079a/s150x150/49766698_1079661332208739_5393337093402545472_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=9",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/945b83923551c2419d02fb394ca93ecf/5D6FFDF2/t51.2885-15/e35/s320x320/39220053_1153571588114505_375944023631724544_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Aug 23, 2018, 2:58 PM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1852250486931812607_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/c0b40cc16747180b59ae966afb3c020d/5D3CFA02/t51.2885-15/e35/s150x150/39220053_1153571588114505_375944023631724544_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1852250486931812607_104680?carousel_id=0",
|
||||
"modifiedDate": "1535025486"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/61b460c2217002b2ca277d8e2844717c/5D5EFFFD/t51.2885-15/e35/s320x320/39346998_282273142585083_1015643341825507328_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Aug 23, 2018, 2:58 PM 1.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1852250486931812607_1046801",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/4b39d19ad2583c95acbb11e762579e35/5D58090D/t51.2885-15/e35/s150x150/39346998_282273142585083_1015643341825507328_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1852250486931812607_104680?carousel_id=1",
|
||||
"modifiedDate": "1535025486"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/e7a7a3f0701ce18a1b036c24cb7cea9b/5D5AFDE4/t51.2885-15/e35/s320x320/38699807_314497562641072_2259807118783676416_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Aug 23, 2018, 2:58 PM 2.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1852250486931812607_1046802",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/853fe9cc8931bd25d910f1cb27e34c91/5D3C8E14/t51.2885-15/e35/s150x150/38699807_314497562641072_2259807118783676416_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1852250486931812607_104680?carousel_id=2",
|
||||
"modifiedDate": "1535025486"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/3fb0681ab45a93f538a6388d77d1e0a6/5D6C7686/t51.2885-15/e35/s320x320/39628514_334313577140078_7876516111540289536_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Aug 23, 2018, 2:58 PM 3.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1852250486931812607_1046803",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/d047037085ec8f9bcf5737275a54741b/5D70D676/t51.2885-15/e35/s150x150/39628514_334313577140078_7876516111540289536_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1852250486931812607_104680?carousel_id=3",
|
||||
"modifiedDate": "1535025486"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/be4a02058c30e658896f3a1cc10938fd/5D59783D/t51.2885-15/e35/s320x320/39507580_504831159978201_5212373753534611456_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Aug 23, 2018, 1:20 PM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1852201222927172582_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/4a05e2f9b3464fa502d8102e98e81c13/5D5A45CD/t51.2885-15/e35/s150x150/39507580_504831159978201_5212373753534611456_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1852201222927172582_104680",
|
||||
"modifiedDate": "1535019613"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/1ed3c5ce49461c85d30e74eb371c5142/5D3D332D/t51.2885-15/e35/s320x320/38235951_280335879462106_4098398707425214464_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Aug 14, 2018, 1:40 AM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1845325816068388330_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/fb2dacd2d08807cacc48a76acbb8cb32/5D7001DD/t51.2885-15/e35/s150x150/38235951_280335879462106_4098398707425214464_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1845325816068388330_104680",
|
||||
"modifiedDate": "1534200001"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/2aaab426ea979d4fec469c53aa748a1a/5D5B581D/t51.2885-15/e35/s320x320/38097421_294352187994556_231473126364413952_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Aug 14, 2018, 1:37 AM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1845324587128869025_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/568448b82d5883bf6c5b2b554dcb3c71/5D5BFCA9/t51.2885-15/e35/c135.0.809.809a/s150x150/38097421_294352187994556_231473126364413952_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1845324587128869025_104680",
|
||||
"modifiedDate": "1534199854"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/73a478dae4d7ed45ba86bb51c6f1279c/5D3BC45B/t51.2885-15/e35/s320x320/38004914_1332543240181648_8551268285629333504_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Aug 2, 2018, 8:27 PM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1837195735932389212_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/55047e7ff9ab12727fc38865852cfea4/5D3A6523/t51.2885-15/e35/s150x150/38004914_1332543240181648_8551268285629333504_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1837195735932389212_104680",
|
||||
"modifiedDate": "1533230820"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/4436a67f44ab681184d648b8cef2bd0d/5D5C35EB/t51.2885-15/e35/s320x320/36979476_227621117961595_1230588248623939584_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jul 12, 2018, 6:58 PM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1821930696162038399_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/b5626d3918d93266ee92b414853b7eaa/5D515F04/t51.2885-15/e35/c123.0.735.735a/s150x150/36979476_227621117961595_1230588248623939584_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1821930696162038399_104680",
|
||||
"modifiedDate": "1531411085"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/f26b47afa38a487002fb9cd823c911a9/5D53FC32/t51.2885-15/e35/s320x320/36037683_1012778022214785_7430470254473510912_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jun 24, 2018, 7:33 PM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1808902468027558819_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/b93e11cda790a99ffd3bcdbfd50199e4/5D4044D4/t51.2885-15/e35/c101.0.606.606a/s150x150/36037683_1012778022214785_7430470254473510912_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1808902468027558819_104680?carousel_id=0",
|
||||
"modifiedDate": "1529857999"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/1be4dae4e20a9b63e887ea5879590a9b/5D526FF1/t51.2885-15/e35/s320x320/35575252_166097084248741_3980291461882052608_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jun 24, 2018, 7:33 PM 1.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1808902468027558819_1046801",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/6c1e545968b58fa4bd9279284f5e4eb2/5D3A341F/t51.2885-15/e35/c135.0.809.809a/s150x150/35575252_166097084248741_3980291461882052608_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1808902468027558819_104680?carousel_id=1",
|
||||
"modifiedDate": "1529857999"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "video",
|
||||
"name": "Instagram Jun 24, 2018, 7:33 PM 2.mp4",
|
||||
"mimeType": "video/mp4",
|
||||
"id": "1808902468027558819_1046802",
|
||||
"thumbnail": null,
|
||||
"requestPath": "1808902468027558819_104680?carousel_id=2",
|
||||
"modifiedDate": "1529857999"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/862ced4e0f047ba14ff29c6cc0318c44/5D57DF38/t51.2885-15/e35/s320x320/35278170_837444053128139_5243357991205339136_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jun 20, 2018, 2:42 AM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1805494813368147007_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/4829dc1acad2af234ff4628d7d4750c2/5D3D81C8/t51.2885-15/e35/s150x150/35278170_837444053128139_5243357991205339136_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1805494813368147007_104680",
|
||||
"modifiedDate": "1529451775"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/7c4ceb66525b802444dccd5a86924fc2/5D74C314/t51.2885-15/e35/s320x320/28764233_1638269642893040_4480301736187133952_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Mar 18, 2018, 9:32 PM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1737934383083311905_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/5f57985b3b0a727ba6cb8b5107f8a992/5D50EF6C/t51.2885-15/e35/s150x150/28764233_1638269642893040_4480301736187133952_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1737934383083311905_104680",
|
||||
"modifiedDate": "1521397944"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/899393f947e53ac5b36a995d67d111df/5D5BD35A/t51.2885-15/e35/s320x320/26067758_541222112905573_5423593755456307200_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 11, 2018, 5:18 AM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1689609196894186490_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/fd06fd5bb588684209877d1414315f16/5D5973AA/t51.2885-15/e35/s150x150/26067758_541222112905573_5423593755456307200_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1689609196894186490_104680",
|
||||
"modifiedDate": "1515637133"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/4cde437dd22560fab9d5eb3de0b8b2e0/5D7201E3/t51.2885-15/e35/s320x320/25036240_2079885088923563_8690166922391584768_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Dec 22, 2017, 8:05 PM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1675559745477333499_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/db7f2fbf1db02963e8bea721101cc287/5D6E489B/t51.2885-15/e35/s150x150/25036240_2079885088923563_8690166922391584768_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1675559745477333499_104680",
|
||||
"modifiedDate": "1513962308"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/af35659718f36655e8bf703f25bb233c/5D5F644F/t51.2885-15/e35/s320x320/24331847_1536227273080845_4856051751851130880_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Dec 4, 2017, 7:44 AM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1662141091721036563_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/190fd6922e69ad087597ba0697938cf5/5D5B97BC/t51.2885-15/e35/c102.0.875.875/s150x150/24331847_1536227273080845_4856051751851130880_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1662141091721036563_104680",
|
||||
"modifiedDate": "1512362680"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/b83e927ac2d13ad1ac9d2cafdc01f959/5D405387/t51.2885-15/e35/s320x320/23417132_213197029221132_8238897611998756864_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Nov 12, 2017, 7:02 PM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1646537186320644618_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/5b346e973c29acbbcaea0f61f1809f4e/5D560A77/t51.2885-15/e35/s150x150/23417132_213197029221132_8238897611998756864_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1646537186320644618_104680",
|
||||
"modifiedDate": "1510502549"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/e83ae26b09c5187a1a53083f8bdeb364/5D76BF66/t51.2885-15/e35/s320x320/23347341_1932821443410985_573390529291616256_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Nov 7, 2017, 4:55 AM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1642487154005217857_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/992905011fba71ca828cb6f5b83df600/5D6F5B96/t51.2885-15/e35/s150x150/23347341_1932821443410985_573390529291616256_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1642487154005217857_104680?carousel_id=0",
|
||||
"modifiedDate": "1510019748"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/50ae2bf3f9340ff4f1d78653087d7450/5D71B641/t51.2885-15/e35/s320x320/23280046_500879000292815_4369528731417444352_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Nov 7, 2017, 4:55 AM 1.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1642487154005217857_1046801",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/01cf69185d6c33521df01696920039f4/5D56B014/t51.2885-15/e35/c2.0.598.598/s150x150/23280046_500879000292815_4369528731417444352_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1642487154005217857_104680?carousel_id=1",
|
||||
"modifiedDate": "1510019748"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/1b1512e3914c2fda53ad3a840c2681be/5D6D9C4A/t51.2885-15/e35/s320x320/22858331_1958241871109983_4354847188874952704_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Oct 29, 2017, 7:22 PM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1636400252659303992_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/5a5c1885bbd867feda749149e1dd56d6/5D5B9732/t51.2885-15/e35/s150x150/22858331_1958241871109983_4354847188874952704_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1636400252659303992_104680?carousel_id=0",
|
||||
"modifiedDate": "1509294133"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/8615347d7e76a9300f3593abf4e9016c/5D6C344D/t51.2885-15/e35/s320x320/23098451_123562928335776_7617655378888622080_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Oct 29, 2017, 7:22 PM 1.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1636400252659303992_1046801",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/9811c2c8bfd4f7d97045d8bbf3ecd2d1/5D708FBD/t51.2885-15/e35/s150x150/23098451_123562928335776_7617655378888622080_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1636400252659303992_104680?carousel_id=1",
|
||||
"modifiedDate": "1509294133"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/01a9ab9149a22a597a80fc4cbf13738f/5D50E262/t51.2885-15/e35/s320x320/23098990_848783778612174_4475941923474898944_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Oct 29, 2017, 7:22 PM 2.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1636400252659303992_1046802",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/ea649769b559a7f076bd727bee49a2d6/5D3C7892/t51.2885-15/e35/s150x150/23098990_848783778612174_4475941923474898944_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1636400252659303992_104680?carousel_id=2",
|
||||
"modifiedDate": "1509294133"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/fc516c0e146dbf9b0fca960a9411fdf7/5D3ED20B/t51.2885-15/e35/s320x320/22802126_1517752501642900_5606082552176574464_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Oct 29, 2017, 7:22 PM 3.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1636400252659303992_1046803",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/001f32c24f6ccd508cc4905336073053/5D71F973/t51.2885-15/e35/s150x150/22802126_1517752501642900_5606082552176574464_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1636400252659303992_104680?carousel_id=3",
|
||||
"modifiedDate": "1509294133"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/9ccca5c9eadd69ccc13e53cf4f43b314/5D390493/t51.2885-15/e35/s320x320/22802520_139793160103830_5225518814476632064_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Oct 29, 2017, 7:22 PM 4.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1636400252659303992_1046804",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/3d85f3116a34cbd465421dca31972e16/5D752E63/t51.2885-15/e35/s150x150/22802520_139793160103830_5225518814476632064_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1636400252659303992_104680?carousel_id=4",
|
||||
"modifiedDate": "1509294133"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/031438a7a7f73fadff9af4f9f27e897a/5D5AB962/t51.2885-15/e35/s320x320/22639252_183626528874474_2694579090425380864_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Oct 22, 2017, 2:52 PM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1631190853481371017_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/793785790f639b4d1c281f8f85b617b7/5D5CC492/t51.2885-15/e35/s150x150/22639252_183626528874474_2694579090425380864_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1631190853481371017_104680",
|
||||
"modifiedDate": "1508673124"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/1c45c002dca07b795008b734a2dadf1a/5D567634/t51.2885-15/e35/s320x320/22580990_1537478786332265_2919339273100460032_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Oct 17, 2017, 12:11 AM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1627123826827378513_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/9cdda68ef82a7d5415184d24b7938aae/5D5B8E4C/t51.2885-15/e35/s150x150/22580990_1537478786332265_2919339273100460032_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1627123826827378513_104680?carousel_id=0",
|
||||
"modifiedDate": "1508188297"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/83c22b41fcf93fbf4c0c2a2164c74eba/5D6009D3/t51.2885-15/e35/s320x320/22430535_1082180631919539_4957960914784485376_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Oct 17, 2017, 12:11 AM 1.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1627123826827378513_1046801",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/b1ec088d4bad03e47344f9e8742e26af/5D5CF2AB/t51.2885-15/e35/s150x150/22430535_1082180631919539_4957960914784485376_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1627123826827378513_104680?carousel_id=1",
|
||||
"modifiedDate": "1508188297"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/b4fa4fc2530639cc555702153d5142da/5D5C414B/t51.2885-15/e35/s320x320/22430474_501048343603948_3378745776992681984_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Oct 17, 2017, 12:11 AM 2.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1627123826827378513_1046802",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/0d97e18dbf6176009d599a76b76b83db/5D57C4BB/t51.2885-15/e35/s150x150/22430474_501048343603948_3378745776992681984_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1627123826827378513_104680?carousel_id=2",
|
||||
"modifiedDate": "1508188297"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/c7bc86b8e610c7d9dc6642f53697ded2/5D53711A/t51.2885-15/e35/s320x320/22430500_182297355651006_8614244764025356288_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Oct 17, 2017, 12:11 AM 3.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1627123826827378513_1046803",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/d87f16c611a0f28c4b49a5c14136d13c/5D39C3EA/t51.2885-15/e35/s150x150/22430500_182297355651006_8614244764025356288_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1627123826827378513_104680?carousel_id=3",
|
||||
"modifiedDate": "1508188297"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/89a31fd438a9953a17ac5687da438581/5D54979A/t51.2885-15/e35/s320x320/22637761_173098443269988_8262666012554428416_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Oct 17, 2017, 12:11 AM 4.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1627123826827378513_1046804",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/2b6932601eec3e25c447510f777b227d/5D40AF6A/t51.2885-15/e35/s150x150/22637761_173098443269988_8262666012554428416_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1627123826827378513_104680?carousel_id=4",
|
||||
"modifiedDate": "1508188297"
|
||||
},
|
||||
{
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/d6d074a714c2e6758cd1fc3732211dc0/5D5C94AD/t51.2885-15/e35/s320x320/22344026_1843951665618388_1364607905617149952_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Oct 10, 2017, 7:53 PM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1622645126576609002_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/1b8dd33db144490ca9087796b16792ca/5D3DCDD5/t51.2885-15/e35/s150x150/22344026_1843951665618388_1364607905617149952_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1622645126576609002_104680",
|
||||
"modifiedDate": "1507654394"
|
||||
}
|
||||
],
|
||||
"folders": [],
|
||||
"directories": [
|
||||
{
|
||||
"id": "recent"
|
||||
}
|
||||
],
|
||||
"activeRow": -1,
|
||||
"filterInput": "",
|
||||
"isSearchVisible": false,
|
||||
"didFirstRender": true,
|
||||
"loading": false
|
||||
},
|
||||
"Dropbox": {
|
||||
"currentSelection": [],
|
||||
"authenticated": false,
|
||||
"files": [],
|
||||
"folders": [],
|
||||
"directories": [],
|
||||
"activeRow": -1,
|
||||
"filterInput": "",
|
||||
"isSearchVisible": false
|
||||
},
|
||||
"Webcam": {
|
||||
"cameraReady": false
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"uppy-instagramjan1820191139pm6jpeg-image/jpeg": {
|
||||
"source": "Instagram",
|
||||
"id": "uppy-instagramjan1820191139pm6jpeg-image/jpeg",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 6.jpeg",
|
||||
"extension": "jpeg",
|
||||
"meta": {
|
||||
"username": "John",
|
||||
"license": "Creative Commons",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 6.jpeg",
|
||||
"type": "image/jpeg"
|
||||
},
|
||||
"type": "image/jpeg",
|
||||
"data": {
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/385551f9d659254e79e52e2069962abe/5D3DB99B/t51.2885-15/e35/s320x320/49541690_1326265230849388_780299101204315442_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 6.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_1046806",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/39449810cc5a750d7da2ff0311986e5f/5D6F4796/t51.2885-15/e35/c0.0.1079.1079a/s150x150/49541690_1326265230849388_780299101204315442_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=6",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
"progress": {
|
||||
"uploadStarted": 1556219160869,
|
||||
"uploadComplete": true,
|
||||
"percentage": 100,
|
||||
"bytesUploaded": 69427,
|
||||
"bytesTotal": 69427
|
||||
},
|
||||
"size": null,
|
||||
"isRemote": true,
|
||||
"remote": {
|
||||
"companionUrl": "http://localhost:3020",
|
||||
"url": "http://localhost:3020/instagram/get/1959779810654008285_104680?carousel_id=6",
|
||||
"body": {
|
||||
"fileId": "1959779810654008285_1046806"
|
||||
},
|
||||
"providerOptions": {
|
||||
"companionUrl": "http://localhost:3020",
|
||||
"provider": "instagram",
|
||||
"authProvider": "instagram",
|
||||
"pluginId": "Instagram"
|
||||
}
|
||||
},
|
||||
"preview": "https://scontent.cdninstagram.com/vp/39449810cc5a750d7da2ff0311986e5f/5D6F4796/t51.2885-15/e35/c0.0.1079.1079a/s150x150/49541690_1326265230849388_780299101204315442_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"serverToken": "4a8616b1-58c9-464c-a5f0-450f4fac7959",
|
||||
"response": {
|
||||
"uploadURL": "https://master.tus.io/files/0db2bbb15785b5fbe1e04450a2ad7e27+NMjcmLga7xPuGJT6Ri65E4qN34Hck4CpgPG4r8uQdR2iyOTNDS2_hSHzROTuV.ApP.F9tLbZR04303y.X0apIHXPstqQAgOPyO1tbUKfqJ3SQ6XkX_gRfmc8hadlJK_H"
|
||||
},
|
||||
"uploadURL": "https://master.tus.io/files/0db2bbb15785b5fbe1e04450a2ad7e27+NMjcmLga7xPuGJT6Ri65E4qN34Hck4CpgPG4r8uQdR2iyOTNDS2_hSHzROTuV.ApP.F9tLbZR04303y.X0apIHXPstqQAgOPyO1tbUKfqJ3SQ6XkX_gRfmc8hadlJK_H",
|
||||
"isPaused": false
|
||||
},
|
||||
"uppy-instagramjan1820191139pm5jpeg-image/jpeg": {
|
||||
"source": "Instagram",
|
||||
"id": "uppy-instagramjan1820191139pm5jpeg-image/jpeg",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 5.jpeg",
|
||||
"extension": "jpeg",
|
||||
"meta": {
|
||||
"username": "John",
|
||||
"license": "Creative Commons",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 5.jpeg",
|
||||
"type": "image/jpeg"
|
||||
},
|
||||
"type": "image/jpeg",
|
||||
"data": {
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/e93879efc4dad02ed95f28c845b30c1b/5D6EE4D2/t51.2885-15/e35/s320x320/50024282_2330888297144674_8558997522361102927_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 5.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_1046805",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/c173758a1dbcdc033973a4d9b77061fa/5D3FD13A/t51.2885-15/e35/c0.0.1079.1079a/s150x150/50024282_2330888297144674_8558997522361102927_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=5",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
"progress": {
|
||||
"uploadStarted": 1556219160871,
|
||||
"uploadComplete": true,
|
||||
"percentage": 100,
|
||||
"bytesUploaded": null,
|
||||
"bytesTotal": null
|
||||
},
|
||||
"size": null,
|
||||
"isRemote": true,
|
||||
"remote": {
|
||||
"companionUrl": "http://localhost:3020",
|
||||
"url": "http://localhost:3020/instagram/get/1959779810654008285_104680?carousel_id=5",
|
||||
"body": {
|
||||
"fileId": "1959779810654008285_1046805"
|
||||
},
|
||||
"providerOptions": {
|
||||
"companionUrl": "http://localhost:3020",
|
||||
"provider": "instagram",
|
||||
"authProvider": "instagram",
|
||||
"pluginId": "Instagram"
|
||||
}
|
||||
},
|
||||
"preview": "https://scontent.cdninstagram.com/vp/c173758a1dbcdc033973a4d9b77061fa/5D3FD13A/t51.2885-15/e35/c0.0.1079.1079a/s150x150/50024282_2330888297144674_8558997522361102927_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"serverToken": "78105341-af5e-4576-9fc7-d0a11becdc51",
|
||||
"response": {
|
||||
"uploadURL": "https://master.tus.io/files/691bd69026e91a7568a240cb909139e5+gDSpFJXfEGIaPQyzAZk0OZ_y5g2fsNI2FPdNuoCQl0ka7Ch.uAXWtsUhjyB3ieH84JkHl03_h5jAXJn4X6TZRsF8yfZqVVBaYRdZbOqpZcYoBwdQ.SNFmASrukqcmYVa"
|
||||
},
|
||||
"uploadURL": "https://master.tus.io/files/691bd69026e91a7568a240cb909139e5+gDSpFJXfEGIaPQyzAZk0OZ_y5g2fsNI2FPdNuoCQl0ka7Ch.uAXWtsUhjyB3ieH84JkHl03_h5jAXJn4X6TZRsF8yfZqVVBaYRdZbOqpZcYoBwdQ.SNFmASrukqcmYVa",
|
||||
"isPaused": false
|
||||
},
|
||||
"uppy-instagramjan1820191139pm4jpeg-image/jpeg": {
|
||||
"source": "Instagram",
|
||||
"id": "uppy-instagramjan1820191139pm4jpeg-image/jpeg",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 4.jpeg",
|
||||
"extension": "jpeg",
|
||||
"meta": {
|
||||
"username": "John",
|
||||
"license": "Creative Commons",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 4.jpeg",
|
||||
"type": "image/jpeg"
|
||||
},
|
||||
"type": "image/jpeg",
|
||||
"data": {
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/9b869a8590154eb01bdb8c4d834dbc96/5D68F919/t51.2885-15/e35/s320x320/49536508_2715697688455532_3941725382632324585_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 4.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_1046804",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/9d5ef7d80443c6e606dd2032bd9bbef3/5D736861/t51.2885-15/e35/s150x150/49536508_2715697688455532_3941725382632324585_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=4",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
"progress": {
|
||||
"uploadStarted": 1556219160872,
|
||||
"uploadComplete": true,
|
||||
"percentage": 100,
|
||||
"bytesUploaded": 79296,
|
||||
"bytesTotal": 79296
|
||||
},
|
||||
"size": null,
|
||||
"isRemote": true,
|
||||
"remote": {
|
||||
"companionUrl": "http://localhost:3020",
|
||||
"url": "http://localhost:3020/instagram/get/1959779810654008285_104680?carousel_id=4",
|
||||
"body": {
|
||||
"fileId": "1959779810654008285_1046804"
|
||||
},
|
||||
"providerOptions": {
|
||||
"companionUrl": "http://localhost:3020",
|
||||
"provider": "instagram",
|
||||
"authProvider": "instagram",
|
||||
"pluginId": "Instagram"
|
||||
}
|
||||
},
|
||||
"preview": "https://scontent.cdninstagram.com/vp/9d5ef7d80443c6e606dd2032bd9bbef3/5D736861/t51.2885-15/e35/s150x150/49536508_2715697688455532_3941725382632324585_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"serverToken": "11061e76-6fa9-4c0d-b877-c0759f077d94",
|
||||
"response": {
|
||||
"uploadURL": "https://master.tus.io/files/f49cc001f5447ea90c898b69b1789d96+GD3uaee_lz.07izRtBlC1E99eum2CAWHHGOgdiTDyP8DpGM31fiBIQrCzCnbjGZ2xVqad3W1S1SL5R96dRlNdNKie4uDpRrhKcW17RQBkWyKyiE5fDKe8FhEo.9JstBx"
|
||||
},
|
||||
"uploadURL": "https://master.tus.io/files/f49cc001f5447ea90c898b69b1789d96+GD3uaee_lz.07izRtBlC1E99eum2CAWHHGOgdiTDyP8DpGM31fiBIQrCzCnbjGZ2xVqad3W1S1SL5R96dRlNdNKie4uDpRrhKcW17RQBkWyKyiE5fDKe8FhEo.9JstBx",
|
||||
"isPaused": false
|
||||
},
|
||||
"uppy-instagramjan1820191139pm3jpeg-image/jpeg": {
|
||||
"source": "Instagram",
|
||||
"id": "uppy-instagramjan1820191139pm3jpeg-image/jpeg",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 3.jpeg",
|
||||
"extension": "jpeg",
|
||||
"meta": {
|
||||
"username": "John",
|
||||
"license": "Creative Commons",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 3.jpeg",
|
||||
"type": "image/jpeg"
|
||||
},
|
||||
"type": "image/jpeg",
|
||||
"data": {
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/3da7cd1f79dcecde1e871ea09cf65a87/5D737FDF/t51.2885-15/e35/s320x320/50515172_1138232866338235_1751853475314282763_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 3.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_1046803",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/5c51a2dac516e575a66cf10c39615faf/5D6922A7/t51.2885-15/e35/s150x150/50515172_1138232866338235_1751853475314282763_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=3",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
"progress": {
|
||||
"uploadStarted": 1556219160872,
|
||||
"uploadComplete": true,
|
||||
"percentage": 100,
|
||||
"bytesUploaded": 129055,
|
||||
"bytesTotal": 129055
|
||||
},
|
||||
"size": null,
|
||||
"isRemote": true,
|
||||
"remote": {
|
||||
"companionUrl": "http://localhost:3020",
|
||||
"url": "http://localhost:3020/instagram/get/1959779810654008285_104680?carousel_id=3",
|
||||
"body": {
|
||||
"fileId": "1959779810654008285_1046803"
|
||||
},
|
||||
"providerOptions": {
|
||||
"companionUrl": "http://localhost:3020",
|
||||
"provider": "instagram",
|
||||
"authProvider": "instagram",
|
||||
"pluginId": "Instagram"
|
||||
}
|
||||
},
|
||||
"preview": "https://scontent.cdninstagram.com/vp/5c51a2dac516e575a66cf10c39615faf/5D6922A7/t51.2885-15/e35/s150x150/50515172_1138232866338235_1751853475314282763_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"serverToken": "b12c3999-1be2-4a15-86ea-530e73e6da76",
|
||||
"response": {
|
||||
"uploadURL": "https://master.tus.io/files/9a89b79d6ed7b6ced857a036aad43fb6+WmV6kpylmuFV1HHA3JKIgmU5wJRwyRmweOpEUjQT86Zc5YLVa_nwsmV.P4Pl4oF15rmhb5PvkukMVTKN98WQff_QGD0TjBSHDYqVNOVGuJ8_w4.A.IWOl3yRSk58rkHf"
|
||||
},
|
||||
"uploadURL": "https://master.tus.io/files/9a89b79d6ed7b6ced857a036aad43fb6+WmV6kpylmuFV1HHA3JKIgmU5wJRwyRmweOpEUjQT86Zc5YLVa_nwsmV.P4Pl4oF15rmhb5PvkukMVTKN98WQff_QGD0TjBSHDYqVNOVGuJ8_w4.A.IWOl3yRSk58rkHf",
|
||||
"isPaused": false
|
||||
},
|
||||
"uppy-instagramfeb112019334pmjpeg-image/jpeg": {
|
||||
"source": "Instagram",
|
||||
"id": "uppy-instagramfeb112019334pmjpeg-image/jpeg",
|
||||
"name": "Instagram Feb 11, 2019, 3:34 PM.jpeg",
|
||||
"extension": "jpeg",
|
||||
"meta": {
|
||||
"username": "John",
|
||||
"license": "Creative Commons",
|
||||
"name": "Instagram Feb 11, 2019, 3:34 PM.jpeg",
|
||||
"type": "image/jpeg"
|
||||
},
|
||||
"type": "image/jpeg",
|
||||
"data": {
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/420d2bdc2cb30251d7ecf8e516f7fa7d/5D55A291/t51.2885-15/e35/s320x320/50692753_2474466385958388_3994336603663016042_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Feb 11, 2019, 3:34 PM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1976930126949922734_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/ff2400b726bbd3415c4a07b723615578/5D3C08E9/t51.2885-15/e35/s150x150/50692753_2474466385958388_3994336603663016042_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1976930126949922734_104680",
|
||||
"modifiedDate": "1549888457"
|
||||
},
|
||||
"progress": {
|
||||
"uploadStarted": 1556219160874,
|
||||
"uploadComplete": true,
|
||||
"percentage": 100,
|
||||
"bytesUploaded": 86926,
|
||||
"bytesTotal": 86926
|
||||
},
|
||||
"size": null,
|
||||
"isRemote": true,
|
||||
"remote": {
|
||||
"companionUrl": "http://localhost:3020",
|
||||
"url": "http://localhost:3020/instagram/get/1976930126949922734_104680",
|
||||
"body": {
|
||||
"fileId": "1976930126949922734_104680"
|
||||
},
|
||||
"providerOptions": {
|
||||
"companionUrl": "http://localhost:3020",
|
||||
"provider": "instagram",
|
||||
"authProvider": "instagram",
|
||||
"pluginId": "Instagram"
|
||||
}
|
||||
},
|
||||
"preview": "https://scontent.cdninstagram.com/vp/ff2400b726bbd3415c4a07b723615578/5D3C08E9/t51.2885-15/e35/s150x150/50692753_2474466385958388_3994336603663016042_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"serverToken": "dde6723d-29f7-4983-8b42-0a9e18683872",
|
||||
"response": {
|
||||
"uploadURL": "https://master.tus.io/files/533f69131228bc58faef2b247af1f1bd+hHkHXy5FmiPzutH2jEfQMMHiUtae68lrmZKRenqvSSU94FcsKxurqzg10jHSvV3lFps9zdzXfbmb8VBEX6znqffM603qapJuDhfeNZkgrBxOkgKglyvQLV_9ydgaJNLE"
|
||||
},
|
||||
"uploadURL": "https://master.tus.io/files/533f69131228bc58faef2b247af1f1bd+hHkHXy5FmiPzutH2jEfQMMHiUtae68lrmZKRenqvSSU94FcsKxurqzg10jHSvV3lFps9zdzXfbmb8VBEX6znqffM603qapJuDhfeNZkgrBxOkgKglyvQLV_9ydgaJNLE",
|
||||
"isPaused": false
|
||||
},
|
||||
"uppy-instagramjan1820191139pmjpeg-image/jpeg": {
|
||||
"source": "Instagram",
|
||||
"id": "uppy-instagramjan1820191139pmjpeg-image/jpeg",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM.jpeg",
|
||||
"extension": "jpeg",
|
||||
"meta": {
|
||||
"username": "John",
|
||||
"license": "Creative Commons",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM.jpeg",
|
||||
"type": "image/jpeg"
|
||||
},
|
||||
"type": "image/jpeg",
|
||||
"data": {
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/78aa49ad8ccbe12b07fb20f07c1b9a1d/5D520E84/t51.2885-15/e35/s320x320/49858772_2238267119827712_38852393303322952_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_104680",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/b3eda9daaa214951c1fb1a0f7000d8de/5D3F3F89/t51.2885-15/e35/s150x150/49858772_2238267119827712_38852393303322952_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=0",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
"progress": {
|
||||
"uploadStarted": 1556219160874,
|
||||
"uploadComplete": true,
|
||||
"percentage": 100,
|
||||
"bytesUploaded": 80471,
|
||||
"bytesTotal": 80471
|
||||
},
|
||||
"size": null,
|
||||
"isRemote": true,
|
||||
"remote": {
|
||||
"companionUrl": "http://localhost:3020",
|
||||
"url": "http://localhost:3020/instagram/get/1959779810654008285_104680?carousel_id=0",
|
||||
"body": {
|
||||
"fileId": "1959779810654008285_104680"
|
||||
},
|
||||
"providerOptions": {
|
||||
"companionUrl": "http://localhost:3020",
|
||||
"provider": "instagram",
|
||||
"authProvider": "instagram",
|
||||
"pluginId": "Instagram"
|
||||
}
|
||||
},
|
||||
"preview": "https://scontent.cdninstagram.com/vp/b3eda9daaa214951c1fb1a0f7000d8de/5D3F3F89/t51.2885-15/e35/s150x150/49858772_2238267119827712_38852393303322952_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"serverToken": "60675af3-fec5-45d8-87d6-30363a0534fa",
|
||||
"response": {
|
||||
"uploadURL": "https://master.tus.io/files/1f4f0dabad36e2995d4fde177ce64d0c+C.z6wtvRCGwt5hr4hUpbLE3NrduyiOiDn55wE8e81j88CRvceybDQ04iOQhlfl817wlPa0f2JU5aPjhZlp.pA4sBKPqLOlDMAkakwsVIGR4IW5nLYRoRGuauiJjYDMW."
|
||||
},
|
||||
"uploadURL": "https://master.tus.io/files/1f4f0dabad36e2995d4fde177ce64d0c+C.z6wtvRCGwt5hr4hUpbLE3NrduyiOiDn55wE8e81j88CRvceybDQ04iOQhlfl817wlPa0f2JU5aPjhZlp.pA4sBKPqLOlDMAkakwsVIGR4IW5nLYRoRGuauiJjYDMW.",
|
||||
"isPaused": false
|
||||
},
|
||||
"uppy-instagramjan1820191139pm1jpeg-image/jpeg": {
|
||||
"source": "Instagram",
|
||||
"id": "uppy-instagramjan1820191139pm1jpeg-image/jpeg",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 1.jpeg",
|
||||
"extension": "jpeg",
|
||||
"meta": {
|
||||
"username": "John",
|
||||
"license": "Creative Commons",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 1.jpeg",
|
||||
"type": "image/jpeg"
|
||||
},
|
||||
"type": "image/jpeg",
|
||||
"data": {
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/ec36a8b8b7db546abeb3cdcf12b52ba5/5D77DC5F/t51.2885-15/e35/s320x320/49858316_1974473399524283_2231924729468134373_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 1.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_1046801",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/d8ef3dd687b6aab396cae9284226c7df/5D711727/t51.2885-15/e35/s150x150/49858316_1974473399524283_2231924729468134373_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=1",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
"progress": {
|
||||
"uploadStarted": 1556219160876,
|
||||
"uploadComplete": false,
|
||||
"percentage": 15,
|
||||
"bytesUploaded": 12561.5,
|
||||
"bytesTotal": 80471
|
||||
},
|
||||
"size": null,
|
||||
"isRemote": true,
|
||||
"remote": {
|
||||
"companionUrl": "http://localhost:3020",
|
||||
"url": "http://localhost:3020/instagram/get/1959779810654008285_104680?carousel_id=1",
|
||||
"body": {
|
||||
"fileId": "1959779810654008285_1046801"
|
||||
},
|
||||
"providerOptions": {
|
||||
"companionUrl": "http://localhost:3020",
|
||||
"provider": "instagram",
|
||||
"authProvider": "instagram",
|
||||
"pluginId": "Instagram"
|
||||
}
|
||||
},
|
||||
"preview": "https://scontent.cdninstagram.com/vp/d8ef3dd687b6aab396cae9284226c7df/5D711727/t51.2885-15/e35/s150x150/49858316_1974473399524283_2231924729468134373_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"serverToken": "d6f75847-6685-456e-9db6-c345f32749af",
|
||||
"isPaused": true
|
||||
},
|
||||
"uppy-instagramjan1820191139pm2jpeg-image/jpeg": {
|
||||
"source": "Instagram",
|
||||
"id": "uppy-instagramjan1820191139pm2jpeg-image/jpeg",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 2.jpeg",
|
||||
"extension": "jpeg",
|
||||
"meta": {
|
||||
"username": "John",
|
||||
"license": "Creative Commons",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 2.jpeg",
|
||||
"type": "image/jpeg"
|
||||
},
|
||||
"type": "image/jpeg",
|
||||
"data": {
|
||||
"isFolder": false,
|
||||
"icon": "https://scontent.cdninstagram.com/vp/3055f2ae78d775fc031654d20e791ebc/5D51235E/t51.2885-15/e35/s320x320/49933915_184580175831450_4288362971970794931_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"name": "Instagram Jan 18, 2019, 11:39 PM 2.jpeg",
|
||||
"mimeType": "image/jpeg",
|
||||
"id": "1959779810654008285_1046802",
|
||||
"thumbnail": "https://scontent.cdninstagram.com/vp/3d1b5aa16b8d64bc7c14ae1e3b13dfbe/5D5DD553/t51.2885-15/e35/c0.0.1079.1079a/s150x150/49933915_184580175831450_4288362971970794931_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"requestPath": "1959779810654008285_104680?carousel_id=2",
|
||||
"modifiedDate": "1547843980"
|
||||
},
|
||||
"progress": {
|
||||
"uploadStarted": 1556219160878,
|
||||
"uploadComplete": true,
|
||||
"percentage": 100,
|
||||
"bytesUploaded": 111226,
|
||||
"bytesTotal": 111226
|
||||
},
|
||||
"size": null,
|
||||
"isRemote": true,
|
||||
"remote": {
|
||||
"companionUrl": "http://localhost:3020",
|
||||
"url": "http://localhost:3020/instagram/get/1959779810654008285_104680?carousel_id=2",
|
||||
"body": {
|
||||
"fileId": "1959779810654008285_1046802"
|
||||
},
|
||||
"providerOptions": {
|
||||
"companionUrl": "http://localhost:3020",
|
||||
"provider": "instagram",
|
||||
"authProvider": "instagram",
|
||||
"pluginId": "Instagram"
|
||||
}
|
||||
},
|
||||
"preview": "https://scontent.cdninstagram.com/vp/3d1b5aa16b8d64bc7c14ae1e3b13dfbe/5D5DD553/t51.2885-15/e35/c0.0.1079.1079a/s150x150/49933915_184580175831450_4288362971970794931_n.jpg?_nc_ht=scontent.cdninstagram.com",
|
||||
"serverToken": "92ae2486-90f0-4e2f-82f9-285ad87e2c5a",
|
||||
"response": {
|
||||
"uploadURL": "https://master.tus.io/files/c83164658faa4fdd7a12dc9e6044510f+YJKdoXIXl7PjA_UXhQ.PDqPhnk4A_O_LWm.3qau9X8kzHXg7dgI.igu5n_yPVBG0PezDPi2b8fSjBfc2KZjX3qUZxST9pb0iQP20_wKPax9Y4slQRQr7qpiUugFTljbA"
|
||||
},
|
||||
"uploadURL": "https://master.tus.io/files/c83164658faa4fdd7a12dc9e6044510f+YJKdoXIXl7PjA_UXhQ.PDqPhnk4A_O_LWm.3qau9X8kzHXg7dgI.igu5n_yPVBG0PezDPi2b8fSjBfc2KZjX3qUZxST9pb0iQP20_wKPax9Y4slQRQr7qpiUugFTljbA",
|
||||
"isPaused": false
|
||||
}
|
||||
},
|
||||
"currentUploads": {
|
||||
"cjux0phjn00012a5vzjw0buly": {
|
||||
"fileIDs": [
|
||||
"uppy-instagramjan1820191139pm6jpeg-image/jpeg",
|
||||
"uppy-instagramjan1820191139pm5jpeg-image/jpeg",
|
||||
"uppy-instagramjan1820191139pm4jpeg-image/jpeg",
|
||||
"uppy-instagramjan1820191139pm3jpeg-image/jpeg",
|
||||
"uppy-instagramfeb112019334pmjpeg-image/jpeg",
|
||||
"uppy-instagramjan1820191139pmjpeg-image/jpeg",
|
||||
"uppy-instagramjan1820191139pm1jpeg-image/jpeg",
|
||||
"uppy-instagramjan1820191139pm2jpeg-image/jpeg"
|
||||
],
|
||||
"step": 0,
|
||||
"result": {}
|
||||
}
|
||||
},
|
||||
"allowNewUpload": true,
|
||||
"capabilities": {
|
||||
"uploadProgress": true,
|
||||
"individualCancellation": true,
|
||||
"resumableUploads": true
|
||||
},
|
||||
"totalProgress": 1328,
|
||||
"meta": {
|
||||
"username": "John",
|
||||
"license": "Creative Commons"
|
||||
},
|
||||
"info": {
|
||||
"isHidden": true,
|
||||
"type": "info",
|
||||
"message": ""
|
||||
},
|
||||
"error": null
|
||||
}
|
||||
|
Before Width: | Height: | Size: 636 KiB |
|
Before Width: | Height: | Size: 3.6 MiB |
|
|
@ -1 +0,0 @@
|
|||
./cat.jpg
|
||||
|
|
@ -1 +0,0 @@
|
|||
./cat.jpg
|
||||
|
Before Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 544 KiB |
|
Before Width: | Height: | Size: 4.1 MiB |
|
|
@ -1,5 +0,0 @@
|
|||
%PDF-1.
|
||||
1 0 obj<</Pages 2 0 R>>endobj
|
||||
2 0 obj<</Kids[3 0 R]/Count 1>>endobj
|
||||
3 0 obj<</Parent 2 0 R>>endobj
|
||||
trailer <</Root 1 0 R>>
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
describe('Dashboard with @uppy/aws-s3-multipart', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/dashboard-aws-multipart')
|
||||
cy.get('.uppy-Dashboard-input:first').as('file-input')
|
||||
cy.intercept({ method: 'POST', pathname: '/s3/multipart' }).as('post')
|
||||
cy.intercept({ method: 'GET', pathname: '/s3/multipart/*/1' }).as('get')
|
||||
cy.intercept({ method: 'PUT' }).as('put')
|
||||
})
|
||||
|
||||
it('should upload cat image successfully', () => {
|
||||
cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', {
|
||||
force: true,
|
||||
})
|
||||
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait(['@post', '@get', '@put'])
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
it('should upload Russian poem image successfully', () => {
|
||||
const fileName = '١٠ كم мест для Нью-Йорке.pdf'
|
||||
cy.get('@file-input').selectFile(`cypress/fixtures/images/${fileName}`, {
|
||||
force: true,
|
||||
})
|
||||
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait(['@post', '@get', '@put'])
|
||||
cy.get('.uppy-Dashboard-Item-name').should('contain', fileName)
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
|
||||
it('should handle retry request gracefully', () => {
|
||||
cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', {
|
||||
force: true,
|
||||
})
|
||||
|
||||
cy.intercept('POST', '/s3/multipart', {
|
||||
forceNetworkError: true,
|
||||
times: 1,
|
||||
}).as('createMultipartUpload-fails')
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait(['@createMultipartUpload-fails'])
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Upload failed')
|
||||
|
||||
cy.intercept('POST', '/s3/multipart', {
|
||||
statusCode: 200,
|
||||
times: 1,
|
||||
body: JSON.stringify({
|
||||
key: 'mocked-key-attempt1',
|
||||
uploadId: 'mocked-uploadId-attempt1',
|
||||
}),
|
||||
}).as('createMultipartUpload-attempt1')
|
||||
cy.intercept(
|
||||
'GET',
|
||||
'/s3/multipart/mocked-uploadId-attempt1/1?key=mocked-key-attempt1',
|
||||
{ forceNetworkError: true },
|
||||
).as('signPart-fails')
|
||||
cy.intercept(
|
||||
'DELETE',
|
||||
'/s3/multipart/mocked-uploadId-attempt1?key=mocked-key-attempt1',
|
||||
{ statusCode: 200, body: '{}' },
|
||||
).as('abortAttempt-1')
|
||||
cy.get('.uppy-StatusBar-actions > .uppy-c-btn').click()
|
||||
cy.wait([
|
||||
'@createMultipartUpload-attempt1',
|
||||
'@signPart-fails',
|
||||
'@abortAttempt-1',
|
||||
])
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Upload failed')
|
||||
|
||||
cy.intercept('POST', '/s3/multipart', {
|
||||
statusCode: 200,
|
||||
times: 1,
|
||||
body: JSON.stringify({
|
||||
key: 'mocked-key-attempt2',
|
||||
uploadId: 'mocked-uploadId-attempt2',
|
||||
}),
|
||||
}).as('createMultipartUpload-attempt2')
|
||||
cy.intercept(
|
||||
'GET',
|
||||
'/s3/multipart/mocked-uploadId-attempt2/1?key=mocked-key-attempt2',
|
||||
{
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
ETag: 'W/"222-GXE2wLoMKDihw3wxZFH1APdUjHM"',
|
||||
},
|
||||
body: JSON.stringify({ url: '/put-fail', expires: 8 }),
|
||||
},
|
||||
).as('signPart-toFail')
|
||||
cy.intercept(
|
||||
'DELETE',
|
||||
'/s3/multipart/mocked-uploadId-attempt2?key=mocked-key-attempt2',
|
||||
{ statusCode: 200, body: '{}' },
|
||||
).as('abortAttempt-2')
|
||||
cy.intercept('PUT', '/put-fail', { forceNetworkError: true }).as(
|
||||
'put-fails',
|
||||
)
|
||||
cy.get('.uppy-StatusBar-actions > .uppy-c-btn').click()
|
||||
cy.wait([
|
||||
'@createMultipartUpload-attempt2',
|
||||
'@signPart-toFail',
|
||||
...Array(5).fill('@put-fails'),
|
||||
'@abortAttempt-2',
|
||||
])
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Upload failed')
|
||||
|
||||
cy.intercept(
|
||||
'GET',
|
||||
'/s3/multipart/mocked-uploadId-attempt2/1?key=mocked-key-attempt2',
|
||||
{
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
ETag: 'ETag-attempt2',
|
||||
},
|
||||
body: JSON.stringify({ url: '/put-success-attempt2', expires: 8 }),
|
||||
},
|
||||
).as('signPart-attempt2')
|
||||
cy.intercept('PUT', '/put-success-attempt2', {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
ETag: 'ETag-attempt2',
|
||||
},
|
||||
}).as('put-attempt2')
|
||||
cy.intercept(
|
||||
'POST',
|
||||
'/s3/multipart/mocked-uploadId-attempt2/complete?key=mocked-key-attempt2',
|
||||
{ forceNetworkError: true },
|
||||
).as('completeMultipartUpload-fails')
|
||||
cy.get('.uppy-StatusBar-actions > .uppy-c-btn').click()
|
||||
cy.wait([
|
||||
'@createMultipartUpload-attempt2',
|
||||
'@signPart-attempt2',
|
||||
'@put-attempt2',
|
||||
'@completeMultipartUpload-fails',
|
||||
'@abortAttempt-2',
|
||||
])
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Upload failed')
|
||||
|
||||
cy.intercept('POST', '/s3/multipart', {
|
||||
statusCode: 200,
|
||||
times: 1,
|
||||
body: JSON.stringify({
|
||||
key: 'mocked-key-attempt3',
|
||||
uploadId: 'mocked-uploadId-attempt3',
|
||||
}),
|
||||
}).as('createMultipartUpload-attempt3')
|
||||
let intercepted = 0
|
||||
cy.intercept(
|
||||
'GET',
|
||||
'/s3/multipart/mocked-uploadId-attempt3/1?key=mocked-key-attempt3',
|
||||
(req) => {
|
||||
if (intercepted++ < 2) {
|
||||
// Ensure that Uppy can recover from at least 2 network errors at this stage.
|
||||
req.destroy()
|
||||
return
|
||||
}
|
||||
req.reply({
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
ETag: 'ETag-attempt3',
|
||||
},
|
||||
body: JSON.stringify({ url: '/put-success-attempt3', expires: 8 }),
|
||||
})
|
||||
},
|
||||
).as('signPart-attempt3')
|
||||
cy.intercept('PUT', '/put-success-attempt3', {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
ETag: 'ETag-attempt3',
|
||||
},
|
||||
}).as('put-attempt3')
|
||||
cy.intercept(
|
||||
'POST',
|
||||
'/s3/multipart/mocked-uploadId-attempt3/complete?key=mocked-key-attempt3',
|
||||
{
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({
|
||||
location: 'someLocation',
|
||||
}),
|
||||
},
|
||||
).as('completeMultipartUpload-attempt3')
|
||||
cy.get('.uppy-StatusBar-actions > .uppy-c-btn').click()
|
||||
cy.wait([
|
||||
'@createMultipartUpload-attempt3',
|
||||
...Array(3).fill('@signPart-attempt3'),
|
||||
'@put-attempt3',
|
||||
'@completeMultipartUpload-attempt3',
|
||||
])
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
|
||||
it('should complete when resuming after pause', () => {
|
||||
cy.get('@file-input').selectFile(
|
||||
[
|
||||
'cypress/fixtures/images/cat.jpg',
|
||||
'cypress/fixtures/images/traffic.jpg',
|
||||
],
|
||||
{ force: true },
|
||||
)
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
|
||||
cy.wait('@post')
|
||||
|
||||
cy.get('button[data-cy=togglePauseResume]').click()
|
||||
cy.wait(300) // Wait an arbitrary amount of time as a user would do.
|
||||
cy.get('button[data-cy=togglePauseResume]').click()
|
||||
|
||||
cy.wait('@get')
|
||||
|
||||
cy.get('button[data-cy=togglePauseResume]').click()
|
||||
cy.wait(300) // Wait an arbitrary amount of time as a user would do.
|
||||
cy.get('button[data-cy=togglePauseResume]').click()
|
||||
|
||||
cy.wait(['@get', '@put'])
|
||||
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
})
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
describe('Dashboard with @uppy/aws-s3', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/dashboard-aws')
|
||||
cy.get('.uppy-Dashboard-input:first').as('file-input')
|
||||
cy.intercept({ method: 'GET', pathname: '/s3/params' }).as('get')
|
||||
cy.intercept({ method: 'POST' }).as('post')
|
||||
})
|
||||
|
||||
it('should upload cat image successfully', () => {
|
||||
cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', {
|
||||
force: true,
|
||||
})
|
||||
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait(['@post', '@get'])
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
})
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
function uglierBytes(text) {
|
||||
const KB = 2 ** 10
|
||||
const MB = KB * KB
|
||||
|
||||
if (text.endsWith(' KB')) {
|
||||
return Number(text.slice(0, -3)) * KB
|
||||
}
|
||||
|
||||
if (text.endsWith(' MB')) {
|
||||
return Number(text.slice(0, -3)) * MB
|
||||
}
|
||||
|
||||
if (text.endsWith(' B')) {
|
||||
return Number(text.slice(0, -2))
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Not what the computer thinks a human-readable size string look like: ${text}`,
|
||||
)
|
||||
}
|
||||
|
||||
describe('dashboard-compressor', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/dashboard-compressor')
|
||||
cy.get('.uppy-Dashboard-input:first').as('file-input')
|
||||
})
|
||||
|
||||
it('should compress images', () => {
|
||||
const sizeBeforeCompression = []
|
||||
|
||||
cy.get('@file-input').selectFile(
|
||||
[
|
||||
'cypress/fixtures/images/cat.jpg',
|
||||
'cypress/fixtures/images/traffic.jpg',
|
||||
],
|
||||
{ force: true },
|
||||
)
|
||||
|
||||
cy.get('.uppy-Dashboard-Item-statusSize').each((element) => {
|
||||
const text = element.text()
|
||||
sizeBeforeCompression.push(uglierBytes(text))
|
||||
})
|
||||
|
||||
cy.window().then(({ uppy }) => {
|
||||
uppy.on('preprocess-complete', (file) => {
|
||||
expect(file.extension).to.equal('webp')
|
||||
expect(file.type).to.equal('image/webp')
|
||||
|
||||
cy.get('.uppy-Dashboard-Item-statusSize').should((elements) => {
|
||||
expect(elements).to.have.length(sizeBeforeCompression.length)
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
expect(sizeBeforeCompression[i]).to.be.greaterThan(
|
||||
uglierBytes(elements[i].textContent),
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1,378 +0,0 @@
|
|||
import type Uppy from '@uppy/core'
|
||||
import type Transloadit from '@uppy/transloadit'
|
||||
|
||||
function getPlugin<M = any, B = any>(uppy: Uppy<M, B>) {
|
||||
return uppy.getPlugin<Transloadit<M, B>>('Transloadit')!
|
||||
}
|
||||
|
||||
describe('Dashboard with Transloadit', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/dashboard-transloadit')
|
||||
cy.get('.uppy-Dashboard-input:first').as('file-input')
|
||||
})
|
||||
|
||||
it('should upload all files as a single assembly with UppyFile metadata in Upload-Metadata', () => {
|
||||
cy.intercept({ path: '/assemblies', method: 'POST' }).as('createAssembly')
|
||||
|
||||
cy.get('@file-input').selectFile(
|
||||
[
|
||||
'cypress/fixtures/images/cat.jpg',
|
||||
'cypress/fixtures/images/traffic.jpg',
|
||||
],
|
||||
{ force: true },
|
||||
)
|
||||
|
||||
cy.window().then(({ uppy }) => {
|
||||
// Set metadata on all files
|
||||
uppy.setMeta({ sharedMetaProperty: 'bar' })
|
||||
const [file1, file2] = uppy.getFiles()
|
||||
// Set unique metdata per file as before that's how we determined to create multiple assemblies
|
||||
uppy.setFileMeta(file1.id, { one: 'one' })
|
||||
uppy.setFileMeta(file2.id, { two: 'two' })
|
||||
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
|
||||
cy.intercept('POST', '/resumable/*', (req) => {
|
||||
expect(req.headers['upload-metadata']).to.include('sharedMetaProperty')
|
||||
req.continue()
|
||||
})
|
||||
|
||||
cy.wait(['@createAssembly']).then(() => {
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
// should only create one assembly
|
||||
cy.get('@createAssembly.all').should('have.length', 1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it.skip('should close assembly when cancelled', () => {
|
||||
cy.intercept({ path: '/resumable/*', method: 'POST' }).as('tusCreate')
|
||||
cy.intercept({ path: '/assemblies', method: 'POST' }).as('createAssemblies')
|
||||
cy.intercept({ path: '/assemblies/*', method: 'DELETE' }).as('delete')
|
||||
|
||||
cy.window().then(({ uppy }) => {
|
||||
cy.get('@file-input').selectFile(
|
||||
[
|
||||
'cypress/fixtures/images/cat.jpg',
|
||||
'cypress/fixtures/images/traffic.jpg',
|
||||
'cypress/fixtures/images/car.jpg',
|
||||
],
|
||||
{ force: true },
|
||||
)
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
|
||||
cy.wait(['@createAssemblies', '@tusCreate']).then(() => {
|
||||
const { assembly } = getPlugin(uppy)
|
||||
|
||||
expect(assembly.closed).to.be.false
|
||||
|
||||
uppy.cancelAll()
|
||||
|
||||
cy.wait(['@delete']).then(() => {
|
||||
expect(assembly.closed).to.be.true
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should not emit error if upload is cancelled right away', () => {
|
||||
cy.intercept({ path: '/assemblies', method: 'POST' }).as('createAssemblies')
|
||||
|
||||
cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', {
|
||||
force: true,
|
||||
})
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
|
||||
const handler = cy.spy()
|
||||
|
||||
cy.window().then(({ uppy }) => {
|
||||
const { files } = uppy.getState()
|
||||
uppy.on('upload-error', handler)
|
||||
|
||||
const [fileID] = Object.keys(files)
|
||||
uppy.removeFile(fileID)
|
||||
uppy.removeFile(fileID)
|
||||
cy.wait('@createAssemblies').then(() => expect(handler).not.to.be.called)
|
||||
})
|
||||
})
|
||||
|
||||
it('should not re-use erroneous tus keys', () => {
|
||||
function createAssemblyStatus({
|
||||
message,
|
||||
assembly_id,
|
||||
bytes_expected,
|
||||
...other
|
||||
}) {
|
||||
return {
|
||||
message,
|
||||
assembly_id,
|
||||
parent_id: null,
|
||||
account_id: 'deadbeef',
|
||||
account_name: 'foo',
|
||||
account_slug: 'foo',
|
||||
template_id: null,
|
||||
template_name: null,
|
||||
instance: 'test.transloadit.com',
|
||||
assembly_url: `http://api2.test.transloadit.com/assemblies/${assembly_id}`,
|
||||
assembly_ssl_url: `https://api2-test.transloadit.com/assemblies/${assembly_id}`,
|
||||
uppyserver_url: 'https://api2-test.transloadit.com/companion/',
|
||||
companion_url: 'https://api2-test.transloadit.com/companion/',
|
||||
websocket_url: 'about:blank',
|
||||
tus_url: 'https://api2-test.transloadit.com/resumable/files/',
|
||||
bytes_received: 0,
|
||||
bytes_expected,
|
||||
upload_duration: 0.162,
|
||||
client_agent: null,
|
||||
client_ip: null,
|
||||
client_referer: null,
|
||||
transloadit_client:
|
||||
'uppy-core:3.2.0,uppy-transloadit:3.1.3,uppy-tus:3.1.0,uppy-dropbox:3.1.1,uppy-box:2.1.1,uppy-facebook:3.1.1,uppy-google-drive:3.1.1,uppy-instagram:3.1.1,uppy-onedrive:3.1.1,uppy-zoom:2.1.1,uppy-url:3.3.1',
|
||||
start_date: new Date().toISOString(),
|
||||
upload_meta_data_extracted: false,
|
||||
warnings: [],
|
||||
is_infinite: false,
|
||||
has_dupe_jobs: false,
|
||||
execution_start: null,
|
||||
execution_duration: null,
|
||||
queue_duration: 0.009,
|
||||
jobs_queue_duration: 0,
|
||||
notify_start: null,
|
||||
notify_url: null,
|
||||
notify_response_code: null,
|
||||
notify_response_data: null,
|
||||
notify_duration: null,
|
||||
last_job_completed: null,
|
||||
fields: {},
|
||||
running_jobs: [],
|
||||
bytes_usage: 0,
|
||||
executing_jobs: [],
|
||||
started_jobs: [],
|
||||
parent_assembly_status: null,
|
||||
params: '{}',
|
||||
template: null,
|
||||
merged_params: '{}',
|
||||
expected_tus_uploads: 1,
|
||||
started_tus_uploads: 0,
|
||||
finished_tus_uploads: 0,
|
||||
tus_uploads: [],
|
||||
uploads: [],
|
||||
results: {},
|
||||
build_id: '4765326956',
|
||||
status_endpoint: `https://api2-test.transloadit.com/assemblies/${assembly_id}`,
|
||||
...other,
|
||||
}
|
||||
}
|
||||
cy.get('@file-input').selectFile(['cypress/fixtures/images/cat.jpg'], {
|
||||
force: true,
|
||||
})
|
||||
|
||||
// SETUP for FIRST ATTEMPT (error response from Transloadit backend)
|
||||
const assemblyIDAttempt1 = '500e56004f4347a288194edd7c7a0ae1'
|
||||
const tusIDAttempt1 = 'a9daed4af4981880faf29b0e9596a14d'
|
||||
cy.intercept('POST', 'https://api2.transloadit.com/assemblies', {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(
|
||||
createAssemblyStatus({
|
||||
ok: 'ASSEMBLY_UPLOADING',
|
||||
message: 'The Assembly is still in the process of being uploaded.',
|
||||
assembly_id: assemblyIDAttempt1,
|
||||
bytes_expected: 263871,
|
||||
}),
|
||||
),
|
||||
}).as('createAssembly')
|
||||
|
||||
cy.intercept('POST', 'https://api2-test.transloadit.com/resumable/files/', {
|
||||
statusCode: 201,
|
||||
headers: {
|
||||
Location: `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt1}`,
|
||||
},
|
||||
times: 1,
|
||||
}).as('tusCall')
|
||||
cy.intercept(
|
||||
'PATCH',
|
||||
`https://api2-test.transloadit.com/resumable/files/${tusIDAttempt1}`,
|
||||
{
|
||||
statusCode: 204,
|
||||
headers: {
|
||||
'Upload-Length': '263871',
|
||||
'Upload-Offset': '263871',
|
||||
},
|
||||
times: 1,
|
||||
},
|
||||
)
|
||||
cy.intercept(
|
||||
'HEAD',
|
||||
`https://api2-test.transloadit.com/resumable/files/${tusIDAttempt1}`,
|
||||
{ statusCode: 204 },
|
||||
)
|
||||
|
||||
cy.intercept(
|
||||
'GET',
|
||||
`https://api2-test.transloadit.com/assemblies/${assemblyIDAttempt1}`,
|
||||
{
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(
|
||||
createAssemblyStatus({
|
||||
error: 'INVALID_FILE_META_DATA',
|
||||
http_code: 400,
|
||||
message: 'Whatever error message from Transloadit backend',
|
||||
reason: 'Whatever reason',
|
||||
msg: 'Whatever error from Transloadit backend',
|
||||
assembly_id: '500e56004f4347a288194edd7c7a0ae1',
|
||||
bytes_expected: 263871,
|
||||
}),
|
||||
),
|
||||
},
|
||||
).as('failureReported')
|
||||
|
||||
cy.intercept('POST', 'https://transloaditstatus.com/client_error', {
|
||||
statusCode: 200,
|
||||
body: '{}',
|
||||
})
|
||||
|
||||
// FIRST ATTEMPT
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait(['@createAssembly', '@tusCall', '@failureReported'])
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Upload failed')
|
||||
|
||||
// SETUP for SECOND ATTEMPT
|
||||
const assemblyIDAttempt2 = '6a3fa40e527d4d73989fce678232a5e1'
|
||||
const tusIDAttempt2 = 'b8ebed4af4981880faf29b0e9596b25e'
|
||||
cy.intercept('POST', 'https://api2.transloadit.com/assemblies', {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(
|
||||
createAssemblyStatus({
|
||||
ok: 'ASSEMBLY_UPLOADING',
|
||||
message: 'The Assembly is still in the process of being uploaded.',
|
||||
assembly_id: assemblyIDAttempt2,
|
||||
tus_url: 'https://api2-test.transloadit.com/resumable/files/attempt2',
|
||||
bytes_expected: 263871,
|
||||
}),
|
||||
),
|
||||
}).as('createAssembly-attempt2')
|
||||
|
||||
cy.intercept(
|
||||
'POST',
|
||||
'https://api2-test.transloadit.com/resumable/files/attempt2',
|
||||
{
|
||||
statusCode: 201,
|
||||
headers: {
|
||||
'Upload-Length': '263871',
|
||||
'Upload-Offset': '0',
|
||||
Location: `https://api2-test.transloadit.com/resumable/files/${tusIDAttempt2}`,
|
||||
},
|
||||
times: 1,
|
||||
},
|
||||
).as('tusCall-attempt2')
|
||||
|
||||
cy.intercept(
|
||||
'PATCH',
|
||||
`https://api2-test.transloadit.com/resumable/files/${tusIDAttempt2}`,
|
||||
{
|
||||
statusCode: 204,
|
||||
headers: {
|
||||
'Upload-Length': '263871',
|
||||
'Upload-Offset': '263871',
|
||||
'Tus-Resumable': '1.0.0',
|
||||
},
|
||||
times: 1,
|
||||
},
|
||||
)
|
||||
cy.intercept(
|
||||
'HEAD',
|
||||
`https://api2-test.transloadit.com/resumable/files/${tusIDAttempt2}`,
|
||||
{ statusCode: 204 },
|
||||
)
|
||||
|
||||
cy.intercept(
|
||||
'GET',
|
||||
`https://api2-test.transloadit.com/assemblies/${assemblyIDAttempt2}`,
|
||||
{
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(
|
||||
createAssemblyStatus({
|
||||
ok: 'ASSEMBLY_COMPLETED',
|
||||
http_code: 200,
|
||||
message: 'The Assembly was successfully completed.',
|
||||
assembly_id: assemblyIDAttempt2,
|
||||
bytes_received: 263871,
|
||||
bytes_expected: 263871,
|
||||
}),
|
||||
),
|
||||
},
|
||||
).as('assemblyCompleted-attempt2')
|
||||
|
||||
// SECOND ATTEMPT
|
||||
cy.get('.uppy-StatusBar-actions > .uppy-c-btn').click()
|
||||
cy.wait([
|
||||
'@createAssembly-attempt2',
|
||||
'@tusCall-attempt2',
|
||||
'@assemblyCompleted-attempt2',
|
||||
])
|
||||
})
|
||||
|
||||
it('should complete on retry', () => {
|
||||
cy.intercept('/assemblies/*').as('assemblies')
|
||||
cy.intercept('/resumable/*').as('resumable')
|
||||
|
||||
cy.get('@file-input').selectFile(
|
||||
[
|
||||
'cypress/fixtures/images/cat.jpg',
|
||||
'cypress/fixtures/images/traffic.jpg',
|
||||
],
|
||||
{ force: true },
|
||||
)
|
||||
|
||||
cy.intercept('POST', 'https://transloaditstatus.com/client_error', {
|
||||
statusCode: 200,
|
||||
body: '{}',
|
||||
})
|
||||
|
||||
cy.intercept(
|
||||
{ method: 'POST', pathname: '/assemblies', times: 1 },
|
||||
{ statusCode: 500, body: {} },
|
||||
).as('failedAssemblyCreation')
|
||||
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait('@failedAssemblyCreation')
|
||||
|
||||
cy.get('button[data-cy=retry]').click()
|
||||
|
||||
cy.wait(['@assemblies', '@resumable'])
|
||||
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
|
||||
it('should complete when resuming after pause', () => {
|
||||
cy.intercept({ path: '/assemblies', method: 'POST' }).as('createAssemblies')
|
||||
cy.intercept({ path: '/resumable/files/', method: 'POST' }).as(
|
||||
'firstUpload',
|
||||
)
|
||||
cy.intercept({ path: '/resumable/files/*', method: 'PATCH' }).as(
|
||||
'secondUpload',
|
||||
)
|
||||
|
||||
cy.get('@file-input').selectFile(
|
||||
[
|
||||
'cypress/fixtures/images/cat.jpg',
|
||||
'cypress/fixtures/images/traffic.jpg',
|
||||
],
|
||||
{ force: true },
|
||||
)
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
|
||||
cy.wait('@createAssemblies')
|
||||
|
||||
// wait for the upload to start, then pause
|
||||
cy.wait('@firstUpload')
|
||||
cy.get('button[data-cy=togglePauseResume]').click()
|
||||
|
||||
cy.wait(300) // Wait an arbitrary amount of time as a user would do.
|
||||
|
||||
cy.get('button[data-cy=togglePauseResume]').click()
|
||||
|
||||
cy.wait('@secondUpload')
|
||||
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
})
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
import {
|
||||
runRemoteUnsplashUploadTest,
|
||||
runRemoteUrlImageUploadTest,
|
||||
} from './reusable-tests.ts'
|
||||
|
||||
// NOTE: we have to use different files to upload per test
|
||||
// because we are uploading to https://tusd.tusdemo.net,
|
||||
// constantly uploading the same images gives a different cached result (or something).
|
||||
describe('Dashboard with Tus', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/dashboard-tus')
|
||||
cy.get('.uppy-Dashboard-input:first').as('file-input')
|
||||
cy.intercept('/files/*').as('tus')
|
||||
cy.intercept({ method: 'POST', pathname: '/files' }).as('post')
|
||||
cy.intercept({ method: 'PATCH', pathname: '/files/*' }).as('patch')
|
||||
})
|
||||
|
||||
it('should upload cat image successfully', () => {
|
||||
cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', {
|
||||
force: true,
|
||||
})
|
||||
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait(['@post', '@patch']).then(() => {
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
})
|
||||
|
||||
it('should start exponential backoff when receiving HTTP 429', () => {
|
||||
cy.get('@file-input').selectFile('cypress/fixtures/images/baboon.png', {
|
||||
force: true,
|
||||
})
|
||||
|
||||
cy.intercept(
|
||||
{ method: 'PATCH', pathname: '/files/*', times: 2 },
|
||||
{ statusCode: 429, body: {} },
|
||||
).as('patch')
|
||||
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait('@tus').then(() => {
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
})
|
||||
|
||||
it('should upload remote image with URL plugin', () => {
|
||||
runRemoteUrlImageUploadTest()
|
||||
})
|
||||
|
||||
it('should upload remote image with Unsplash plugin', () => {
|
||||
runRemoteUnsplashUploadTest()
|
||||
})
|
||||
})
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
describe('dashboard-ui', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/dashboard-ui')
|
||||
cy.get('.uppy-Dashboard-input:first').as('file-input')
|
||||
cy.get('.uppy-Dashboard-AddFiles').as('drop-target')
|
||||
})
|
||||
|
||||
it('should not throw when calling uppy.destroy()', () => {
|
||||
cy.get('@file-input').selectFile(
|
||||
[
|
||||
'cypress/fixtures/images/cat.jpg',
|
||||
'cypress/fixtures/images/traffic.jpg',
|
||||
],
|
||||
{ force: true },
|
||||
)
|
||||
|
||||
cy.window().then(({ uppy }) => {
|
||||
expect(uppy.destroy()).to.not.throw
|
||||
})
|
||||
})
|
||||
|
||||
it('should render thumbnails', () => {
|
||||
cy.get('@file-input').selectFile(
|
||||
[
|
||||
'cypress/fixtures/images/cat.jpg',
|
||||
'cypress/fixtures/images/traffic.jpg',
|
||||
],
|
||||
{ force: true },
|
||||
)
|
||||
cy.get('.uppy-Dashboard-Item-previewImg')
|
||||
.should('have.length', 2)
|
||||
.each((element) => expect(element).attr('src').to.include('blob:'))
|
||||
})
|
||||
|
||||
it('should support drag&drop', () => {
|
||||
cy.get('@drop-target').selectFile(
|
||||
[
|
||||
'cypress/fixtures/images/cat.jpg',
|
||||
'cypress/fixtures/images/cat-symbolic-link',
|
||||
'cypress/fixtures/images/cat-symbolic-link.jpg',
|
||||
'cypress/fixtures/images/traffic.jpg',
|
||||
],
|
||||
{ action: 'drag-drop' },
|
||||
)
|
||||
|
||||
cy.get('.uppy-Dashboard-Item').should('have.length', 4)
|
||||
cy.get('.uppy-Dashboard-Item-previewImg')
|
||||
.should('have.length', 3)
|
||||
.each((element) => expect(element).attr('src').to.include('blob:'))
|
||||
cy.window().then(({ uppy }) => {
|
||||
expect(
|
||||
JSON.stringify(uppy.getFiles().map((file) => file.meta.relativePath)),
|
||||
).to.be.equal('[null,null,null,null]')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
describe.skip('@uppy/vue', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/dashboard-vue')
|
||||
cy.get('input[type="file"]').first().as('file-input')
|
||||
})
|
||||
|
||||
it('should render headless components in Vue 3 correctly', () => {
|
||||
cy.get('@file-input').selectFile(
|
||||
[
|
||||
'cypress/fixtures/images/cat.jpg',
|
||||
'cypress/fixtures/images/traffic.jpg',
|
||||
],
|
||||
{ force: true },
|
||||
)
|
||||
|
||||
// Test FilesList shows files correctly
|
||||
cy.get('ul[data-uppy-element="files-list"]').should('exist')
|
||||
cy.get('ul[data-uppy-element="files-list"] li').should('have.length', 2)
|
||||
|
||||
// Test FilesGrid shows files correctly
|
||||
cy.get('div[data-uppy-element="files-grid"]').should('exist')
|
||||
cy.get('div[data-uppy-element="files-grid"] div.uppy-reset').should(
|
||||
'have.length',
|
||||
2,
|
||||
)
|
||||
|
||||
// Test UploadButton is functional
|
||||
cy.get('#files-grid button[data-uppy-element="upload-button"]')
|
||||
.should('exist')
|
||||
.and('contain', 'Upload')
|
||||
.and('not.be.disabled')
|
||||
.click()
|
||||
|
||||
// Check if button shows progress during upload
|
||||
cy.get('#files-grid button[data-uppy-element="upload-button"] span').should(
|
||||
'contain',
|
||||
'Uploaded',
|
||||
)
|
||||
// Check if cancel button appears during upload
|
||||
cy.get('#files-grid button[data-uppy-element="cancel-button"]')
|
||||
.should('exist')
|
||||
.and('contain', 'Cancel')
|
||||
|
||||
cy.get('#files-grid button[data-uppy-element="upload-button"] span').should(
|
||||
'contain',
|
||||
'Complete',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
import {
|
||||
interceptCompanionUrlMetaRequest,
|
||||
interceptCompanionUrlRequest,
|
||||
runRemoteUnsplashUploadTest,
|
||||
runRemoteUrlImageUploadTest,
|
||||
} from './reusable-tests.ts'
|
||||
|
||||
describe('Dashboard with XHR', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/dashboard-xhr')
|
||||
})
|
||||
|
||||
it('should upload remote image with URL plugin', () => {
|
||||
runRemoteUrlImageUploadTest()
|
||||
})
|
||||
|
||||
it('should return correct file name with URL plugin from remote image with Content-Disposition', () => {
|
||||
const fileName = `DALL·E IMG_9078 - 学中文 🤑`
|
||||
cy.get('[data-cy="Url"]').click()
|
||||
cy.get('.uppy-Url-input').type(
|
||||
'http://localhost:4678/file-with-content-disposition',
|
||||
)
|
||||
interceptCompanionUrlMetaRequest()
|
||||
cy.get('.uppy-Url-importButton').click()
|
||||
cy.wait('@url-meta').then(() => {
|
||||
cy.get('.uppy-Dashboard-Item-name').should('contain', fileName)
|
||||
cy.get('.uppy-Dashboard-Item-status').should('contain', '84 KB')
|
||||
})
|
||||
})
|
||||
|
||||
it('should return correct file name with URL plugin from remote image without Content-Disposition', () => {
|
||||
cy.get('[data-cy="Url"]').click()
|
||||
cy.get('.uppy-Url-input').type('http://localhost:4678/file-no-headers')
|
||||
interceptCompanionUrlMetaRequest()
|
||||
cy.get('.uppy-Url-importButton').click()
|
||||
cy.wait('@url-meta').then(() => {
|
||||
cy.get('.uppy-Dashboard-Item-name').should('contain', 'file-no')
|
||||
cy.get('.uppy-Dashboard-Item-status').should('contain', '0')
|
||||
})
|
||||
})
|
||||
|
||||
it('should return correct file name even when Companion doesnt supply it', () => {
|
||||
cy.intercept('POST', 'http://localhost:3020/url/meta', {
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
body: JSON.stringify({ size: 123, type: 'image/jpeg' }),
|
||||
}).as('url')
|
||||
|
||||
cy.get('[data-cy="Url"]').click()
|
||||
cy.get('.uppy-Url-input').type(
|
||||
'http://localhost:4678/file-with-content-disposition',
|
||||
)
|
||||
interceptCompanionUrlMetaRequest()
|
||||
cy.get('.uppy-Url-importButton').click()
|
||||
cy.wait('@url-meta').then(() => {
|
||||
cy.get('.uppy-Dashboard-Item-name').should('contain', 'file-with')
|
||||
cy.get('.uppy-Dashboard-Item-status').should('contain', '123 B')
|
||||
})
|
||||
})
|
||||
|
||||
it('should upload unknown size files', () => {
|
||||
cy.get('[data-cy="Url"]').click()
|
||||
cy.get('.uppy-Url-input').type('http://localhost:4678/unknown-size')
|
||||
cy.get('.uppy-Url-importButton').click()
|
||||
interceptCompanionUrlRequest()
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait('@url').then(() => {
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
})
|
||||
|
||||
it('should upload remote image with Unsplash plugin', () => {
|
||||
runRemoteUnsplashUploadTest()
|
||||
})
|
||||
})
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
describe('@uppy/react', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/react')
|
||||
cy.get('#dashboard .uppy-Dashboard-input:first').as('dashboard-input')
|
||||
cy.get('#modal .uppy-Dashboard-input:first').as('modal-input')
|
||||
cy.get('#drag-drop .uppy-DragDrop-input').as('dragdrop-input')
|
||||
})
|
||||
|
||||
it('should render Dashboard in React and show thumbnails', () => {
|
||||
cy.get('@dashboard-input').selectFile(
|
||||
[
|
||||
'cypress/fixtures/images/cat.jpg',
|
||||
'cypress/fixtures/images/traffic.jpg',
|
||||
],
|
||||
{ force: true },
|
||||
)
|
||||
cy.get('#dashboard .uppy-Dashboard-Item-previewImg')
|
||||
.should('have.length', 2)
|
||||
.each((element) => expect(element).attr('src').to.include('blob:'))
|
||||
})
|
||||
|
||||
it('should render Dashboard with Remote Sources plugin pack', () => {
|
||||
const sources = [
|
||||
'My Device',
|
||||
'Google Drive',
|
||||
'OneDrive',
|
||||
'Unsplash',
|
||||
'Zoom',
|
||||
'Link',
|
||||
]
|
||||
cy.get('#dashboard .uppy-DashboardTab-name').each((item, index, list) => {
|
||||
expect(list).to.have.length(6)
|
||||
// Returns the current element from the loop
|
||||
expect(Cypress.$(item).text()).to.eq(sources[index])
|
||||
})
|
||||
})
|
||||
|
||||
it('should render Modal in React and show thumbnails', () => {
|
||||
cy.get('#open').click()
|
||||
cy.get('@modal-input').selectFile(
|
||||
[
|
||||
'cypress/fixtures/images/cat.jpg',
|
||||
'cypress/fixtures/images/traffic.jpg',
|
||||
],
|
||||
{ force: true },
|
||||
)
|
||||
cy.get('#modal .uppy-Dashboard-Item-previewImg')
|
||||
.should('have.length', 2)
|
||||
.each((element) => expect(element).attr('src').to.include('blob:'))
|
||||
})
|
||||
|
||||
it('should render Drag & Drop in React and create a thumbail with @uppy/thumbnail-generator', () => {
|
||||
const spy = cy.spy()
|
||||
|
||||
// @ts-ignore fix me
|
||||
cy.window().then(({ uppy }) => uppy.on('thumbnail:generated', spy))
|
||||
cy.get('@dragdrop-input').selectFile(
|
||||
[
|
||||
'cypress/fixtures/images/cat.jpg',
|
||||
'cypress/fixtures/images/traffic.jpg',
|
||||
],
|
||||
{ force: true },
|
||||
)
|
||||
// not sure how I can accurately wait for the thumbnail
|
||||
cy.wait(1000).then(() => expect(spy).to.be.called)
|
||||
})
|
||||
})
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
/* global cy */
|
||||
|
||||
export const interceptCompanionUrlRequest = () =>
|
||||
cy
|
||||
.intercept({ method: 'POST', url: 'http://localhost:3020/url/get' })
|
||||
.as('url')
|
||||
export const interceptCompanionUrlMetaRequest = () =>
|
||||
cy
|
||||
.intercept({ method: 'POST', url: 'http://localhost:3020/url/meta' })
|
||||
.as('url-meta')
|
||||
|
||||
export function runRemoteUrlImageUploadTest() {
|
||||
cy.get('[data-cy="Url"]').click()
|
||||
cy.get('.uppy-Url-input').type(
|
||||
'https://raw.githubusercontent.com/transloadit/uppy/main/e2e/cypress/fixtures/images/cat.jpg',
|
||||
)
|
||||
cy.get('.uppy-Url-importButton').click()
|
||||
interceptCompanionUrlRequest()
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait('@url').then(() => {
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
}
|
||||
|
||||
export function runRemoteUnsplashUploadTest() {
|
||||
cy.get('[data-cy="Unsplash"]').click()
|
||||
cy.get('.uppy-SearchProvider-input').type('book')
|
||||
cy.intercept({
|
||||
method: 'GET',
|
||||
url: 'http://localhost:3020/search/unsplash/list?q=book',
|
||||
}).as('unsplash-list')
|
||||
cy.get('.uppy-SearchProvider-searchButton').click()
|
||||
cy.wait('@unsplash-list')
|
||||
// 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.intercept({
|
||||
method: 'POST',
|
||||
url: 'http://localhost:3020/search/unsplash/get/*',
|
||||
}).as('unsplash-get')
|
||||
cy.get('.uppy-StatusBar-actionBtn--upload').click()
|
||||
cy.wait('@unsplash-get').then(() => {
|
||||
cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
|
||||
})
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
// ***********************************************
|
||||
// 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) => { ... })
|
||||
//
|
||||
|
||||
import { createFakeFile } from './createFakeFile.ts'
|
||||
|
||||
Cypress.Commands.add('createFakeFile', createFakeFile)
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
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,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
// ***********************************************************
|
||||
// This example support/e2e.ts 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.js using ES2015 syntax:
|
||||
import './commands.ts'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
||||
// @ts-ignore
|
||||
import installLogsCollector from 'cypress-terminal-report/src/installLogsCollector.js'
|
||||
|
||||
installLogsCollector()
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
// ***********************************************************
|
||||
// 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<any, any>
|
||||
}
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
import fs from 'node:fs/promises'
|
||||
import prompts from 'prompts'
|
||||
|
||||
/**
|
||||
* Utility function that strips indentation from multi-line strings.
|
||||
* Inspired from https://github.com/dmnd/dedent.
|
||||
*/
|
||||
function dedent(strings, ...parts) {
|
||||
const nonSpacingChar = /\S/m.exec(strings[0])
|
||||
if (nonSpacingChar == null) return ''
|
||||
|
||||
const indent =
|
||||
nonSpacingChar.index -
|
||||
strings[0].lastIndexOf('\n', nonSpacingChar.index) -
|
||||
1
|
||||
const dedentEachLine = (str) =>
|
||||
str
|
||||
.split('\n')
|
||||
.map((line, i) => line.slice(i && indent))
|
||||
.join('\n')
|
||||
let returnLines = dedentEachLine(
|
||||
strings[0].slice(nonSpacingChar.index),
|
||||
indent,
|
||||
)
|
||||
for (let i = 1; i < strings.length; i++) {
|
||||
returnLines += String(parts[i - 1]) + dedentEachLine(strings[i], indent)
|
||||
}
|
||||
return returnLines
|
||||
}
|
||||
|
||||
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>',
|
||||
` <li><a href="${name}/index.html">${name}</a></li>\n </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)}`)
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
import http from 'node:http'
|
||||
|
||||
const requestListener = (req, res) => {
|
||||
const endpoint = req.url
|
||||
|
||||
switch (endpoint) {
|
||||
case '/file-with-content-disposition': {
|
||||
const fileName = `DALL·E IMG_9078 - 学中文 🤑`
|
||||
res.setHeader(
|
||||
'Content-Disposition',
|
||||
`attachment; filename="ASCII-name.zip"; filename*=UTF-8''${encodeURIComponent(fileName)}`,
|
||||
)
|
||||
res.setHeader('Content-Type', 'image/jpeg')
|
||||
res.setHeader('Content-Length', '86500')
|
||||
break
|
||||
}
|
||||
case '/file-no-headers':
|
||||
break
|
||||
|
||||
case '/unknown-size':
|
||||
{
|
||||
res.setHeader('Content-Type', 'text/html; charset=UTF-8')
|
||||
res.setHeader('Transfer-Encoding', 'chunked')
|
||||
const chunkSize = 1e5
|
||||
if (req.method === 'GET') {
|
||||
let i = 0
|
||||
const interval = setInterval(() => {
|
||||
if (i >= 10) {
|
||||
// 1MB
|
||||
clearInterval(interval)
|
||||
res.end()
|
||||
return
|
||||
}
|
||||
res.write(
|
||||
Buffer.from(
|
||||
Array.from({ length: chunkSize }, () => '1').join(''),
|
||||
),
|
||||
)
|
||||
res.write('\n')
|
||||
i++
|
||||
}, 10)
|
||||
} else if (req.method === 'HEAD') {
|
||||
res.end()
|
||||
} else {
|
||||
throw new Error('Unhandled method')
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
res.writeHead(404).end('Unhandled request')
|
||||
}
|
||||
|
||||
res.end()
|
||||
}
|
||||
|
||||
export default function startMockServer(host, port) {
|
||||
const server = http.createServer(requestListener)
|
||||
server.listen(port, host, () => {
|
||||
console.log(`Mock server is running on http://${host}:${port}`)
|
||||
})
|
||||
}
|
||||
|
||||
// startMockServer('localhost', 4678)
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
{
|
||||
"name": "e2e",
|
||||
"private": true,
|
||||
"author": "Merlijn Vos <merlijn@soverin.net>",
|
||||
"description": "End-to-end test suite for Uppy",
|
||||
"scripts": {
|
||||
"client:start": "parcel --no-autoinstall 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/google-drive-picker": "workspace:^",
|
||||
"@uppy/google-photos-picker": "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": {
|
||||
"@parcel/transformer-vue": "^2.9.3",
|
||||
"cypress": "^13.0.0",
|
||||
"cypress-terminal-report": "^6.0.0",
|
||||
"deep-freeze": "^0.0.1",
|
||||
"execa": "^9.6.0",
|
||||
"parcel": "^2.9.3",
|
||||
"process": "^0.11.10",
|
||||
"prompts": "^2.4.2",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"typescript": "~5.4",
|
||||
"vue": "^3.2.33"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"noEmit": true,
|
||||
"target": "es2020",
|
||||
"lib": ["es2020", "dom"],
|
||||
"types": ["cypress"]
|
||||
},
|
||||
"include": ["cypress/**/*.ts"]
|
||||
}
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
"scripts": {
|
||||
"client": "light-server -p 3000 -s client",
|
||||
"server": "node ./server/index.js",
|
||||
"start": "yarn run server & yarn run client",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"start": "yarn run server & yarn run client"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc && vite build",
|
||||
"test": "vitest run --browser.headless",
|
||||
"dev": "vite",
|
||||
"serve": "vite preview"
|
||||
},
|
||||
|
|
@ -22,8 +23,12 @@
|
|||
"devDependencies": {
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"@vitest/browser": "^3.2.4",
|
||||
"playwright": "^1.53.2",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.2.0"
|
||||
"vite": "^7.0.3",
|
||||
"vitest": "^3.2.4",
|
||||
"vitest-browser-react": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@ import UppyRemoteSources from '@uppy/remote-sources'
|
|||
import UppyScreenCapture from '@uppy/screen-capture'
|
||||
import Tus from '@uppy/tus'
|
||||
import UppyWebcam from '@uppy/webcam'
|
||||
import React, { useRef, useState } from 'react'
|
||||
import CustomDropzone from './CustomDropzone.tsx'
|
||||
import { RemoteSource } from './RemoteSource.js'
|
||||
import ScreenCapture from './ScreenCapture.tsx'
|
||||
import Webcam from './Webcam.tsx'
|
||||
import { useRef, useState } from 'react'
|
||||
import CustomDropzone from './CustomDropzone'
|
||||
import { RemoteSource } from './RemoteSource'
|
||||
import ScreenCapture from './ScreenCapture'
|
||||
import Webcam from './Webcam'
|
||||
|
||||
import './app.css'
|
||||
import '@uppy/react/dist/styles.css'
|
||||
|
|
|
|||
124
examples/react/test/index.test.tsx
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import { userEvent } from '@vitest/browser/context'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { render } from 'vitest-browser-react'
|
||||
import App from '../src/App'
|
||||
|
||||
const createMockFile = (name: string, type: string, size: number = 1024) => {
|
||||
return new File(['test content'], name, { type })
|
||||
}
|
||||
|
||||
describe('App', () => {
|
||||
test('renders all main sections and upload button is initially disabled', async () => {
|
||||
const screen = render(<App />)
|
||||
|
||||
await expect.element(screen.getByText('With list')).toBeInTheDocument()
|
||||
await expect.element(screen.getByText('With grid')).toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByText('With custom dropzone'))
|
||||
.toBeInTheDocument()
|
||||
|
||||
const uploadButton = screen.getByRole('button', { name: /upload/i })
|
||||
await expect.element(uploadButton).toBeInTheDocument()
|
||||
await expect.element(uploadButton).toBeDisabled()
|
||||
})
|
||||
|
||||
test('can add and remove files and upload', async () => {
|
||||
const screen = render(<App />)
|
||||
|
||||
const fileInput = document.getElementById(
|
||||
'uppy-dropzone-file-input',
|
||||
) as Element
|
||||
await userEvent.upload(fileInput, createMockFile('test.txt', 'text/plain'))
|
||||
|
||||
// for list and grid
|
||||
for (const element of screen.getByText('test.txt').elements()) {
|
||||
await expect.element(element).toBeInTheDocument()
|
||||
}
|
||||
await screen.getByText('remove').first().click()
|
||||
for (const element of screen.getByText('test.txt').elements()) {
|
||||
await expect.element(element).not.toBeInTheDocument()
|
||||
}
|
||||
|
||||
await userEvent.upload(fileInput, createMockFile('test.txt', 'text/plain'))
|
||||
await screen.getByRole('button', { name: /upload/i }).click()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: /complete/i }))
|
||||
.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('ScreenCapture Component', () => {
|
||||
test('renders with title, control buttons, and close functionality works', async () => {
|
||||
const screen = render(<App />)
|
||||
|
||||
await screen.getByRole('button', { name: 'Screen Capture' }).click()
|
||||
|
||||
await expect
|
||||
.element(screen.getByRole('heading', { name: 'Screen Capture' }))
|
||||
.toBeInTheDocument()
|
||||
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Screenshot' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Record' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Stop' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Submit' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Discard' }))
|
||||
.toBeInTheDocument()
|
||||
|
||||
const closeButton = screen.getByText('✕')
|
||||
await closeButton.click()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Webcam Component', () => {
|
||||
test('renders with title, control buttons, and close functionality works', async () => {
|
||||
const screen = render(<App />)
|
||||
|
||||
await screen.getByRole('button', { name: 'Webcam' }).click()
|
||||
|
||||
await expect
|
||||
.element(screen.getByRole('heading', { name: 'Camera' }))
|
||||
.toBeInTheDocument()
|
||||
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Snapshot' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Record' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Stop' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Submit' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Discard' }))
|
||||
.toBeInTheDocument()
|
||||
|
||||
const closeButton = screen.getByText('✕')
|
||||
await closeButton.click()
|
||||
})
|
||||
})
|
||||
|
||||
describe('RemoteSource Component', () => {
|
||||
test('renders login button and login interaction works', async () => {
|
||||
const screen = render(<App />)
|
||||
|
||||
await screen.getByRole('button', { name: 'Dropbox' }).click()
|
||||
|
||||
const loginButton = screen.getByRole('button', { name: 'Login' })
|
||||
await expect.element(loginButton).toBeInTheDocument()
|
||||
|
||||
await loginButton.click()
|
||||
await expect.element(loginButton).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
14
examples/react/vitest.config.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import tailwindcss from '@tailwindcss/vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
test: {
|
||||
browser: {
|
||||
enabled: true,
|
||||
provider: 'playwright',
|
||||
instances: [{ browser: 'chromium' }],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"test": "vitest run --browser.headless",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
|
|
@ -26,10 +27,13 @@
|
|||
"@sveltejs/kit": "^2.16.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@vitest/browser": "^3.2.4",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^6.2.5"
|
||||
"vite": "^6.2.5",
|
||||
"vitest": "^3.2.4",
|
||||
"vitest-browser-svelte": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
126
examples/sveltekit/test/index.test.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
import { userEvent } from '@vitest/browser/context'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { render } from 'vitest-browser-svelte'
|
||||
import App from '../src/routes/+page.svelte'
|
||||
|
||||
const createMockFile = (name: string, type: string, size: number = 1024) => {
|
||||
return new File(['test content'], name, { type })
|
||||
}
|
||||
|
||||
describe('App', () => {
|
||||
test('renders all main sections and upload button is initially disabled', async () => {
|
||||
const screen = render(App)
|
||||
|
||||
await expect.element(screen.getByText('With list')).toBeInTheDocument()
|
||||
await expect.element(screen.getByText('With grid')).toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByText('With custom dropzone'))
|
||||
.toBeInTheDocument()
|
||||
|
||||
const uploadButton = screen.getByRole('button', { name: /upload/i })
|
||||
await expect.element(uploadButton).toBeInTheDocument()
|
||||
await expect.element(uploadButton).toBeDisabled()
|
||||
})
|
||||
|
||||
test('can add and remove files and upload', async () => {
|
||||
const screen = render(App)
|
||||
|
||||
const fileInput = document.getElementById(
|
||||
'uppy-dropzone-file-input',
|
||||
) as Element
|
||||
await userEvent.upload(fileInput, createMockFile('test.txt', 'text/plain'))
|
||||
|
||||
// for list and grid
|
||||
for (const element of screen.getByText('test.txt').elements()) {
|
||||
await expect.element(element).toBeInTheDocument()
|
||||
}
|
||||
await screen.getByText('remove').first().click()
|
||||
for (const element of screen.getByText('test.txt').elements()) {
|
||||
await expect.element(element).not.toBeInTheDocument()
|
||||
}
|
||||
|
||||
await userEvent.upload(fileInput, createMockFile('test.txt', 'text/plain'))
|
||||
await screen.getByRole('button', { name: /upload/i }).click()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: /complete/i }))
|
||||
.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('ScreenCapture Component', () => {
|
||||
test('renders with title, control buttons, and close functionality works', async () => {
|
||||
const screen = render(App)
|
||||
|
||||
await screen
|
||||
.getByRole('button', { name: 'Screen Capture', exact: true })
|
||||
.click()
|
||||
|
||||
await expect
|
||||
.element(screen.getByRole('heading', { name: 'Screen Capture' }))
|
||||
.toBeInTheDocument()
|
||||
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Screenshot' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Record' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Stop' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Submit' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Discard' }))
|
||||
.toBeInTheDocument()
|
||||
|
||||
const closeButton = screen.getByText('✕')
|
||||
await closeButton.click()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Webcam Component', () => {
|
||||
test('renders with title, control buttons, and close functionality works', async () => {
|
||||
const screen = render(App)
|
||||
|
||||
await screen.getByRole('button', { name: 'Webcam', exact: true }).click()
|
||||
|
||||
await expect
|
||||
.element(screen.getByRole('heading', { name: 'Camera' }))
|
||||
.toBeInTheDocument()
|
||||
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Snapshot' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Record' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Stop' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Submit' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Discard' }))
|
||||
.toBeInTheDocument()
|
||||
|
||||
const closeButton = screen.getByText('✕')
|
||||
await closeButton.click()
|
||||
})
|
||||
})
|
||||
|
||||
describe('RemoteSource Component', () => {
|
||||
test('renders login button and login interaction works', async () => {
|
||||
const screen = render(App)
|
||||
|
||||
await screen.getByRole('button', { name: 'Dropbox', exact: true }).click()
|
||||
|
||||
const loginButton = screen.getByRole('button', { name: 'Login' })
|
||||
await expect.element(loginButton).toBeInTheDocument()
|
||||
|
||||
await loginButton.click()
|
||||
await expect.element(loginButton).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
14
examples/sveltekit/vitest.config.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { sveltekit } from '@sveltejs/kit/vite'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit()],
|
||||
test: {
|
||||
browser: {
|
||||
enabled: true,
|
||||
provider: 'playwright',
|
||||
instances: [{ browser: 'chromium' }],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"test": "vitest run --browser.headless",
|
||||
"preview": "vite preview --port 5050"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
@ -23,7 +24,10 @@
|
|||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@vitejs/plugin-vue": "^5.0.0",
|
||||
"@vitest/browser": "^3.2.4",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"vite": "^6.2.0"
|
||||
"vite": "^6.2.0",
|
||||
"vitest": "^3.2.4",
|
||||
"vitest-browser-vue": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 29 KiB |
126
examples/vue/test/index.test.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
import { userEvent } from '@vitest/browser/context'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { render } from 'vitest-browser-vue'
|
||||
import App from '../src/App.vue'
|
||||
|
||||
const createMockFile = (name: string, type: string, size: number = 1024) => {
|
||||
return new File(['test content'], name, { type })
|
||||
}
|
||||
|
||||
describe('App', () => {
|
||||
test('renders all main sections and upload button is initially disabled', async () => {
|
||||
const screen = render(App)
|
||||
|
||||
await expect.element(screen.getByText('With list')).toBeInTheDocument()
|
||||
await expect.element(screen.getByText('With grid')).toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByText('With custom dropzone'))
|
||||
.toBeInTheDocument()
|
||||
|
||||
const uploadButton = screen.getByRole('button', { name: /upload/i })
|
||||
await expect.element(uploadButton).toBeInTheDocument()
|
||||
await expect.element(uploadButton).toBeDisabled()
|
||||
})
|
||||
|
||||
test('can add and remove files and upload', async () => {
|
||||
const screen = render(App)
|
||||
|
||||
const fileInput = document.getElementById(
|
||||
'uppy-dropzone-file-input',
|
||||
) as Element
|
||||
await userEvent.upload(fileInput, createMockFile('test.txt', 'text/plain'))
|
||||
|
||||
// for list and grid
|
||||
for (const element of screen.getByText('test.txt').elements()) {
|
||||
await expect.element(element).toBeInTheDocument()
|
||||
}
|
||||
await screen.getByText('remove').first().click()
|
||||
for (const element of screen.getByText('test.txt').elements()) {
|
||||
await expect.element(element).not.toBeInTheDocument()
|
||||
}
|
||||
|
||||
await userEvent.upload(fileInput, createMockFile('test.txt', 'text/plain'))
|
||||
await screen.getByRole('button', { name: /upload/i }).click()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: /complete/i }))
|
||||
.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('ScreenCapture Component', () => {
|
||||
test('renders with title, control buttons, and close functionality works', async () => {
|
||||
const screen = render(App)
|
||||
|
||||
await screen
|
||||
.getByRole('button', { name: 'Screen Capture', exact: true })
|
||||
.click()
|
||||
|
||||
await expect
|
||||
.element(screen.getByRole('heading', { name: 'Screen Capture' }))
|
||||
.toBeInTheDocument()
|
||||
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Screenshot' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Record' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Stop' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Submit' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Discard' }))
|
||||
.toBeInTheDocument()
|
||||
|
||||
const closeButton = screen.getByText('✕')
|
||||
await closeButton.click()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Webcam Component', () => {
|
||||
test('renders with title, control buttons, and close functionality works', async () => {
|
||||
const screen = render(App)
|
||||
|
||||
await screen.getByRole('button', { name: 'Webcam', exact: true }).click()
|
||||
|
||||
await expect
|
||||
.element(screen.getByRole('heading', { name: 'Camera' }))
|
||||
.toBeInTheDocument()
|
||||
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Snapshot' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Record' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Stop' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Submit' }))
|
||||
.toBeInTheDocument()
|
||||
await expect
|
||||
.element(screen.getByRole('button', { name: 'Discard' }))
|
||||
.toBeInTheDocument()
|
||||
|
||||
const closeButton = screen.getByText('✕')
|
||||
await closeButton.click()
|
||||
})
|
||||
})
|
||||
|
||||
describe('RemoteSource Component', () => {
|
||||
test('renders login button and login interaction works', async () => {
|
||||
const screen = render(App)
|
||||
|
||||
await screen.getByRole('button', { name: 'Dropbox', exact: true }).click()
|
||||
|
||||
const loginButton = screen.getByRole('button', { name: 'Login' })
|
||||
await expect.element(loginButton).toBeInTheDocument()
|
||||
|
||||
await loginButton.click()
|
||||
await expect.element(loginButton).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
14
examples/vue/vitest.config.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import tailwindcss from '@tailwindcss/vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue(), tailwindcss()],
|
||||
test: {
|
||||
browser: {
|
||||
enabled: true,
|
||||
provider: 'playwright',
|
||||
instances: [{ browser: 'chromium' }],
|
||||
},
|
||||
},
|
||||
})
|
||||
27
package.json
|
|
@ -9,9 +9,7 @@
|
|||
"packages/@uppy/*",
|
||||
"packages/@uppy/angular/projects/uppy/*",
|
||||
"packages/uppy",
|
||||
"private/*",
|
||||
"test/endtoend",
|
||||
"e2e"
|
||||
"private/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "turbo run build build:css --filter='./packages/@uppy/*' --filter='./packages/uppy'",
|
||||
|
|
@ -22,39 +20,20 @@
|
|||
"check:ci": "yarn exec biome ci",
|
||||
"dev": "yarn workspace @uppy-dev/dev dev",
|
||||
"dev:with-companion": "npm-run-all --parallel start:companion dev",
|
||||
"e2e:ci": "start-server-and-test 'npm-run-all --parallel e2e:client start:companion:with-loadbalancer' '1234|3020' e2e:headless",
|
||||
"e2e:client": "yarn workspace e2e client:start",
|
||||
"e2e:cypress": "yarn workspace e2e cypress:open",
|
||||
"e2e:generate": "yarn workspace e2e generate-test",
|
||||
"e2e:headless": "yarn workspace e2e cypress:headless",
|
||||
"e2e": "npm-run-all --parallel build:watch e2e:client start:companion:with-loadbalancer e2e:cypress",
|
||||
"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": "yarn workspace @uppy/companion start:dev",
|
||||
"start:companion:with-loadbalancer": "e2e/start-companion-with-load-balancer.mjs",
|
||||
"test": "turbo run test --filter='./packages/@uppy/*' --filter='./packages/uppy'",
|
||||
"test": "turbo run test --filter='./packages/@uppy/*' --filter='./packages/uppy' --filter='./examples/{react,vue,sveltekit}'",
|
||||
"test:watch": "turbo watch test --filter='./packages/@uppy/*' --filter='./packages/uppy'",
|
||||
"typecheck": "turbo run typecheck --filter='./packages/@uppy/*' --filter='./packages/uppy'",
|
||||
"uploadcdn": "yarn workspace uppy exec -- node upload-to-cdn.js",
|
||||
"version": "changeset version && corepack yarn install --mode=update-lockfile",
|
||||
"release": "changeset publish"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "^18",
|
||||
"@types/webpack-dev-server": "^4",
|
||||
"p-queue": "patch:p-queue@npm%3A8.0.1#~/.yarn/patches/p-queue-npm-8.0.1-fe1ddcd827.patch",
|
||||
"resize-observer-polyfill": "patch:resize-observer-polyfill@npm%3A1.5.1#./.yarn/patches/resize-observer-polyfill-npm-1.5.1-603120e8a0.patch",
|
||||
"start-server-and-test": "patch:start-server-and-test@npm:1.14.0#.yarn/patches/start-server-and-test-npm-1.14.0-841aa34fdf.patch",
|
||||
"uuid@^8.3.2": "patch:uuid@npm:8.3.2#.yarn/patches/uuid-npm-8.3.2-eca0baba53.patch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.0.5",
|
||||
"@changesets/changelog-github": "^0.5.1",
|
||||
"@changesets/cli": "^2.29.5",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"start-server-and-test": "^1.14.0",
|
||||
"turbo": "^2.5.4",
|
||||
"typescript": "^5.8.3",
|
||||
"vue-template-compiler": "workspace:*"
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"packageManager": "yarn@4.4.1+sha224.fd21d9eb5fba020083811af1d4953acc21eeb9f6ff97efd1b3f9d4de",
|
||||
"engines": {
|
||||
|
|
|
|||
|
|
@ -441,6 +441,51 @@ describe('AwsS3Multipart', () => {
|
|||
expect(uploadErrorMock.mock.calls.length).toEqual(1)
|
||||
expect(uploadSuccessMock.mock.calls.length).toEqual(1) // This fails for me becuase upload returned early.
|
||||
})
|
||||
|
||||
it('retries signPart when it fails', async () => {
|
||||
// The retry logic for signPart happens in the uploadChunk method of HTTPCommunicationQueue
|
||||
// For a 6MB file, we expect 2 parts, so signPart should be called for each part
|
||||
let callCount = 0
|
||||
const signPartWithRetry = vi.fn((file, { partNumber }) => {
|
||||
callCount++
|
||||
if (callCount === 1) {
|
||||
// First call fails with a retryable error
|
||||
throw { source: { status: 500 } }
|
||||
}
|
||||
return {
|
||||
url: `https://bucket.s3.us-east-2.amazonaws.com/test/upload/multitest.dat?partNumber=${partNumber}&uploadId=6aeb1980f3fc7ce0b5454d25b71992`,
|
||||
}
|
||||
})
|
||||
|
||||
const core = new Core().use(AwsS3Multipart, {
|
||||
shouldUseMultipart: true,
|
||||
retryDelays: [10],
|
||||
createMultipartUpload: vi.fn(() => ({
|
||||
uploadId: '6aeb1980f3fc7ce0b5454d25b71992',
|
||||
key: 'test/upload/multitest.dat',
|
||||
})),
|
||||
completeMultipartUpload: vi.fn(async () => ({ location: 'test' })),
|
||||
abortMultipartUpload: vi.fn(),
|
||||
signPart: signPartWithRetry,
|
||||
uploadPartBytes: vi.fn().mockResolvedValue({ status: 200 }),
|
||||
listParts: undefined as any,
|
||||
})
|
||||
const fileSize = 5 * MB + 1 * MB
|
||||
|
||||
core.addFile({
|
||||
source: 'vi',
|
||||
name: 'multitest.dat',
|
||||
type: 'application/octet-stream',
|
||||
data: new File([new Uint8Array(fileSize)], '', {
|
||||
type: 'application/octet-stream',
|
||||
}),
|
||||
})
|
||||
|
||||
await core.upload()
|
||||
|
||||
// Should be called 3 times: 1 failed + 1 retry + 1 for second part
|
||||
expect(signPartWithRetry).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('dynamic companionHeader', () => {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@
|
|||
"@types/cors": "2.8.6",
|
||||
"@types/eslint": "^8.2.0",
|
||||
"@types/express-session": "1.17.3",
|
||||
"@types/http-proxy": "^1",
|
||||
"@types/jsonwebtoken": "8.3.7",
|
||||
"@types/lodash": "4.14.191",
|
||||
"@types/morgan": "1.7.37",
|
||||
|
|
@ -85,6 +86,8 @@
|
|||
"@types/request": "2.48.8",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"@types/ws": "8.5.3",
|
||||
"execa": "^9.6.0",
|
||||
"http-proxy": "^1.18.1",
|
||||
"jest": "^29.0.0",
|
||||
"nock": "^13.1.3",
|
||||
"supertest": "6.2.4",
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ const startCompanion = ({ name, port }) =>
|
|||
? ['--watch-path', 'packages/@uppy/companion/src', '--watch']
|
||||
: []),
|
||||
],
|
||||
cwd: new URL('../', import.meta.url),
|
||||
cwd: new URL('../../../../', import.meta.url),
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
// Note: these env variables will override anything set in .env
|
||||
|
|
@ -71,6 +71,8 @@ const startCompanion = ({ name, port }) =>
|
|||
COMPANION_ENABLE_URL_ENDPOINT: 'true',
|
||||
COMPANION_LOGGER_PROCESS_NAME: name,
|
||||
COMPANION_CLIENT_ORIGINS: 'true',
|
||||
COMPANION_DATADIR: '/tmp',
|
||||
COMPANION_DOMAIN: 'localhost:3020',
|
||||
},
|
||||
})
|
||||
|
||||
|
|
@ -25,8 +25,18 @@ export default function Dropzone(props: DropzoneProps) {
|
|||
)
|
||||
|
||||
return (
|
||||
<div className="uppy-reset" data-uppy-element="dropzone">
|
||||
<input {...getInputProps()} className="uppy:hidden" />
|
||||
<div
|
||||
className="uppy-reset"
|
||||
data-uppy-element="dropzone"
|
||||
role="presentation"
|
||||
tabIndex={0}
|
||||
>
|
||||
<input
|
||||
{...getInputProps()}
|
||||
tabIndex={-1}
|
||||
name="uppy-dropzone-file-input"
|
||||
className="uppy:hidden"
|
||||
/>
|
||||
<div
|
||||
{...getRootProps()}
|
||||
style={{
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
|
@ -17,7 +17,7 @@ CompressorPlugin.prototype.compress = async (blob: File) => {
|
|||
}
|
||||
|
||||
const sampleImage = fs.readFileSync(
|
||||
path.join(__dirname, '../../../../e2e/cypress/fixtures/images/image.jpg'),
|
||||
path.join(__dirname, '../fixtures/image.jpg'),
|
||||
)
|
||||
|
||||
const file1 = {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@
|
|||
"preact": "^10.5.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/deep-freeze": "^0",
|
||||
"cssnano": "^7.0.7",
|
||||
"deep-freeze": "^0.0.1",
|
||||
"jsdom": "^26.1.0",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-cli": "^11.0.1",
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import prettierBytes from '@transloadit/prettier-bytes'
|
|||
import type { Body, Meta } from '@uppy/core'
|
||||
import type { Locale } from '@uppy/utils/lib/Translator'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
// @ts-expect-error trying to import a file from outside the package
|
||||
import DeepFrozenStore from '../../../../e2e/cypress/fixtures/DeepFrozenStore.mjs'
|
||||
import BasePlugin, {
|
||||
type DefinePluginOpts,
|
||||
type PluginOpts,
|
||||
|
|
@ -15,6 +13,8 @@ import Core from './index.js'
|
|||
import { debugLogger } from './loggers.js'
|
||||
import AcquirerPlugin1 from './mocks/acquirerPlugin1.js'
|
||||
import AcquirerPlugin2 from './mocks/acquirerPlugin2.js'
|
||||
// @ts-expect-error untyped
|
||||
import DeepFrozenStore from './mocks/DeepFrozenStore.mjs'
|
||||
import InvalidPlugin from './mocks/invalidPlugin.js'
|
||||
import InvalidPluginWithoutId from './mocks/invalidPluginWithoutId.js'
|
||||
import InvalidPluginWithoutType from './mocks/invalidPluginWithoutType.js'
|
||||
|
|
@ -23,7 +23,7 @@ import UIPlugin from './UIPlugin.js'
|
|||
import type { State } from './Uppy.js'
|
||||
|
||||
const sampleImage = fs.readFileSync(
|
||||
path.join(__dirname, '../../../../e2e/cypress/fixtures/images/image.jpg'),
|
||||
path.join(__dirname, '../../compressor/fixtures/image.jpg'),
|
||||
)
|
||||
|
||||
// @ts-expect-error type object can be second argument
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import deepFreeze from 'deep-freeze'
|
||||
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
|
||||
/**
|
||||
* Default store + deepFreeze on setState to make sure nothing is mutated accidentally
|
||||
*/
|
||||
|
|
@ -10,7 +10,8 @@
|
|||
"build": "tsc --build tsconfig.build.json",
|
||||
"build:css": "sass --load-path=../../ src/style.scss dist/style.css && postcss dist/style.css -u cssnano -o dist/style.min.css",
|
||||
"typecheck": "tsc --build",
|
||||
"test": "vitest run --environment=jsdom --silent='passed-only'"
|
||||
"test": "vitest run --silent='passed-only'",
|
||||
"test:e2e": "vitest watch --project browser --browser.headless false"
|
||||
},
|
||||
"keywords": [
|
||||
"file uploader",
|
||||
|
|
@ -46,6 +47,7 @@
|
|||
"@uppy/status-bar": "workspace:^",
|
||||
"@uppy/url": "workspace:^",
|
||||
"@uppy/webcam": "workspace:^",
|
||||
"@vitest/browser": "^3.2.4",
|
||||
"cssnano": "^7.0.7",
|
||||
"jsdom": "^26.1.0",
|
||||
"postcss": "^8.5.6",
|
||||
|
|
|
|||
33
packages/@uppy/dashboard/src/index.browser.test.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import Uppy from '@uppy/core'
|
||||
import { page, userEvent } from '@vitest/browser/context'
|
||||
import { expect, test } from 'vitest'
|
||||
import Dashboard from './Dashboard.js'
|
||||
|
||||
// Normally you would use one of vitest's framework renderers, such as vitest-browser-react,
|
||||
// but that's overkill for us so we write our own plain HTML renderer.
|
||||
function render(html: string) {
|
||||
document.body.innerHTML = ''
|
||||
const root = document.createElement('main')
|
||||
root.innerHTML = html
|
||||
document.body.appendChild(root)
|
||||
return root
|
||||
}
|
||||
|
||||
test('Basic Dashboard functionality works in the browser', async () => {
|
||||
render('<div id="uppy"></div>')
|
||||
new Uppy().use(Dashboard, {
|
||||
target: '#uppy',
|
||||
inline: true,
|
||||
metaFields: [{ id: 'license', name: 'License' }],
|
||||
})
|
||||
|
||||
await expect.element(page.getByText('Drop files here')).toBeVisible()
|
||||
const fileInput = document.getElementsByClassName('uppy-Dashboard-input')[0]
|
||||
await userEvent.upload(fileInput, new File(['Hello, World!'], 'test.txt'))
|
||||
await expect.element(page.getByText('test.txt')).toBeVisible()
|
||||
await page.getByTitle('Edit file test.txt').click()
|
||||
const licenseInput = page.getByLabelText('License')
|
||||
await expect.element(licenseInput).toBeVisible()
|
||||
await userEvent.fill(licenseInput.element(), 'MIT')
|
||||
await page.getByText('Save changes').click()
|
||||
})
|
||||
30
packages/@uppy/dashboard/vitest.config.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
projects: [
|
||||
{
|
||||
test: {
|
||||
name: 'unit',
|
||||
include: [
|
||||
'src/**/*.test.{ts,tsx}',
|
||||
'!src/**/*.browser.test.{ts,tsx}',
|
||||
],
|
||||
environment: 'jsdom',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: {
|
||||
name: 'browser',
|
||||
include: ['src/**/*.browser.test.{ts,tsx}'],
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: true,
|
||||
provider: 'playwright',
|
||||
instances: [{ browser: 'chromium' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
|
@ -44,6 +44,7 @@
|
|||
"devDependencies": {
|
||||
"@uppy/core": "workspace:^",
|
||||
"jsdom": "^26.1.0",
|
||||
"msw": "^2.10.4",
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.2.4",
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
import { once } from 'node:events'
|
||||
import { createServer } from 'node:http'
|
||||
import Core from '@uppy/core'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { HttpResponse, http } from 'msw'
|
||||
import { setupServer } from 'msw/node'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import Transloadit from './index.ts'
|
||||
import 'whatwg-fetch'
|
||||
|
||||
// Mock EventSource for testing
|
||||
global.EventSource = vi.fn(() => ({
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
close: vi.fn(),
|
||||
}))
|
||||
|
||||
describe('Transloadit', () => {
|
||||
it('Does not leave lingering progress if getAssemblyOptions fails', () => {
|
||||
const error = new Error('expected failure')
|
||||
|
|
@ -69,56 +76,79 @@ describe('Transloadit', () => {
|
|||
)
|
||||
})
|
||||
|
||||
// For some reason this test doesn't pass on CI
|
||||
it.skip('Can start an assembly with no files and no fields', async () => {
|
||||
const server = createServer((req, res) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*')
|
||||
res.setHeader('Access-Control-Allow-Headers', '*')
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
res.end('{"websocket_url":"about:blank"}')
|
||||
}).listen()
|
||||
await once(server, 'listening')
|
||||
const uppy = new Core({
|
||||
autoProceed: false,
|
||||
})
|
||||
it('should complete when resuming after pause', async () => {
|
||||
let assemblyCallCount = 0
|
||||
let firstUploadCallCount = 0
|
||||
let secondUploadCallCount = 0
|
||||
|
||||
const server = setupServer(
|
||||
http.post('*/assemblies', ({ request }) => {
|
||||
assemblyCallCount++
|
||||
return HttpResponse.json({
|
||||
assembly_id: 'test-assembly-id',
|
||||
websocket_url: 'ws://localhost:8080',
|
||||
tus_url: 'https://localhost/resumable/files/',
|
||||
assembly_ssl_url: 'https://localhost/assemblies/test-assembly-id',
|
||||
ok: 'ASSEMBLY_EXECUTING',
|
||||
})
|
||||
}),
|
||||
http.get('*/assemblies/*', () => {
|
||||
return HttpResponse.json({
|
||||
assembly_id: 'test-assembly-id',
|
||||
ok: 'ASSEMBLY_COMPLETED',
|
||||
results: {},
|
||||
})
|
||||
}),
|
||||
http.post('*/resumable/files/', () => {
|
||||
firstUploadCallCount++
|
||||
return HttpResponse.json({
|
||||
tus_enabled: true,
|
||||
resumable_file_id: `test-file-id-${firstUploadCallCount}`,
|
||||
})
|
||||
}),
|
||||
http.patch('*/resumable/files/*', () => {
|
||||
secondUploadCallCount++
|
||||
return HttpResponse.json({
|
||||
ok: 'RESUMABLE_FILE_UPLOADED',
|
||||
})
|
||||
}),
|
||||
http.post('https://transloaditstatus.com/client_error', () => {
|
||||
return HttpResponse.json({})
|
||||
}),
|
||||
)
|
||||
|
||||
server.listen({ onUnhandledRequest: 'error' })
|
||||
|
||||
const uppy = new Core()
|
||||
const successSpy = vi.fn()
|
||||
uppy.on('complete', successSpy)
|
||||
uppy.use(Transloadit, {
|
||||
service: `http://localhost:${server.address().port}`,
|
||||
alwaysRunAssembly: true,
|
||||
params: {
|
||||
auth: { key: 'some auth key string' },
|
||||
template_id: 'some template id string',
|
||||
assemblyOptions: {
|
||||
params: {
|
||||
auth: { key: 'test-auth-key' },
|
||||
template_id: 'test-template-id',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await uppy.upload()
|
||||
server.closeAllConnections()
|
||||
await new Promise((resolve) => server.close(resolve))
|
||||
})
|
||||
|
||||
// For some reason this test doesn't pass on CI
|
||||
it.skip('Can start an assembly with no files and some fields', async () => {
|
||||
const server = createServer((req, res) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*')
|
||||
res.setHeader('Access-Control-Allow-Headers', '*')
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
res.end('{"websocket_url":"about:blank"}')
|
||||
}).listen()
|
||||
await once(server, 'listening')
|
||||
const uppy = new Core({
|
||||
autoProceed: false,
|
||||
uppy.addFile({
|
||||
source: 'test',
|
||||
name: 'cat.jpg',
|
||||
data: Buffer.from('test file content'),
|
||||
})
|
||||
uppy.use(Transloadit, {
|
||||
service: `http://localhost:${server.address().port}`,
|
||||
alwaysRunAssembly: true,
|
||||
params: {
|
||||
auth: { key: 'some auth key string' },
|
||||
template_id: 'some template id string',
|
||||
},
|
||||
fields: ['hasOwnProperty'],
|
||||
uppy.addFile({
|
||||
source: 'test',
|
||||
name: 'traffic.jpg',
|
||||
data: Buffer.from('test file content 2'),
|
||||
})
|
||||
|
||||
uppy.upload()
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
uppy.pauseAll()
|
||||
await uppy.upload()
|
||||
server.closeAllConnections()
|
||||
await new Promise((resolve) => server.close(resolve))
|
||||
|
||||
expect(successSpy).toHaveBeenCalled()
|
||||
|
||||
server.close()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@
|
|||
"scripts": {
|
||||
"build": "tsc --build tsconfig.build.json",
|
||||
"build:css": "sass --load-path=../../ src/style.scss dist/style.css && postcss dist/style.css -u cssnano -o dist/style.min.css",
|
||||
"typecheck": "tsc --build"
|
||||
"typecheck": "tsc --build",
|
||||
"test": "vitest run --silent='passed-only'",
|
||||
"test:e2e": "vitest watch --project browser --browser.headless false"
|
||||
},
|
||||
"keywords": [
|
||||
"file uploader",
|
||||
|
|
@ -37,10 +39,12 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@uppy/core": "workspace:^",
|
||||
"@vitest/browser": "^3.2.4",
|
||||
"cssnano": "^7.0.7",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-cli": "^11.0.1",
|
||||
"sass": "^1.89.2",
|
||||
"typescript": "^5.8.3"
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
57
packages/@uppy/url/src/Url.browser.test.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import Uppy from '@uppy/core'
|
||||
import { page, userEvent } from '@vitest/browser/context'
|
||||
import { expect, test } from 'vitest'
|
||||
import Url from './Url.js'
|
||||
|
||||
// Normally you would use one of vitest's framework renderers, such as vitest-browser-react,
|
||||
// but that's overkill for us so we write our own plain HTML renderer.
|
||||
function render(html: string) {
|
||||
document.body.innerHTML = ''
|
||||
const root = document.createElement('main')
|
||||
root.innerHTML = html
|
||||
document.body.appendChild(root)
|
||||
return root
|
||||
}
|
||||
|
||||
test('should return correct file name with URL plugin from remote image with Content-Disposition', async () => {
|
||||
render('<div id="uppy"></div>')
|
||||
const uppy = new Uppy().use(Url, {
|
||||
companionUrl: 'http://localhost:3020',
|
||||
target: '#uppy',
|
||||
})
|
||||
|
||||
const mockServerUrl = 'http://localhost:62450'
|
||||
await page
|
||||
.getByPlaceholder('Enter URL to import a file')
|
||||
.fill(`${mockServerUrl}/file-with-content-disposition`)
|
||||
await page.getByText('Import').click()
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
await uppy.upload()
|
||||
|
||||
const file = uppy.getFiles()[0]
|
||||
expect(file.name).toBe('DALL·E IMG_9078 - 学中文 🤑')
|
||||
expect(file.type).toBe('image/jpeg')
|
||||
expect(file.size).toBe(86500)
|
||||
})
|
||||
|
||||
test('should return correct file name with URL plugin from remote image without Content-Disposition', async () => {
|
||||
render('<div id="uppy"></div>')
|
||||
const uppy = new Uppy().use(Url, {
|
||||
companionUrl: 'http://localhost:3020',
|
||||
target: '#uppy',
|
||||
})
|
||||
|
||||
const mockServerUrl = 'http://localhost:62450'
|
||||
await page
|
||||
.getByPlaceholder('Enter URL to import a file')
|
||||
.fill(`${mockServerUrl}/file-no-headers`)
|
||||
await page.getByText('Import').click()
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
await uppy.upload()
|
||||
|
||||
const file = uppy.getFiles()[0]
|
||||
expect(file, 'file does not exist').toBeTruthy()
|
||||
expect(file.name).toBe('file-no-headers')
|
||||
expect(file.type).toBe('application/octet-stream')
|
||||
expect(file.size).toBeNull()
|
||||
})
|
||||
21
packages/@uppy/url/vitest.config.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
projects: [
|
||||
{
|
||||
test: {
|
||||
name: 'browser',
|
||||
include: ['src/**/*.browser.test.{ts,tsx}'],
|
||||
globalSetup: './vitest.setup.ts',
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: true,
|
||||
provider: 'playwright',
|
||||
instances: [{ browser: 'chromium' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
70
packages/@uppy/url/vitest.setup.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { spawn } from 'node:child_process'
|
||||
import { createServer } from 'node:http'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { setTimeout } from 'node:timers/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { TestProject } from 'vitest/node'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
const mockServerPort = 62450
|
||||
|
||||
export default async function setup(project: TestProject) {
|
||||
const mockServer = createServer((req, res) => {
|
||||
const fileName = `DALL·E IMG_9078 - 学中文 🤑`
|
||||
|
||||
if (req.url === '/file-with-content-disposition') {
|
||||
res.writeHead(200, {
|
||||
'content-disposition': `attachment; filename="ASCII-name.zip"; filename*=UTF-8''${encodeURIComponent(
|
||||
fileName,
|
||||
)}`,
|
||||
'content-type': 'image/jpeg',
|
||||
'content-length': '86500',
|
||||
})
|
||||
|
||||
if (req.method === 'HEAD') {
|
||||
res.end()
|
||||
} else {
|
||||
res.end('mock image data')
|
||||
}
|
||||
} else if (req.url === '/file-no-headers') {
|
||||
// Explicitly remove any default content-type
|
||||
res.removeHeader('content-type')
|
||||
res.writeHead(200, {})
|
||||
|
||||
if (req.method === 'HEAD') {
|
||||
res.end()
|
||||
} else {
|
||||
res.end('mock file content')
|
||||
}
|
||||
} else {
|
||||
res.writeHead(404)
|
||||
res.end()
|
||||
}
|
||||
})
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
mockServer.listen(mockServerPort, 'localhost', resolve)
|
||||
})
|
||||
|
||||
const companionProcess = spawn(
|
||||
'node',
|
||||
[join(__dirname, '../../@uppy/companion/test/with-load-balancer.mjs')],
|
||||
{
|
||||
stdio: 'inherit',
|
||||
cwd: join(__dirname, '../../../..'),
|
||||
env: {
|
||||
...process.env,
|
||||
// Pass the mock server URL to companion if needed
|
||||
MOCK_SERVER_URL: `http://localhost:${mockServerPort}`,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
await setTimeout(1000)
|
||||
|
||||
return () => {
|
||||
companionProcess.kill()
|
||||
mockServer.close()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"name": "@types/jasmine",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"description": "This package is here to avoid type conflict between @types/jasmine used by Angular and @types/mocha used by everything else.",
|
||||
"types": "types.d.ts"
|
||||
}
|
||||
2
private/@types/jasmine/types.d.ts
vendored
|
|
@ -1,2 +0,0 @@
|
|||
declare module 'jasmine' {}
|
||||
declare module 'jasminewd2' {}
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* This script can be used to initiate the transition for a plugin from ESM source to
|
||||
* TS source. It will rename the files, update the imports, and add a `tsconfig.json`.
|
||||
*/
|
||||
|
||||
import { existsSync } from 'node:fs'
|
||||
import {
|
||||
appendFile,
|
||||
open,
|
||||
opendir,
|
||||
readFile,
|
||||
rm,
|
||||
writeFile,
|
||||
} from 'node:fs/promises'
|
||||
import { createRequire } from 'node:module'
|
||||
import { basename, extname, join } from 'node:path'
|
||||
import { argv } from 'node:process'
|
||||
|
||||
const packageRoot = new URL(`../../packages/${argv[2]}/`, import.meta.url)
|
||||
let dir
|
||||
|
||||
try {
|
||||
dir = await opendir(new URL('./src/', packageRoot), { recursive: true })
|
||||
} catch (cause) {
|
||||
throw new Error(`Unable to find package "${argv[2]}"`, { cause })
|
||||
}
|
||||
const packageJSON = JSON.parse(
|
||||
await readFile(new URL('./package.json', packageRoot), 'utf-8'),
|
||||
)
|
||||
|
||||
if (packageJSON.type !== 'module') {
|
||||
throw new Error('Cannot convert non-ESM package to TS')
|
||||
}
|
||||
|
||||
const uppyDeps = new Set(
|
||||
Object.keys(packageJSON.dependencies || {})
|
||||
.concat(Object.keys(packageJSON.peerDependencies || {}))
|
||||
.concat(Object.keys(packageJSON.devDependencies || {}))
|
||||
.filter((pkg) => pkg.startsWith('@uppy/')),
|
||||
)
|
||||
|
||||
// We want TS to check the source files so it doesn't use outdated (or missing) types:
|
||||
const paths = Object.fromEntries(
|
||||
(function* generatePaths() {
|
||||
const require = createRequire(packageRoot)
|
||||
for (const pkg of uppyDeps) {
|
||||
const nickname = pkg.slice('@uppy/'.length)
|
||||
// eslint-disable-next-line import/no-dynamic-require
|
||||
const pkgJson = require(`../${nickname}/package.json`)
|
||||
if (pkgJson.main) {
|
||||
yield [
|
||||
pkg,
|
||||
[`../${nickname}/${pkgJson.main.replace(/^(\.\/)?lib\//, 'src/')}`],
|
||||
]
|
||||
}
|
||||
yield [`${pkg}/lib/*`, [`../${nickname}/src/*`]]
|
||||
}
|
||||
})(),
|
||||
)
|
||||
const references = Array.from(uppyDeps, (pkg) => ({
|
||||
path: `../${pkg.slice('@uppy/'.length)}/tsconfig.build.json`,
|
||||
}))
|
||||
|
||||
const depsNotYetConvertedToTS = references.filter(
|
||||
(ref) => !existsSync(new URL(ref.path, packageRoot)),
|
||||
)
|
||||
|
||||
if (depsNotYetConvertedToTS.length) {
|
||||
// We need to first convert the dependencies, otherwise we won't be working with the correct types.
|
||||
throw new Error('Some dependencies have not yet been converted to TS', {
|
||||
cause: depsNotYetConvertedToTS.map((ref) =>
|
||||
ref.path.replace(/^\.\./, '@uppy'),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
let tsConfig
|
||||
try {
|
||||
tsConfig = await open(new URL('./tsconfig.json', packageRoot), 'wx')
|
||||
} catch (cause) {
|
||||
throw new Error('It seems this package has already been transitioned to TS', {
|
||||
cause,
|
||||
})
|
||||
}
|
||||
|
||||
for await (const dirent of dir) {
|
||||
if (!dirent.isDirectory()) {
|
||||
const { name } = dirent
|
||||
const ext = extname(name)
|
||||
if (ext !== '.js' && ext !== '.jsx') continue // eslint-disable-line no-continue
|
||||
const filePath =
|
||||
basename(dirent.path) === name
|
||||
? dirent.path // Some versions of Node.js give the full path as dirent.path.
|
||||
: join(dirent.path, name) // Others supply only the path to the parent.
|
||||
await writeFile(
|
||||
`${filePath.slice(0, -ext.length)}${ext.replace('js', 'ts')}`,
|
||||
(await readFile(filePath, 'utf-8'))
|
||||
.replace(
|
||||
// The following regex aims to capture all imports and reexports of local .js(x) files to replace it to .ts(x)
|
||||
// It's far from perfect and will have false positives and false negatives.
|
||||
/((?:^|\n)(?:import(?:\s+\w+\s+from)?|export\s*\*\s*from|(?:import|export)\s*(?:\{[^}]*\}|\*\s*as\s+\w+\s)\s*from)\s*["']\.\.?\/[^'"]+\.)js(x?["'])/g, // eslint-disable-line max-len
|
||||
'$1ts$2',
|
||||
)
|
||||
.replace(
|
||||
// The following regex aims to capture all local package.json imports.
|
||||
/\nimport \w+ from ['"]..\/([^'"]+\/)*package.json['"]\n/g,
|
||||
(originalImport) =>
|
||||
`\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n` +
|
||||
`// @ts-ignore We don't want TS to generate types for the package.json${originalImport}`,
|
||||
),
|
||||
)
|
||||
await rm(filePath)
|
||||
}
|
||||
}
|
||||
|
||||
await tsConfig.writeFile(
|
||||
`${JSON.stringify(
|
||||
{
|
||||
extends: '../../../tsconfig.shared',
|
||||
compilerOptions: {
|
||||
emitDeclarationOnly: false,
|
||||
noEmit: true,
|
||||
paths,
|
||||
},
|
||||
include: ['./package.json', './src/**/*.*'],
|
||||
references,
|
||||
},
|
||||
undefined,
|
||||
2,
|
||||
)}\n`,
|
||||
)
|
||||
|
||||
await tsConfig.close()
|
||||
|
||||
await writeFile(
|
||||
new URL('./tsconfig.build.json', packageRoot),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
extends: '../../../tsconfig.shared',
|
||||
compilerOptions: {
|
||||
outDir: './lib',
|
||||
paths,
|
||||
resolveJsonModule: false,
|
||||
rootDir: './src',
|
||||
},
|
||||
include: ['./src/**/*.*'],
|
||||
exclude: ['./src/**/*.test.ts'],
|
||||
references,
|
||||
},
|
||||
undefined,
|
||||
2,
|
||||
)}\n`,
|
||||
)
|
||||
|
||||
await appendFile(new URL('./.npmignore', packageRoot), `\ntsconfig.*\n`)
|
||||
|
||||
console.log('Done')
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "vue-template-compiler",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"description": "This package is there only to avoid a version conflict in the Vue2 example."
|
||||
}
|
||||