Skip to content

Commit

Permalink
Merge branch 'main' into react-18
Browse files Browse the repository at this point in the history
* main:
  Make Cypress more stable & add e2e test for error events when upload fails (#3662)
  @uppy/webcam: refactor to ESM (#3686)
  @uppy/audio: fix types (#3689)
  Only deploy on companion changes (#3677)
  @uppy/image-editor: refactor to ESM (#3685)
  Add `save` translation to Spanish locale (#3678)
  • Loading branch information
Murderlon committed May 4, 2022
2 parents fc8e655 + cb0380c commit 0652f4d
Show file tree
Hide file tree
Showing 35 changed files with 908 additions and 850 deletions.
4 changes: 3 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,11 @@ module.exports = {
'packages/@uppy/file-input/src/**/*.js',
'packages/@uppy/form/src/**/*.js',
'packages/@uppy/google-drive/src/**/*.js',
'packages/@uppy/image-editor/src/**/*.js',
'packages/@uppy/svelte/src/**/*.js',
'packages/@uppy/svelte/rollup.config.js',
'packages/@uppy/vue/src/**/*.js',
'packages/@uppy/webcam/src/**/*.js',
],
parser: 'espree',
parserOptions: {
Expand Down Expand Up @@ -433,7 +435,7 @@ module.exports = {
},
{
files: ['e2e/**/*.ts', 'e2e/**/*.js', 'e2e/**/*.jsx'],
rules: { 'import/no-extraneous-dependencies': 'off' },
rules: { 'import/no-extraneous-dependencies': 'off', 'no-unused-expressions': 'off' },
},
],
}
2 changes: 2 additions & 0 deletions .github/workflows/companion-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ name: Companion Deploy
on:
push:
branches: [main]
paths:
- 'packages/@uppy/companion/**'

jobs:
docker:
Expand Down
2 changes: 1 addition & 1 deletion e2e/cypress.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"baseUrl": "http://localhost:1234",
"defaultCommandTimeout": 8000
"defaultCommandTimeout": 16000
}
32 changes: 15 additions & 17 deletions e2e/cypress/integration/dashboard-compressor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ function uglierBytes (text) {
return Number(text.slice(0, -2))
}

throw new Error(`Not what the computer thinks a human-readable size string look like: ${text}`)
throw new Error(
`Not what the computer thinks a human-readable size string look like: ${text}`,
)
}

describe('dashboard-compressor', () => {
Expand All @@ -33,27 +35,23 @@ describe('dashboard-compressor', () => {
sizeBeforeCompression.push(uglierBytes(text))
})

cy.get('.uppy-StatusBar-actionBtn--upload').click()

cy.get('.uppy-Informer p[role="alert"]', {
timeout: 12000,
}).should('be.visible')

cy.window().then(({ uppy }) => {
for (const file of uppy.getFiles()) {
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)
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),
)
}
})
})

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()
})
})
})
28 changes: 27 additions & 1 deletion e2e/cypress/integration/dashboard-tus.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ type Tus = BaseTus & {
requests: { isPaused: boolean }
}

// 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')
Expand All @@ -13,6 +16,29 @@ describe('Dashboard with Tus', () => {
cy.intercept('http://localhost:3020/search/unsplash/*').as('unsplash')
})

it.only('should emit `error` and `upload-error` events on failed POST request', () => {
cy.get('@file-input').attachFile(['images/traffic.jpg'])

const error = cy.spy()
const uploadError = cy.spy()
cy.window().then(({ uppy }) => {
uppy.on('upload-error', uploadError)
uppy.on('error', error)
})

cy.get('.uppy-StatusBar-actionBtn--upload').click()

cy.intercept(
{ method: 'POST', url: 'https://tusd.tusdemo.net/*', times: 1 },
{ statusCode: 401, body: { code: 401, message: 'Expired JWT Token' } },
).as('post')

cy.wait('@post').then(() => {
expect(error).to.be.called
expect(uploadError).to.be.called
})
})

it('should upload cat image successfully', () => {
cy.get('@file-input').attachFile('images/cat.jpg')
cy.get('.uppy-StatusBar-actionBtn--upload').click()
Expand All @@ -23,7 +49,7 @@ describe('Dashboard with Tus', () => {
})

it('should start exponential backoff when receiving HTTP 429', () => {
cy.get('@file-input').attachFile(['images/cat.jpg', 'images/traffic.jpg'])
cy.get('@file-input').attachFile(['images/baboon.png'])
cy.get('.uppy-StatusBar-actionBtn--upload').click()

cy.intercept(
Expand Down
2 changes: 1 addition & 1 deletion packages/@uppy/audio/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type AudioLocale from './generatedLocale'

export interface AudioOptions extends PluginOptions {
target?: PluginTarget
showVideoSourceDropdown?: boolean
showAudioSourceDropdown?: boolean
locale?: AudioLocale
}

Expand Down
2 changes: 2 additions & 0 deletions packages/@uppy/core/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ export type FilesAddedCallback<TMeta> = (files: UppyFile<TMeta>[]) => void;
export type FileRemovedCallback<TMeta> = (file: UppyFile<TMeta>, reason: FileRemoveReason) => void;
export type UploadCallback = (data: { id: string, fileIDs: string[] }) => void;
export type ProgressCallback = (progress: number) => void;
export type PreProcessCompleteCallback<TMeta> = (file: UppyFile<TMeta>) => void;
export type UploadProgressCallback<TMeta> = (file: UppyFile<TMeta>, progress: FileProgress) => void;
export type UploadSuccessCallback<TMeta> = (file: UppyFile<TMeta>, response: SuccessResponse) => void
export type UploadCompleteCallback<TMeta> = (result: UploadResult<TMeta>) => void
Expand All @@ -225,6 +226,7 @@ export interface UppyEventMap<TMeta = Record<string, unknown>> {
'file-removed': FileRemovedCallback<TMeta>
'upload': UploadCallback
'progress': ProgressCallback
'preprocess-complete': PreProcessCompleteCallback<TMeta>
'upload-progress': UploadProgressCallback<TMeta>
'upload-success': UploadSuccessCallback<TMeta>
'complete': UploadCompleteCallback<TMeta>
Expand Down
1 change: 1 addition & 0 deletions packages/@uppy/image-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"main": "lib/index.js",
"style": "dist/style.min.css",
"types": "types/index.d.ts",
"type": "module",
"keywords": [
"file uploader",
"upload",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const CropperImport = require('cropperjs')
const { h, Component } = require('preact')
import CropperImport from 'cropperjs'
import { h, Component } from 'preact'

// @TODO A silly hack that we can get rid of when moving to ESM.
// @TODO A silly hack that we can get rid of when we start publishing ESM to the npm package.
// eslint-disable-next-line no-underscore-dangle
const Cropper = CropperImport.__esModule ? CropperImport.default : CropperImport

module.exports = class Editor extends Component {
export default class Editor extends Component {
constructor (props) {
super(props)
this.state = { rotationAngle: 0, rotationDelta: 0 }
Expand Down
144 changes: 144 additions & 0 deletions packages/@uppy/image-editor/src/ImageEditor.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { UIPlugin } from '@uppy/core'
import { h } from 'preact'

import Editor from './Editor.jsx'
import packageJson from '../package.json'
import locale from './locale.js'

export default class ImageEditor extends UIPlugin {
static VERSION = packageJson.version

constructor (uppy, opts) {
super(uppy, opts)
this.id = this.opts.id || 'ImageEditor'
this.title = 'Image Editor'
this.type = 'editor'

this.defaultLocale = locale

const defaultCropperOptions = {
viewMode: 1,
background: false,
autoCropArea: 1,
responsive: true,
croppedCanvasOptions: {},
}

const defaultActions = {
revert: true,
rotate: true,
granularRotate: true,
flip: true,
zoomIn: true,
zoomOut: true,
cropSquare: true,
cropWidescreen: true,
cropWidescreenVertical: true,
}

const defaultOptions = {
quality: 0.8,
}

this.opts = {
...defaultOptions,
...opts,
actions: {
...defaultActions,
...opts.actions,
},
cropperOptions: {
...defaultCropperOptions,
...opts.cropperOptions,
},
}

this.i18nInit()
}

// eslint-disable-next-line class-methods-use-this
canEditFile (file) {
if (!file.type || file.isRemote) {
return false
}

const fileTypeSpecific = file.type.split('/')[1]

if (/^(jpe?g|gif|png|bmp|webp)$/.test(fileTypeSpecific)) {
return true
}

return false
}

save = () => {
const saveBlobCallback = (blob) => {
const { currentImage } = this.getPluginState()

this.uppy.setFileState(currentImage.id, {
data: blob,
size: blob.size,
preview: null,
})

const updatedFile = this.uppy.getFile(currentImage.id)
this.uppy.emit('thumbnail:request', updatedFile)
this.setPluginState({
currentImage: updatedFile,
})
this.uppy.emit('file-editor:complete', updatedFile)
}

const { currentImage } = this.getPluginState()

this.cropper.getCroppedCanvas(this.opts.cropperOptions.croppedCanvasOptions).toBlob(
saveBlobCallback,
currentImage.type,
this.opts.quality,
)
}

storeCropperInstance = (cropper) => {
this.cropper = cropper
}

selectFile = (file) => {
this.uppy.emit('file-editor:start', file)
this.setPluginState({
currentImage: file,
})
}

install () {
this.setPluginState({
currentImage: null,
})

const { target } = this.opts
if (target) {
this.mount(target, this)
}
}

uninstall () {
this.unmount()
}

render () {
const { currentImage } = this.getPluginState()

if (currentImage === null || currentImage.isRemote) {
return null
}

return (
<Editor
currentImage={currentImage}
storeCropperInstance={this.storeCropperInstance}
save={this.save}
opts={this.opts}
i18n={this.i18n}
/>
)
}
}
Loading

0 comments on commit 0652f4d

Please sign in to comment.