From b579a994f852761db5638d7bd8e14f133ef45c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Sat, 29 Aug 2020 19:02:05 +0800 Subject: [PATCH 01/21] fix: Upload order issue (#272) --- src/AjaxUploader.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AjaxUploader.jsx b/src/AjaxUploader.jsx index 2a7612b..de47a61 100644 --- a/src/AjaxUploader.jsx +++ b/src/AjaxUploader.jsx @@ -178,8 +178,9 @@ class AjaxUploader extends Component { props.onError(err, ret, file); }, }; - this.reqs[uid] = request(requestOption); + onStart(file); + this.reqs[uid] = request(requestOption); }); }); } From 62489bfcebfecf6f3ddb30bf4ebcf0af08acfb2c Mon Sep 17 00:00:00 2001 From: zombiej Date: Sat, 29 Aug 2020 19:03:26 +0800 Subject: [PATCH 02/21] chore: Bump 3.2.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1383f26..1ec9858 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rc-upload", - "version": "3.2.0", + "version": "3.2.1", "description": "upload ui component for react", "keywords": [ "react", From 7787ab2aebda9e4218c090ca94d866e6ab15a241 Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Fri, 21 Aug 2020 11:09:06 +0800 Subject: [PATCH 03/21] chore: use father --- .eslintrc.js | 25 ++++ .fatherrc.js | 9 ++ .gitignore | 2 + .prettierrc | 6 + .travis.yml | 2 +- examples/asyncAction.html | 0 examples/{asyncAction.js => asyncAction.tsx} | 11 +- examples/beforeUpload.html | 1 - .../{beforeUpload.js => beforeUpload.tsx} | 11 +- examples/customRequest.html | 1 - .../{customRequest.js => customRequest.tsx} | 7 +- examples/directoryUpload.html | 1 - examples/directoryUpload.js | 49 ------- examples/directoryUpload.tsx | 46 ++++++ examples/drag.html | 1 - examples/{drag.js => drag.tsx} | 24 +++- examples/simple.html | 1 - examples/simple.js | 98 ------------- examples/simple.tsx | 77 ++++++++++ examples/transformFile.html | 1 - .../{transformFile.js => transformFile.tsx} | 7 +- jest.config.js | 4 + now.json | 11 ++ package.json | 38 ++--- src/AjaxUploader.jsx | 132 +++++++++--------- src/attr-accept.js | 3 +- src/uid.js | 3 +- tests/index.js | 2 - tests/request.spec.js | 16 +-- tests/setup.js | 6 + tests/uploader.spec.js | 111 +++++++-------- 31 files changed, 375 insertions(+), 331 deletions(-) create mode 100644 .eslintrc.js create mode 100644 .fatherrc.js create mode 100644 .prettierrc delete mode 100644 examples/asyncAction.html rename examples/{asyncAction.js => asyncAction.tsx} (69%) delete mode 100644 examples/beforeUpload.html rename examples/{beforeUpload.js => beforeUpload.tsx} (73%) delete mode 100644 examples/customRequest.html rename examples/{customRequest.js => customRequest.tsx} (87%) delete mode 100644 examples/directoryUpload.html delete mode 100644 examples/directoryUpload.js create mode 100644 examples/directoryUpload.tsx delete mode 100644 examples/drag.html rename examples/{drag.js => drag.tsx} (66%) delete mode 100644 examples/simple.html delete mode 100644 examples/simple.js create mode 100644 examples/simple.tsx delete mode 100644 examples/transformFile.html rename examples/{transformFile.js => transformFile.tsx} (87%) create mode 100644 jest.config.js create mode 100644 now.json delete mode 100644 tests/index.js create mode 100644 tests/setup.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..29fbf6b --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,25 @@ +const base = require("@umijs/fabric/dist/eslint"); + +module.exports = { + ...base, + rules: { + ...base.rules, + "arrow-parens": 0, + "react/no-array-index-key": 0, + "react/sort-comp": 0, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/no-empty-interface": 0, + "@typescript-eslint/no-inferrable-types": 0, + "react/no-find-dom-node": 0, + "react/require-default-props": 0, + "react/button-has-type": 0, + "no-confusing-arrow": 0, + "import/no-named-as-default-member": 0, + "jsx-a11y/label-has-for": 0, + "jsx-a11y/label-has-associated-control": 0, + "import/no-extraneous-dependencies": 0, + "prefer-destructuring": 0, + "no-underscore-dangle": 0, + "no-param-reassign": 0, + }, +}; diff --git a/.fatherrc.js b/.fatherrc.js new file mode 100644 index 0000000..9d8c16b --- /dev/null +++ b/.fatherrc.js @@ -0,0 +1,9 @@ +export default { + cjs: "babel", + esm: { type: "babel", importLibToEs: true }, + preCommit: { + eslint: true, + prettier: true, + }, + runtimeHelpers: true, +}; diff --git a/.gitignore b/.gitignore index 9d6f5bc..bc73d2e 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ es package-lock.json tmp/ .history +.storybook +.doc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..895b8bd --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "proseWrap": "never", + "printWidth": 100 +} diff --git a/.travis.yml b/.travis.yml index 8edbf2c..f79fa18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ notifications: - yiminghe@gmail.com node_js: -- 6 +- 10 before_install: - | diff --git a/examples/asyncAction.html b/examples/asyncAction.html deleted file mode 100644 index e69de29..0000000 diff --git a/examples/asyncAction.js b/examples/asyncAction.tsx similarity index 69% rename from examples/asyncAction.js rename to examples/asyncAction.tsx index a81ef6f..b75eff4 100644 --- a/examples/asyncAction.js +++ b/examples/asyncAction.tsx @@ -1,11 +1,10 @@ /* eslint no-console:0 */ import React from 'react'; -import ReactDOM from 'react-dom'; -import Upload from 'rc-upload'; +import Upload from '../src/index'; const props = { action: () => { - return new Promise((resolve) => { + return new Promise(resolve => { setTimeout(() => { resolve('/upload.do'); }, 2000); @@ -31,10 +30,12 @@ const Test = () => { }} >
- 开始上传 + + 开始上传 +
); }; -ReactDOM.render(, document.getElementById('__react-content')); +export default Test; diff --git a/examples/beforeUpload.html b/examples/beforeUpload.html deleted file mode 100644 index 48cdce8..0000000 --- a/examples/beforeUpload.html +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/examples/beforeUpload.js b/examples/beforeUpload.tsx similarity index 73% rename from examples/beforeUpload.js rename to examples/beforeUpload.tsx index 8417f28..0160dd3 100644 --- a/examples/beforeUpload.js +++ b/examples/beforeUpload.tsx @@ -1,8 +1,7 @@ /* eslint no-console:0 */ import React from 'react'; -import ReactDOM from 'react-dom'; -import Upload from 'rc-upload'; +import Upload from '../src/index'; const props = { action: '/upload.do', @@ -18,7 +17,7 @@ const props = { }, beforeUpload(file, fileList) { console.log(file, fileList); - return new Promise((resolve) => { + return new Promise(resolve => { console.log('start check'); setTimeout(() => { console.log('check finshed'); @@ -36,10 +35,12 @@ const Test = () => { }} >
- 开始上传 + + 开始上传 +
); }; -ReactDOM.render(, document.getElementById('__react-content')); +export default Test; diff --git a/examples/customRequest.html b/examples/customRequest.html deleted file mode 100644 index 48cdce8..0000000 --- a/examples/customRequest.html +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/examples/customRequest.js b/examples/customRequest.tsx similarity index 87% rename from examples/customRequest.js rename to examples/customRequest.tsx index 9ef7b2d..f11d7fb 100644 --- a/examples/customRequest.js +++ b/examples/customRequest.tsx @@ -1,8 +1,7 @@ /* eslint no-console:0 */ import React from 'react'; -import ReactDOM from 'react-dom'; -import Upload from 'rc-upload'; import axios from 'axios'; +import Upload from '../src/index'; const uploadProps = { action: '/upload.do', @@ -49,7 +48,7 @@ const uploadProps = { withCredentials, headers, onUploadProgress: ({ total, loaded }) => { - onProgress({ percent: Math.round(loaded / total * 100).toFixed(2) }, file); + onProgress({ percent: Math.round((loaded / total) * 100).toFixed(2) }, file); }, }) .then(({ data: response }) => { @@ -81,4 +80,4 @@ const Test = () => { ); }; -ReactDOM.render(, document.getElementById('__react-content')); +export default Test; diff --git a/examples/directoryUpload.html b/examples/directoryUpload.html deleted file mode 100644 index b3a4252..0000000 --- a/examples/directoryUpload.html +++ /dev/null @@ -1 +0,0 @@ -placeholder \ No newline at end of file diff --git a/examples/directoryUpload.js b/examples/directoryUpload.js deleted file mode 100644 index ad035fe..0000000 --- a/examples/directoryUpload.js +++ /dev/null @@ -1,49 +0,0 @@ -/* eslint no-console:0 */ - -import React from 'react'; -import ReactDOM from 'react-dom'; -import Upload from 'rc-upload'; - -class Test extends React.Component { - constructor(props) { - super(props); - this.uploaderProps = { - action: '/upload.do', - data: { a: 1, b: 2 }, - headers: { - Authorization: 'xxxxxxx', - }, - directory: true, - beforeUpload(file) { - console.log('beforeUpload', file.name); - }, - onStart: (file) => { - console.log('onStart', file.name); - }, - onSuccess(file) { - console.log('onSuccess', file); - }, - onProgress(step, file) { - console.log('onProgress', Math.round(step.percent), file.name); - }, - onError(err) { - console.log('onError', err); - }, - }; - } - render() { - return (
- - - -
); - } -} - -ReactDOM.render(, document.getElementById('__react-content')); diff --git a/examples/directoryUpload.tsx b/examples/directoryUpload.tsx new file mode 100644 index 0000000..9d2e29a --- /dev/null +++ b/examples/directoryUpload.tsx @@ -0,0 +1,46 @@ +/* eslint no-console:0 */ + +import React from 'react'; +import Upload from '../src/index'; + +const Test = () => { + const uploaderProps = { + action: '/upload.do', + data: { a: 1, b: 2 }, + headers: { + Authorization: 'xxxxxxx', + }, + directory: true, + beforeUpload(file) { + console.log('beforeUpload', file.name); + }, + onStart: file => { + console.log('onStart', file.name); + }, + onSuccess(file) { + console.log('onSuccess', file); + }, + onProgress(step, file) { + console.log('onProgress', Math.round(step.percent), file.name); + }, + onError(err) { + console.log('onError', err); + }, + }; + + return ( +
+
+ + 开始上传 + +
+
+ ); +}; + +export default Test; diff --git a/examples/drag.html b/examples/drag.html deleted file mode 100644 index 48cdce8..0000000 --- a/examples/drag.html +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/examples/drag.js b/examples/drag.tsx similarity index 66% rename from examples/drag.js rename to examples/drag.tsx index da307e6..3f4a184 100644 --- a/examples/drag.js +++ b/examples/drag.tsx @@ -1,8 +1,6 @@ /* eslint no-console:0 */ - import React from 'react'; -import ReactDOM from 'react-dom'; -import Upload from 'rc-upload'; +import Upload from '../src/index'; const props = { action: '/upload.do', @@ -11,7 +9,7 @@ const props = { beforeUpload(file) { console.log('beforeUpload', file.name); }, - onStart: (file) => { + onStart: file => { console.log('onStart', file.name); }, onSuccess(file) { @@ -27,4 +25,20 @@ const props = { // openFileDialogOnClick: false }; -ReactDOM.render(, document.getElementById('__react-content')); +const Test = () => { + return ( +
+
+ + 开始上传 + +
+
+ ); +}; + +export default Test; diff --git a/examples/simple.html b/examples/simple.html deleted file mode 100644 index 48cdce8..0000000 --- a/examples/simple.html +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/examples/simple.js b/examples/simple.js deleted file mode 100644 index cffc81d..0000000 --- a/examples/simple.js +++ /dev/null @@ -1,98 +0,0 @@ -/* eslint no-console:0 */ - -import React from 'react'; -import ReactDOM from 'react-dom'; -import Upload from 'rc-upload'; - -const style = ` - .rc-upload-disabled { - opacity:0.5; - `; - -class Test extends React.Component { - constructor(props) { - super(props); - this.uploaderProps = { - action: '/upload.do', - data: { a: 1, b: 2 }, - headers: { - Authorization: 'xxxxxxx', - }, - multiple: true, - beforeUpload(file) { - console.log('beforeUpload', file.name); - }, - onStart: (file) => { - console.log('onStart', file.name); - }, - onSuccess(file) { - console.log('onSuccess', file); - }, - onProgress(step, file) { - console.log('onProgress', Math.round(step.percent), file.name); - }, - onError(err) { - console.log('onError', err); - }, - }; - this.state = { - destroyed: false, - }; - } - destroy = () => { - this.setState({ - destroyed: true, - }); - } - render() { - if (this.state.destroyed) { - return null; - } - return (
-

固定位置

- - - - - -

滚动

- -
-
- - 开始上传2 - -
- - -
- - -
); - } -} - -ReactDOM.render(, document.getElementById('__react-content')); diff --git a/examples/simple.tsx b/examples/simple.tsx new file mode 100644 index 0000000..eb8f2ac --- /dev/null +++ b/examples/simple.tsx @@ -0,0 +1,77 @@ +/* eslint no-console:0 */ + +import React from 'react'; +import Upload from '../src/index'; + +const style = ` + .rc-upload-disabled { + opacity:0.5; + `; + +const uploaderProps = { + action: '/upload.do', + data: { a: 1, b: 2 }, + headers: { + Authorization: 'xxxxxxx', + }, + multiple: true, + beforeUpload(file) { + console.log('beforeUpload', file.name); + }, + onStart: file => { + console.log('onStart', file.name); + }, + onSuccess(file) { + console.log('onSuccess', file); + }, + onProgress(step, file) { + console.log('onProgress', Math.round(step.percent), file.name); + }, + onError(err) { + console.log('onError', err); + }, +}; + +const Test = () => { + const [destroyed, setDestroyed] = React.useState(false); + + const destroy = () => { + setDestroyed(true); + }; + + if (destroyed) { + return null; + } + + return ( +
+

固定位置

+ +
+ + 开始上传 + +
+

滚动

+
+
+ + 开始上传2 + +
+ +
+ ); +}; + +export default Test; diff --git a/examples/transformFile.html b/examples/transformFile.html deleted file mode 100644 index 48cdce8..0000000 --- a/examples/transformFile.html +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/examples/transformFile.js b/examples/transformFile.tsx similarity index 87% rename from examples/transformFile.js rename to examples/transformFile.tsx index 31b23ba..28855cb 100644 --- a/examples/transformFile.js +++ b/examples/transformFile.tsx @@ -1,7 +1,6 @@ /* eslint no-console:0 */ import React from 'react'; -import ReactDOM from 'react-dom'; -import Upload from 'rc-upload'; +import Upload from '../src/index'; const uploadProps = { action: '/upload.do', @@ -23,7 +22,7 @@ const uploadProps = { console.log('onProgress', `${percent}%`, file.name); }, transformFile(file) { - return new Promise((resolve) => { + return new Promise(resolve => { // eslint-disable-next-line no-undef const reader = new FileReader(); reader.readAsDataURL(file); @@ -57,4 +56,4 @@ const Test = () => { ); }; -ReactDOM.render(, document.getElementById('__react-content')); +export default Test; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..0a09639 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,4 @@ +module.exports = { + setupFiles: ["./tests/setup.js"], + snapshotSerializers: [require.resolve("enzyme-to-json/serializer")], +}; diff --git a/now.json b/now.json new file mode 100644 index 0000000..fd2f2ad --- /dev/null +++ b/now.json @@ -0,0 +1,11 @@ +{ + "version": 2, + "name": "rc-upload", + "builds": [ + { + "src": "package.json", + "use": "@now/static-build", + "config": { "distDir": ".doc" } + } + ] +} diff --git a/package.json b/package.json index 1ec9858..01eecd4 100644 --- a/package.json +++ b/package.json @@ -23,38 +23,42 @@ ], "main": "./lib/index", "module": "./es/index", - "config": { - "port": 8020 - }, "scripts": { - "build": "rc-tools run build", - "gh-pages": "rc-tools run gh-pages", - "start": "node server", - "compile": "rc-tools run compile", - "pub": "rc-tools run pub", - "lint": "rc-tools run lint", - "test": "jest --setupTestFrameworkScriptFile=raf/polyfill", - "coverage": "jest --coverage && cat ./coverage/lcov.info | coveralls" + "start": "cross-env NODE_ENV=development father doc dev --storybook", + "build": "father doc build --storybook", + "compile": "father build", + "gh-pages": "npm run build && father doc deploy", + "prepublishOnly": "npm run compile && np --yolo --no-publish", + "postpublish": "npm run gh-pages", + "lint": "eslint src/ --ext .ts,.tsx,.jsx,.js,.md", + "prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"", + "test": "father test", + "coverage": "father test --coverage" }, "devDependencies": { + "@types/jest": "^26.0.0", + "@types/react": "^16.9.2", + "@types/react-dom": "^16.9.0", + "@umijs/fabric": "^2.0.0", "axios": "^0.20.0", + "cross-env": "^7.0.0", "co-busboy": "^1.3.0", "coveralls": "^3.0.3", - "expect.js": "0.3.x", + "enzyme": "^3.1.1", + "enzyme-adapter-react-16": "^1.0.1", + "enzyme-to-json": "^3.1.2", + "eslint": "^7.1.0", + "father": "^2.22.0", "fs-extra": "^9.0.0", "gh-pages": "^3.0.0", - "jest": "^20.0.1", - "pre-commit": "1.x", "raf": "^3.4.0", "rc-tools": "8.x", "react": "^16.0.0", "react-dom": "^16.0.0", "sinon": "^9.0.2", + "typescript": "^3.7.2", "vinyl-fs": "^3.0.3" }, - "pre-commit": [ - "lint" - ], "dependencies": { "classnames": "^2.2.5" }, diff --git a/src/AjaxUploader.jsx b/src/AjaxUploader.jsx index de47a61..2ce7635 100644 --- a/src/AjaxUploader.jsx +++ b/src/AjaxUploader.jsx @@ -6,30 +6,27 @@ import getUid from './uid'; import attrAccept from './attr-accept'; import traverseFileTree from './traverseFileTree'; -const dataOrAriaAttributeProps = (props) => { - return Object.keys(props).reduce( - (acc, key) => { - if (key.substr(0, 5) === 'data-' || key.substr(0, 5) === 'aria-' || key === 'role') { - acc[key] = props[key]; - } - return acc; - }, - {}, - ); +const dataOrAriaAttributeProps = props => { + return Object.keys(props).reduce((acc, key) => { + if (key.substr(0, 5) === 'data-' || key.substr(0, 5) === 'aria-' || key === 'role') { + acc[key] = props[key]; + } + return acc; + }, {}); }; class AjaxUploader extends Component { - state = { uid: getUid() } + state = { uid: getUid() }; - reqs = {} + reqs = {}; onChange = e => { - const files = e.target.files; + const { files } = e.target; this.uploadFiles(files); this.reset(); - } + }; - onClick = (e) => { + onClick = e => { const el = this.fileInput; if (!el) { return; @@ -43,13 +40,13 @@ class AjaxUploader extends Component { if (onClick) { onClick(e); } - } + }; onKeyDown = e => { if (e.key === 'Enter') { this.onClick(); } - } + }; onFileDrop = e => { const { multiple } = this.props; @@ -61,11 +58,8 @@ class AjaxUploader extends Component { } if (this.props.directory) { - traverseFileTree( - Array.prototype.slice - .call(e.dataTransfer.items), - this.uploadFiles, - _file => attrAccept(_file, this.props.accept) + traverseFileTree(Array.prototype.slice.call(e.dataTransfer.items), this.uploadFiles, _file => + attrAccept(_file, this.props.accept), ); } else { let files = Array.prototype.slice @@ -78,7 +72,7 @@ class AjaxUploader extends Component { this.uploadFiles(files); } - } + }; componentDidMount() { this._isMounted = true; @@ -89,7 +83,7 @@ class AjaxUploader extends Component { this.abort(); } - uploadFiles = (files) => { + uploadFiles = files => { const postFiles = Array.prototype.slice.call(files); postFiles .map(file => { @@ -110,16 +104,18 @@ class AjaxUploader extends Component { const before = props.beforeUpload(file, fileList); if (before && before.then) { - before.then((processedFile) => { - const processedFileType = Object.prototype.toString.call(processedFile); - if (processedFileType === '[object File]' || processedFileType === '[object Blob]') { - return this.post(processedFile); - } - return this.post(file); - }).catch(e => { - // eslint-disable-next-line no-console - console.log(e); - }); + before + .then(processedFile => { + const processedFileType = Object.prototype.toString.call(processedFile); + if (processedFileType === '[object File]' || processedFileType === '[object Blob]') { + return this.post(processedFile); + } + return this.post(file); + }) + .catch(e => { + // eslint-disable-next-line no-console + console.log(e); + }); } else if (before !== false) { setTimeout(() => this.post(file), 0); } @@ -131,11 +127,7 @@ class AjaxUploader extends Component { return; } const { props } = this; - const { - onStart, - onProgress, - transformFile = (originFile) => originFile, - } = props; + const { onStart, onProgress, transformFile = originFile => originFile } = props; new Promise(resolve => { let { action } = props; @@ -147,13 +139,14 @@ class AjaxUploader extends Component { const { uid } = file; const request = props.customRequest || defaultRequest; const transform = Promise.resolve(transformFile(file)) - .then((transformedFile) => { + .then(transformedFile => { let { data } = props; if (typeof data === 'function') { data = data(transformedFile); } return Promise.all([transformedFile, data]); - }).catch(e => { + }) + .catch(e => { console.error(e); // eslint-disable-line no-console }); @@ -166,9 +159,11 @@ class AjaxUploader extends Component { headers: props.headers, withCredentials: props.withCredentials, method: props.method || 'post', - onProgress: onProgress ? e => { - onProgress(e, file); - } : null, + onProgress: onProgress + ? e => { + onProgress(e, file); + } + : null, onSuccess: (ret, xhr) => { delete this.reqs[uid]; props.onSuccess(ret, file, xhr); @@ -203,7 +198,7 @@ class AjaxUploader extends Component { } delete reqs[uid]; } else { - Object.keys(reqs).forEach((uid) => { + Object.keys(reqs).forEach(uid => { if (reqs[uid] && reqs[uid].abort) { reqs[uid].abort(); } @@ -212,15 +207,25 @@ class AjaxUploader extends Component { } } - saveFileInput = (node) => { + saveFileInput = node => { this.fileInput = node; - } + }; render() { const { - component: Tag, prefixCls, className, disabled, id, - style, multiple, accept, children, directory, openFileDialogOnClick, - onMouseEnter, onMouseLeave, + component: Tag, + prefixCls, + className, + disabled, + id, + style, + multiple, + accept, + children, + directory, + openFileDialogOnClick, + onMouseEnter, + onMouseLeave, ...otherProps } = this.props; const cls = classNames({ @@ -228,22 +233,19 @@ class AjaxUploader extends Component { [`${prefixCls}-disabled`]: disabled, [className]: className, }); - const events = disabled ? {} : { - onClick: openFileDialogOnClick ? this.onClick : () => {}, - onKeyDown: openFileDialogOnClick ? this.onKeyDown : () => {}, - onMouseEnter, - onMouseLeave, - onDrop: this.onFileDrop, - onDragOver: this.onFileDrop, - tabIndex: '0', - }; + const events = disabled + ? {} + : { + onClick: openFileDialogOnClick ? this.onClick : () => {}, + onKeyDown: openFileDialogOnClick ? this.onKeyDown : () => {}, + onMouseEnter, + onMouseLeave, + onDrop: this.onFileDrop, + onDragOver: this.onFileDrop, + tabIndex: '0', + }; return ( - + { const validType = type.trim(); if (validType.charAt(0) === '.') { return endsWith(fileName.toLowerCase(), validType.toLowerCase()); - } else if (/\/\*$/.test(validType)) { + } + if (/\/\*$/.test(validType)) { // This is something like a image/* mime type return baseMimeType === validType.replace(/\/.*$/, ''); } diff --git a/src/uid.js b/src/uid.js index 789a6f6..3ad3f8e 100644 --- a/src/uid.js +++ b/src/uid.js @@ -1,6 +1,7 @@ -const now = +(new Date()); +const now = +new Date(); let index = 0; export default function uid() { + // eslint-disable-next-line no-plusplus return `rc-upload-${now}-${++index}`; } diff --git a/tests/index.js b/tests/index.js deleted file mode 100644 index ed92f29..0000000 --- a/tests/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import './uploader.spec'; -import './request.spec'; diff --git a/tests/request.spec.js b/tests/request.spec.js index 3997b2c..f5fae71 100644 --- a/tests/request.spec.js +++ b/tests/request.spec.js @@ -1,13 +1,11 @@ /* eslint no-console:0 */ -import expect from 'expect.js'; import sinon from 'sinon'; import request from '../src/request'; let xhr; let requests; -const empty = () => { -}; +const empty = () => {}; const option = { onSuccess: empty, action: 'upload.do', @@ -37,8 +35,8 @@ describe('request', () => { it('upload request success', done => { option.onError = done; option.onSuccess = ret => { - expect(ret).to.eql({ success: true }); - expect(requests[0].requestBody.getAll('c[]')).to.eql([3, 4]); + expect(ret).toEqual({ success: true }); + expect(requests[0].requestBody.getAll('c[]')).toEqual(['3', '4']); done(); }; request(option); @@ -47,7 +45,7 @@ describe('request', () => { it('40x code should be error', done => { option.onError = e => { - expect(e.toString()).to.contain('404'); + expect(e.toString()).toContain('404'); done(); }; @@ -59,7 +57,7 @@ describe('request', () => { it('2xx code should be success', done => { option.onError = done; option.onSuccess = ret => { - expect(ret).to.equal(''); + expect(ret).toEqual(''); done(); }; request(option); @@ -68,7 +66,7 @@ describe('request', () => { it('get headers', () => { request(option); - expect(requests[0].requestHeaders).to.eql({ + expect(requests[0].requestHeaders).toEqual({ 'X-Requested-With': 'XMLHttpRequest', from: 'hello', }); @@ -77,6 +75,6 @@ describe('request', () => { it('can empty X-Requested-With', () => { option.headers['X-Requested-With'] = null; request(option); - expect(requests[0].requestHeaders).to.eql({ from: 'hello' }); + expect(requests[0].requestHeaders).toEqual({ from: 'hello' }); }); }); diff --git a/tests/setup.js b/tests/setup.js new file mode 100644 index 0000000..4c18e28 --- /dev/null +++ b/tests/setup.js @@ -0,0 +1,6 @@ +global.requestAnimationFrame = cb => setTimeout(cb, 0); + +const Enzyme = require('enzyme'); +const Adapter = require('enzyme-adapter-react-16'); + +Enzyme.configure({ adapter: new Adapter() }); diff --git a/tests/uploader.spec.js b/tests/uploader.spec.js index b509544..b41e7b7 100644 --- a/tests/uploader.spec.js +++ b/tests/uploader.spec.js @@ -1,5 +1,4 @@ /* eslint no-console:0 */ -import expect from 'expect.js'; import React from 'react'; import ReactDOM from 'react-dom'; import TestUtils from 'react-dom/test-utils'; @@ -121,62 +120,56 @@ describe('uploader', () => { it('with id', done => { ReactDOM.render(, node, function init() { - expect(TestUtils.findRenderedDOMComponentWithTag(this, 'input').id).to.be('bamboo'); + expect(TestUtils.findRenderedDOMComponentWithTag(this, 'input').id).toBe('bamboo'); done(); }); }); it('should pass through data attributes', done => { ReactDOM.render( - ( - - ), + , node, function init() { - expect(TestUtils.findRenderedDOMComponentWithTag(this, 'input') - .getAttribute('data-testid')) - .to.be('data-testid'); - expect(TestUtils.findRenderedDOMComponentWithTag(this, 'input') - .getAttribute('data-my-custom-attr')) - .to.be('custom data attribute'); + expect( + TestUtils.findRenderedDOMComponentWithTag(this, 'input').getAttribute('data-testid'), + ).toBe('data-testid'); + expect( + TestUtils.findRenderedDOMComponentWithTag(this, 'input').getAttribute( + 'data-my-custom-attr', + ), + ).toBe('custom data attribute'); done(); - } + }, ); }); it('should pass through aria attributes', done => { - ReactDOM.render(, node, function init() { - expect(TestUtils.findRenderedDOMComponentWithTag(this, 'input') - .getAttribute('aria-label')) - .to.be('Upload a file'); + ReactDOM.render(, node, function init() { + expect( + TestUtils.findRenderedDOMComponentWithTag(this, 'input').getAttribute('aria-label'), + ).toBe('Upload a file'); done(); }); }); it('should pass through role attributes', done => { - ReactDOM.render(, node, function init() { - expect(TestUtils.findRenderedDOMComponentWithTag(this, 'input') - .getAttribute('role')) - .to.be('button'); + ReactDOM.render(, node, function init() { + expect(TestUtils.findRenderedDOMComponentWithTag(this, 'input').getAttribute('role')).toBe( + 'button', + ); done(); }); }); it('should not pass through unknown props', done => { - ReactDOM.render(, - node, - () => { - // Fails when React reports unrecognized prop is added to DOM in console.error - done(); - } - ); + ReactDOM.render(, node, () => { + // Fails when React reports unrecognized prop is added to DOM in console.error + done(); + }); }); it('create works', () => { - expect(TestUtils.scryRenderedDOMComponentsWithTag(uploader, 'span').length).to.be(1); + expect(TestUtils.scryRenderedDOMComponentsWithTag(uploader, 'span').length).toBe(1); }); it('upload success', done => { @@ -193,8 +186,8 @@ describe('uploader', () => { files.item = i => files[i]; handlers.onSuccess = (ret, file) => { - expect(ret[1]).to.eql(file.name); - expect(file).to.have.property('uid'); + expect(ret[1]).toEqual(file.name); + expect(file).toHaveProperty('uid'); done(); }; @@ -223,9 +216,9 @@ describe('uploader', () => { files.item = i => files[i]; handlers.onError = (err, ret) => { - expect(err instanceof Error).to.equal(true); - expect(err.status).to.equal(400); - expect(ret).to.equal('error 400'); + expect(err instanceof Error).toEqual(true); + expect(err.status).toEqual(400); + expect(ret).toEqual('error 400'); done(); }; @@ -249,8 +242,8 @@ describe('uploader', () => { files.item = i => files[i]; handlers.onSuccess = (ret, file) => { - expect(ret[1]).to.eql(file.name); - expect(file).to.have.property('uid'); + expect(ret[1]).toEqual(file.name); + expect(file).toHaveProperty('uid'); done(); }; @@ -280,7 +273,7 @@ describe('uploader', () => { const mockStart = jest.fn(); handlers.onStart = mockStart; setTimeout(() => { - expect(mockStart.mock.calls.length).to.be(0); + expect(mockStart.mock.calls.length).toBe(0); done(); }, 100); }); @@ -320,9 +313,9 @@ describe('uploader', () => { }; handlers.onSuccess = (ret, file) => { - expect(ret[1]).to.eql(file.name); - expect(file).to.have.property('uid'); - expect(triggerTimes).to.eql(1); + expect(ret[1]).toEqual(file.name); + expect(file).toHaveProperty('uid'); + expect(triggerTimes).toEqual(1); done(); }; @@ -353,7 +346,7 @@ describe('uploader', () => { }, 1000); }); }; - ReactDOM.render(, node, function init() { + ReactDOM.render(, node, function init() { uploader = this; const input = TestUtils.findRenderedDOMComponentWithTag(uploader, 'input'); const files = [ @@ -367,12 +360,12 @@ describe('uploader', () => { files.item = i => files[i]; Simulate.change(input, { target: { files } }); setTimeout(() => { - expect(requests.length).to.be(0); + expect(requests.length).toBe(0); setTimeout(() => { console.log(requests); - expect(requests.length).to.be(1); - expect(requests[0].url).to.be('/upload.do'); - expect(requests[0].requestBody.get('field1')).to.be('a'); + expect(requests.length).toBe(1); + expect(requests[0].url).toBe('/upload.do'); + expect(requests[0].requestBody.get('field1')).toBe('a'); done(); }, 2000); }, 100); @@ -446,7 +439,7 @@ describe('uploader', () => { const mockStart = jest.fn(); handlers.onStart = mockStart; setTimeout(() => { - expect(mockStart.mock.calls.length).to.be(0); + expect(mockStart.mock.calls.length).toBe(0); done(); }, 100); }); @@ -472,17 +465,17 @@ describe('uploader', () => { it('transform file function should be called before data function', done => { const props = { action: '/test', - data (file) { - return new Promise((resolve) => { + data(file) { + return new Promise(resolve => { setTimeout(() => { resolve({ - url: file.url - }) - }, 500) - }) + url: file.url, + }); + }, 500); + }); }, - transformFile (file) { - return new Promise((resolve) => { + transformFile(file) { + return new Promise(resolve => { setTimeout(() => { file.url = 'this is file url'; resolve(file); @@ -509,7 +502,7 @@ describe('uploader', () => { setTimeout(() => { setTimeout(() => { - expect(requests[0].requestBody.get('url')).to.be('this is file url'); + expect(requests[0].requestBody.get('url')).toBe('this is file url'); done(); }, 1000); }, 100); @@ -545,8 +538,8 @@ describe('uploader', () => { files.item = i => files[i]; handlers.onSuccess = (ret, file) => { - expect(ret[1]).to.eql(file.name); - expect(file).to.have.property('uid'); + expect(ret[1]).toEqual(file.name); + expect(file).toHaveProperty('uid'); done(); }; From fc3b9baffe1f0ac90d077b7e0d28a7c8b6ac5761 Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Fri, 21 Aug 2020 11:20:05 +0800 Subject: [PATCH 04/21] fix lint --- src/traverseFileTree.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/traverseFileTree.js b/src/traverseFileTree.js index 3747a92..3cb44c6 100644 --- a/src/traverseFileTree.js +++ b/src/traverseFileTree.js @@ -3,7 +3,7 @@ function loopFiles(item, callback) { let fileList = []; function sequence() { - dirReader.readEntries((entries) => { + dirReader.readEntries(entries => { const entryList = Array.prototype.slice.apply(entries); fileList = fileList.concat(entryList); @@ -22,10 +22,11 @@ function loopFiles(item, callback) { } const traverseFileTree = (files, callback, isAccepted) => { + // eslint-disable-next-line @typescript-eslint/naming-convention const _traverseFileTree = (item, path) => { path = path || ''; if (item.isFile) { - item.file((file) => { + item.file(file => { if (isAccepted(file)) { // https://github.com/ant-design/ant-design/issues/16426 if (item.fullPath && !file.webkitRelativePath) { @@ -45,8 +46,8 @@ const traverseFileTree = (files, callback, isAccepted) => { } }); } else if (item.isDirectory) { - loopFiles(item, (entries) => { - entries.forEach((entryItem) => { + loopFiles(item, entries => { + entries.forEach(entryItem => { _traverseFileTree(entryItem, `${path}${item.name}/`); }); }); From d1b443159d25afdba2f724acdf30f9e145f492dc Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Mon, 24 Aug 2020 10:26:31 +0800 Subject: [PATCH 05/21] add runtime --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 01eecd4..012cac1 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "vinyl-fs": "^3.0.3" }, "dependencies": { + "@babel/runtime": "^7.10.1", "classnames": "^2.2.5" }, "jest": { From 03a387f065d62de4e94e83890c8318eea64acea1 Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Mon, 24 Aug 2020 10:54:39 +0800 Subject: [PATCH 06/21] add compile --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f79fa18..311ed6e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,4 +29,5 @@ env: matrix: - TEST_TYPE=lint - TEST_TYPE=test - - TEST_TYPE=coverage \ No newline at end of file + - TEST_TYPE=coverage + - TEST_TYPE=compile \ No newline at end of file From 57982679b8f220339b4a9fcb9c5cbe6d095ccbf8 Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Mon, 24 Aug 2020 10:55:26 +0800 Subject: [PATCH 07/21] add block --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 311ed6e..70cdd0e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,4 +30,4 @@ env: - TEST_TYPE=lint - TEST_TYPE=test - TEST_TYPE=coverage - - TEST_TYPE=compile \ No newline at end of file + - TEST_TYPE=compile From f980379df14f7accbd8946b0d461bec67fd59a4b Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Mon, 24 Aug 2020 11:45:20 +0800 Subject: [PATCH 08/21] update --- .travis.yml | 2 +- examples/asyncAction.tsx | 2 +- examples/beforeUpload.tsx | 2 +- examples/customRequest.tsx | 2 +- examples/directoryUpload.tsx | 2 +- examples/drag.tsx | 2 +- examples/simple.tsx | 2 +- examples/transformFile.tsx | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 70cdd0e..2d2337c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ notifications: - yiminghe@gmail.com node_js: -- 10 +- 12 before_install: - | diff --git a/examples/asyncAction.tsx b/examples/asyncAction.tsx index b75eff4..61b6773 100644 --- a/examples/asyncAction.tsx +++ b/examples/asyncAction.tsx @@ -1,6 +1,6 @@ /* eslint no-console:0 */ import React from 'react'; -import Upload from '../src/index'; +import Upload from '..'; const props = { action: () => { diff --git a/examples/beforeUpload.tsx b/examples/beforeUpload.tsx index 0160dd3..fcad0fe 100644 --- a/examples/beforeUpload.tsx +++ b/examples/beforeUpload.tsx @@ -1,7 +1,7 @@ /* eslint no-console:0 */ import React from 'react'; -import Upload from '../src/index'; +import Upload from '..'; const props = { action: '/upload.do', diff --git a/examples/customRequest.tsx b/examples/customRequest.tsx index f11d7fb..3a079d9 100644 --- a/examples/customRequest.tsx +++ b/examples/customRequest.tsx @@ -1,7 +1,7 @@ /* eslint no-console:0 */ import React from 'react'; import axios from 'axios'; -import Upload from '../src/index'; +import Upload from '..'; const uploadProps = { action: '/upload.do', diff --git a/examples/directoryUpload.tsx b/examples/directoryUpload.tsx index 9d2e29a..96ff99b 100644 --- a/examples/directoryUpload.tsx +++ b/examples/directoryUpload.tsx @@ -1,7 +1,7 @@ /* eslint no-console:0 */ import React from 'react'; -import Upload from '../src/index'; +import Upload from '..'; const Test = () => { const uploaderProps = { diff --git a/examples/drag.tsx b/examples/drag.tsx index 3f4a184..cb695fa 100644 --- a/examples/drag.tsx +++ b/examples/drag.tsx @@ -1,6 +1,6 @@ /* eslint no-console:0 */ import React from 'react'; -import Upload from '../src/index'; +import Upload from '..'; const props = { action: '/upload.do', diff --git a/examples/simple.tsx b/examples/simple.tsx index eb8f2ac..2626027 100644 --- a/examples/simple.tsx +++ b/examples/simple.tsx @@ -1,7 +1,7 @@ /* eslint no-console:0 */ import React from 'react'; -import Upload from '../src/index'; +import Upload from '..'; const style = ` .rc-upload-disabled { diff --git a/examples/transformFile.tsx b/examples/transformFile.tsx index 28855cb..3995479 100644 --- a/examples/transformFile.tsx +++ b/examples/transformFile.tsx @@ -1,6 +1,6 @@ /* eslint no-console:0 */ import React from 'react'; -import Upload from '../src/index'; +import Upload from '..'; const uploadProps = { action: '/upload.do', From 4951e70e6b5d3281d36635d8f4e1e652f4b15e2d Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Mon, 24 Aug 2020 11:50:05 +0800 Subject: [PATCH 09/21] test 14 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2d2337c..5a5aa98 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ notifications: - yiminghe@gmail.com node_js: -- 12 +- 14 before_install: - | From 4cedf9194ac25f0bbc167fbce578db9af68c5a20 Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Mon, 24 Aug 2020 11:52:01 +0800 Subject: [PATCH 10/21] test 10 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5a5aa98..70cdd0e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ notifications: - yiminghe@gmail.com node_js: -- 14 +- 10 before_install: - | From e443849ab4d9c911f787d53c29d6b060db2a91d8 Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Mon, 24 Aug 2020 13:58:57 +0800 Subject: [PATCH 11/21] fix alert --- examples/directoryUpload.tsx | 2 +- examples/simple.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/directoryUpload.tsx b/examples/directoryUpload.tsx index 96ff99b..3ccedcc 100644 --- a/examples/directoryUpload.tsx +++ b/examples/directoryUpload.tsx @@ -8,7 +8,7 @@ const Test = () => { action: '/upload.do', data: { a: 1, b: 2 }, headers: { - Authorization: 'xxxxxxx', + Authorization: 'xxxxxxx-directory', }, directory: true, beforeUpload(file) { diff --git a/examples/simple.tsx b/examples/simple.tsx index 2626027..fb9f2f1 100644 --- a/examples/simple.tsx +++ b/examples/simple.tsx @@ -12,7 +12,7 @@ const uploaderProps = { action: '/upload.do', data: { a: 1, b: 2 }, headers: { - Authorization: 'xxxxxxx', + Authorization: 'xxxxxxx-simple', }, multiple: true, beforeUpload(file) { From d8a73f082f245f62c9d5cfcbd84981a83a7cdd08 Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Mon, 24 Aug 2020 14:07:32 +0800 Subject: [PATCH 12/21] remove headers --- examples/directoryUpload.tsx | 3 --- examples/simple.tsx | 3 --- 2 files changed, 6 deletions(-) diff --git a/examples/directoryUpload.tsx b/examples/directoryUpload.tsx index 3ccedcc..3615dd3 100644 --- a/examples/directoryUpload.tsx +++ b/examples/directoryUpload.tsx @@ -7,9 +7,6 @@ const Test = () => { const uploaderProps = { action: '/upload.do', data: { a: 1, b: 2 }, - headers: { - Authorization: 'xxxxxxx-directory', - }, directory: true, beforeUpload(file) { console.log('beforeUpload', file.name); diff --git a/examples/simple.tsx b/examples/simple.tsx index fb9f2f1..15d3323 100644 --- a/examples/simple.tsx +++ b/examples/simple.tsx @@ -11,9 +11,6 @@ const style = ` const uploaderProps = { action: '/upload.do', data: { a: 1, b: 2 }, - headers: { - Authorization: 'xxxxxxx-simple', - }, multiple: true, beforeUpload(file) { console.log('beforeUpload', file.name); From dbc71c750d37ae9b5c9394358ea15722f54f4bc1 Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Tue, 25 Aug 2020 13:37:51 +0800 Subject: [PATCH 13/21] fix eslint --- .eslintrc.js | 4 ---- examples/customRequest.tsx | 2 +- examples/simple.tsx | 4 +++- examples/transformFile.tsx | 2 +- src/AjaxUploader.jsx | 6 ++---- src/traverseFileTree.js | 2 ++ 6 files changed, 9 insertions(+), 11 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 29fbf6b..ff56d10 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,7 +4,6 @@ module.exports = { ...base, rules: { ...base.rules, - "arrow-parens": 0, "react/no-array-index-key": 0, "react/sort-comp": 0, "@typescript-eslint/no-explicit-any": 0, @@ -12,14 +11,11 @@ module.exports = { "@typescript-eslint/no-inferrable-types": 0, "react/no-find-dom-node": 0, "react/require-default-props": 0, - "react/button-has-type": 0, "no-confusing-arrow": 0, "import/no-named-as-default-member": 0, "jsx-a11y/label-has-for": 0, "jsx-a11y/label-has-associated-control": 0, "import/no-extraneous-dependencies": 0, - "prefer-destructuring": 0, "no-underscore-dangle": 0, - "no-param-reassign": 0, }, }; diff --git a/examples/customRequest.tsx b/examples/customRequest.tsx index 3a079d9..611251d 100644 --- a/examples/customRequest.tsx +++ b/examples/customRequest.tsx @@ -73,7 +73,7 @@ const Test = () => { >
- +
diff --git a/examples/simple.tsx b/examples/simple.tsx index 15d3323..713f830 100644 --- a/examples/simple.tsx +++ b/examples/simple.tsx @@ -66,7 +66,9 @@ const Test = () => { 开始上传2
- + ); }; diff --git a/examples/transformFile.tsx b/examples/transformFile.tsx index 3995479..b918d5c 100644 --- a/examples/transformFile.tsx +++ b/examples/transformFile.tsx @@ -49,7 +49,7 @@ const Test = () => { >
- +
diff --git a/src/AjaxUploader.jsx b/src/AjaxUploader.jsx index 2ce7635..d6a45f2 100644 --- a/src/AjaxUploader.jsx +++ b/src/AjaxUploader.jsx @@ -87,6 +87,7 @@ class AjaxUploader extends Component { const postFiles = Array.prototype.slice.call(files); postFiles .map(file => { + // eslint-disable-next-line no-param-reassign file.uid = getUid(); return file; }) @@ -189,10 +190,7 @@ class AjaxUploader extends Component { abort(file) { const { reqs } = this; if (file) { - let uid = file; - if (file && file.uid) { - uid = file.uid; - } + const uid = file && file.uid ? file.uid : file; if (reqs[uid] && reqs[uid].abort) { reqs[uid].abort(); } diff --git a/src/traverseFileTree.js b/src/traverseFileTree.js index 3cb44c6..bbfbc9f 100644 --- a/src/traverseFileTree.js +++ b/src/traverseFileTree.js @@ -24,6 +24,7 @@ function loopFiles(item, callback) { const traverseFileTree = (files, callback, isAccepted) => { // eslint-disable-next-line @typescript-eslint/naming-convention const _traverseFileTree = (item, path) => { + // eslint-disable-next-line no-param-reassign path = path || ''; if (item.isFile) { item.file(file => { @@ -35,6 +36,7 @@ const traverseFileTree = (files, callback, isAccepted) => { writable: true, }, }); + // eslint-disable-next-line no-param-reassign file.webkitRelativePath = item.fullPath.replace(/^\//, ''); Object.defineProperties(file, { webkitRelativePath: { From 8a85ff2cb656e4086bf9059314ea561f4ab2b54b Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Tue, 25 Aug 2020 15:42:06 +0800 Subject: [PATCH 14/21] remove ReactDOM --- tests/uploader.spec.js | 324 +++++++++++++++++------------------------ 1 file changed, 131 insertions(+), 193 deletions(-) diff --git a/tests/uploader.spec.js b/tests/uploader.spec.js index b41e7b7..2df046d 100644 --- a/tests/uploader.spec.js +++ b/tests/uploader.spec.js @@ -1,13 +1,10 @@ /* eslint no-console:0 */ import React from 'react'; -import ReactDOM from 'react-dom'; -import TestUtils from 'react-dom/test-utils'; import { format } from 'util'; +import { mount } from 'enzyme'; import sinon from 'sinon'; import Uploader from '../index'; -const { Simulate } = TestUtils; - function Item(name) { this.name = name; this.toString = () => this.name; @@ -72,7 +69,6 @@ describe('uploader', () => { return; } - let node; let uploader; const handlers = {}; @@ -104,77 +100,48 @@ describe('uploader', () => { }, }; - beforeEach(done => { - node = document.createElement('div'); - document.body.appendChild(node); - - ReactDOM.render(, node, function init() { - uploader = this; - done(); - }); + beforeEach(() => { + uploader = mount(); }); afterEach(() => { - ReactDOM.unmountComponentAtNode(node); + uploader.unmount(); }); - it('with id', done => { - ReactDOM.render(, node, function init() { - expect(TestUtils.findRenderedDOMComponentWithTag(this, 'input').id).toBe('bamboo'); - done(); - }); + it('with id', () => { + const wrapper = mount(); + expect(wrapper.find('input').props().id).toBe('bamboo'); }); - it('should pass through data attributes', done => { - ReactDOM.render( - , - node, - function init() { - expect( - TestUtils.findRenderedDOMComponentWithTag(this, 'input').getAttribute('data-testid'), - ).toBe('data-testid'); - expect( - TestUtils.findRenderedDOMComponentWithTag(this, 'input').getAttribute( - 'data-my-custom-attr', - ), - ).toBe('custom data attribute'); - done(); - }, + it('should pass through data & aria attributes', () => { + const wrapper = mount( + , ); + expect(wrapper.find('input').props()['data-testid']).toBe('data-testid'); + expect(wrapper.find('input').props()['data-my-custom-attr']).toBe('custom data attribute'); + expect(wrapper.find('input').props()['aria-label']).toBe('Upload a file'); }); - it('should pass through aria attributes', done => { - ReactDOM.render(, node, function init() { - expect( - TestUtils.findRenderedDOMComponentWithTag(this, 'input').getAttribute('aria-label'), - ).toBe('Upload a file'); - done(); - }); + it('should pass through role attributes', () => { + const wrapper = mount(); + expect(wrapper.find('input').props().role).toBe('button'); }); - it('should pass through role attributes', done => { - ReactDOM.render(, node, function init() { - expect(TestUtils.findRenderedDOMComponentWithTag(this, 'input').getAttribute('role')).toBe( - 'button', - ); - done(); - }); - }); - - it('should not pass through unknown props', done => { - ReactDOM.render(, node, () => { - // Fails when React reports unrecognized prop is added to DOM in console.error - done(); - }); + it('should not pass through unknown props', () => { + const wrapper = mount(); + expect(wrapper.find('input').props().customProp).toBeUndefined(); }); it('create works', () => { - expect(TestUtils.scryRenderedDOMComponentsWithTag(uploader, 'span').length).toBe(1); + expect(uploader.find('span').length).toBeTruthy(); }); it('upload success', done => { - const input = TestUtils.findRenderedDOMComponentWithTag(uploader, 'input'); - + const input = uploader.find('input').first(); const files = [ { name: 'success.png', @@ -194,16 +161,14 @@ describe('uploader', () => { handlers.onError = err => { done(err); }; - - Simulate.change(input, { target: { files } }); - + input.simulate('change', { target: { files } }); setTimeout(() => { requests[0].respond(200, {}, `["","${files[0].name}"]`); }, 100); }); it('upload error', done => { - const input = TestUtils.findRenderedDOMComponentWithTag(uploader, 'input'); + const input = uploader.find('input').first(); const files = [ { @@ -222,14 +187,14 @@ describe('uploader', () => { done(); }; - Simulate.change(input, { target: { files } }); + input.simulate('change', { target: { files } }); setTimeout(() => { requests[0].respond(400, {}, `error 400`); }, 100); }); it('drag to upload', done => { - const input = TestUtils.findRenderedDOMComponentWithTag(uploader, 'input'); + const input = uploader.find('input').first(); const files = [ { @@ -251,7 +216,7 @@ describe('uploader', () => { done(err); }; - Simulate.drop(input, { dataTransfer: { files } }); + input.simulate('drop', { dataTransfer: { files } }); setTimeout(() => { requests[0].respond(200, {}, `["","${files[0].name}"]`); @@ -259,7 +224,7 @@ describe('uploader', () => { }); it('drag unaccepted type files to upload will not trigger onStart', done => { - const input = TestUtils.findRenderedDOMComponentWithTag(uploader, 'input'); + const input = uploader.find('input').first(); const files = [ { name: 'success.jpg', @@ -269,7 +234,7 @@ describe('uploader', () => { }, ]; files.item = i => files[i]; - Simulate.drop(input, { dataTransfer: { files } }); + input.simulate('drop', { dataTransfer: { files } }); const mockStart = jest.fn(); handlers.onStart = mockStart; setTimeout(() => { @@ -279,56 +244,47 @@ describe('uploader', () => { }); it('drag files with multiple false', done => { - ReactDOM.unmountComponentAtNode(node); - - // Create new one - node = document.createElement('div'); - document.body.appendChild(node); - - ReactDOM.render(, node, function init() { - uploader = this; + const wrapper = mount(); + const input = wrapper.find('input').first(); - const input = TestUtils.findRenderedDOMComponentWithTag(uploader, 'input'); - - const files = [ - { - name: 'success.png', - toString() { - return this.name; - }, + const files = [ + { + name: 'success.png', + toString() { + return this.name; }, - { - name: 'filtered.png', - toString() { - return this.name; - }, + }, + { + name: 'filtered.png', + toString() { + return this.name; }, - ]; - files.item = i => files[i]; - - // Only can trigger once - let triggerTimes = 0; - handlers.onStart = () => { - triggerTimes += 1; - }; - - handlers.onSuccess = (ret, file) => { - expect(ret[1]).toEqual(file.name); - expect(file).toHaveProperty('uid'); - expect(triggerTimes).toEqual(1); - done(); - }; + }, + ]; + files.item = i => files[i]; + + // Only can trigger once + let triggerTimes = 0; + handlers.onStart = () => { + triggerTimes += 1; + }; + + handlers.onSuccess = (ret, file) => { + expect(ret[1]).toEqual(file.name); + expect(file).toHaveProperty('uid'); + expect(triggerTimes).toEqual(1); + done(); + }; - handlers.onError = err => { - done(err); - }; + handlers.onError = err => { + done(err); + }; - Simulate.drop(input, { dataTransfer: { files } }); + input.simulate('drop', { dataTransfer: { files } }); - setTimeout(() => { - requests[0].respond(200, {}, `["","${files[0].name}"]`); - }, 100); - }); + setTimeout(() => { + requests[0].respond(200, {}, `["","${files[0].name}"]`); + }, 100); }); it('support action and data is function returns Promise', done => { @@ -346,30 +302,29 @@ describe('uploader', () => { }, 1000); }); }; - ReactDOM.render(, node, function init() { - uploader = this; - const input = TestUtils.findRenderedDOMComponentWithTag(uploader, 'input'); - const files = [ - { - name: 'success.png', - toString() { - return this.name; - }, + const wrapper = mount(); + const input = wrapper.find('input').first(); + + const files = [ + { + name: 'success.png', + toString() { + return this.name; }, - ]; - files.item = i => files[i]; - Simulate.change(input, { target: { files } }); + }, + ]; + files.item = i => files[i]; + input.simulate('change', { target: { files } }); + setTimeout(() => { + expect(requests.length).toBe(0); setTimeout(() => { - expect(requests.length).toBe(0); - setTimeout(() => { - console.log(requests); - expect(requests.length).toBe(1); - expect(requests[0].url).toBe('/upload.do'); - expect(requests[0].requestBody.get('field1')).toBe('a'); - done(); - }, 2000); - }, 100); - }); + console.log(requests); + expect(requests.length).toBe(1); + expect(requests[0].url).toBe('/upload.do'); + expect(requests[0].requestBody.get('field1')).toBe('a'); + done(); + }, 2000); + }, 100); }); }); @@ -378,7 +333,6 @@ describe('uploader', () => { return; } - let node; let uploader; const handlers = {}; @@ -410,18 +364,12 @@ describe('uploader', () => { }, }; - beforeEach(done => { - node = document.createElement('div'); - document.body.appendChild(node); - - ReactDOM.render(, node, function init() { - uploader = this; - done(); - }); + beforeEach(() => { + uploader = mount(); }); it('unaccepted type files to upload will not trigger onStart', done => { - const input = TestUtils.findRenderedDOMComponentWithTag(uploader, 'input'); + const input = uploader.find('input').first(); const files = { name: 'foo', children: [ @@ -435,7 +383,7 @@ describe('uploader', () => { }, ], }; - Simulate.drop(input, { dataTransfer: { items: [makeDataTransferItem(files)] } }); + input.simulate('drop', { dataTransfer: { items: [makeDataTransferItem(files)] } }); const mockStart = jest.fn(); handlers.onStart = mockStart; setTimeout(() => { @@ -446,20 +394,13 @@ describe('uploader', () => { }); describe('transform file before request', () => { - let node; let uploader; - beforeEach(done => { - node = document.createElement('div'); - document.body.appendChild(node); - - ReactDOM.render(, node, function init() { - uploader = this; - done(); - }); + beforeEach(() => { + uploader = mount(); }); afterEach(() => { - ReactDOM.unmountComponentAtNode(node); + uploader.unmount(); }); it('transform file function should be called before data function', done => { @@ -477,36 +418,35 @@ describe('uploader', () => { transformFile(file) { return new Promise(resolve => { setTimeout(() => { + // eslint-disable-next-line no-param-reassign file.url = 'this is file url'; resolve(file); }, 500); }); }, }; - ReactDOM.render(, node, function init() { - uploader = this; - const input = TestUtils.findRenderedDOMComponentWithTag(uploader, 'input'); + const wrapper = mount(); + const input = wrapper.find('input').first(); - const files = [ - { - name: 'success.png', - toString() { - return this.name; - }, + const files = [ + { + name: 'success.png', + toString() { + return this.name; }, - ]; + }, + ]; - files.item = i => files[i]; + files.item = i => files[i]; - Simulate.change(input, { target: { files } }); + input.simulate('change', { target: { files } }); + setTimeout(() => { setTimeout(() => { - setTimeout(() => { - expect(requests[0].requestBody.get('url')).toBe('this is file url'); - done(); - }, 1000); - }, 100); - }); + expect(requests[0].requestBody.get('url')).toBe('this is file url'); + done(); + }, 1000); + }, 100); }); it('noes not affect receive origin file when transform file is null', done => { @@ -522,33 +462,31 @@ describe('uploader', () => { return null; }, }; - ReactDOM.render(, node, function init() { - uploader = this; - const input = TestUtils.findRenderedDOMComponentWithTag(uploader, 'input'); + const wrapper = mount(); + const input = wrapper.find('input').first(); - const files = [ - { - name: 'success.png', - toString() { - return this.name; - }, + const files = [ + { + name: 'success.png', + toString() { + return this.name; }, - ]; + }, + ]; - files.item = i => files[i]; + files.item = i => files[i]; - handlers.onSuccess = (ret, file) => { - expect(ret[1]).toEqual(file.name); - expect(file).toHaveProperty('uid'); - done(); - }; + handlers.onSuccess = (ret, file) => { + expect(ret[1]).toEqual(file.name); + expect(file).toHaveProperty('uid'); + done(); + }; - Simulate.change(input, { target: { files } }); + input.simulate('change', { target: { files } }); - setTimeout(() => { - requests[0].respond(200, {}, `["","${files[0].name}"]`); - }, 100); - }); + setTimeout(() => { + requests[0].respond(200, {}, `["","${files[0].name}"]`); + }, 100); }); }); }); From ec45b0b9272327e77bcce78da03cb2a64293ce2c Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Fri, 28 Aug 2020 15:42:27 +0800 Subject: [PATCH 15/21] change tsx --- src/{AjaxUploader.jsx => AjaxUploader.tsx} | 11 +++- src/Upload.jsx | 39 ------------ src/Upload.tsx | 73 ++++++++++++++++++++++ 3 files changed, 81 insertions(+), 42 deletions(-) rename src/{AjaxUploader.jsx => AjaxUploader.tsx} (97%) delete mode 100644 src/Upload.jsx create mode 100644 src/Upload.tsx diff --git a/src/AjaxUploader.jsx b/src/AjaxUploader.tsx similarity index 97% rename from src/AjaxUploader.jsx rename to src/AjaxUploader.tsx index d6a45f2..c4ae20e 100644 --- a/src/AjaxUploader.jsx +++ b/src/AjaxUploader.tsx @@ -5,6 +5,7 @@ import defaultRequest from './request'; import getUid from './uid'; import attrAccept from './attr-accept'; import traverseFileTree from './traverseFileTree'; +import { UploadProps } from './Upload'; const dataOrAriaAttributeProps = props => { return Object.keys(props).reduce((acc, key) => { @@ -15,11 +16,15 @@ const dataOrAriaAttributeProps = props => { }, {}); }; -class AjaxUploader extends Component { +class AjaxUploader extends Component { state = { uid: getUid() }; reqs = {}; + private fileInput: any; + + private _isMounted: boolean; + onChange = e => { const { files } = e.target; this.uploadFiles(files); @@ -44,7 +49,7 @@ class AjaxUploader extends Component { onKeyDown = e => { if (e.key === 'Enter') { - this.onClick(); + this.onClick(e); } }; @@ -80,7 +85,7 @@ class AjaxUploader extends Component { componentWillUnmount() { this._isMounted = false; - this.abort(); + this.abort(''); } uploadFiles = files => { diff --git a/src/Upload.jsx b/src/Upload.jsx deleted file mode 100644 index 303a774..0000000 --- a/src/Upload.jsx +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint react/prop-types:0 */ -import React, { Component } from 'react'; -import AjaxUpload from './AjaxUploader'; - -function empty() { -} - -class Upload extends Component { - static defaultProps = { - component: 'span', - prefixCls: 'rc-upload', - data: {}, - headers: {}, - name: 'file', - multipart: false, - onStart: empty, - onError: empty, - onSuccess: empty, - multiple: false, - beforeUpload: null, - customRequest: null, - withCredentials: false, - openFileDialogOnClick: true, - } - - abort(file) { - this.uploader.abort(file); - } - - saveUploader = (node) => { - this.uploader = node; - } - - render() { - return ; - } -} - -export default Upload; diff --git a/src/Upload.tsx b/src/Upload.tsx new file mode 100644 index 0000000..b2d9238 --- /dev/null +++ b/src/Upload.tsx @@ -0,0 +1,73 @@ +/* eslint react/prop-types:0 */ +import React, { Component } from 'react'; +import AjaxUpload from './AjaxUploader'; + +function empty() {} + +export interface HttpRequestHeader { + [key: string]: string; +} + +export interface UploadProps { + name?: string; + style?: React.CSSProperties; + className?: string; + disabled?: boolean; + component?: string; + action?: string | ((file: File) => string); + method?: 'POST' | 'PUT' | 'PATCH' | 'post' | 'put' | 'patch'; + directory?: boolean; + data?: object | ((file: File | string | Blob) => object); + headers?: HttpRequestHeader; + accept?: string; + multiple?: boolean; + onStart?: (file: File) => void; + onError?: (error: Error, ret: object, file: File) => void; + onSuccess?: (response: object, file: File, xhr: object) => void; + onProgress?: (event: { percent: number }, file: File) => void; + beforeUpload?: (file: File, FileList: File[]) => boolean | PromiseLike; + customRequest?: () => void; + withCredentials?: boolean; + openFileDialogOnClick?: boolean; + transformFile?: (file: File) => string | Blob | File | PromiseLike; + prefixCls?: string; + id?: string; + onMouseEnter?: () => void; + onMouseLeave?: () => void; + onClick?: (e) => void; +} + +class Upload extends Component { + static defaultProps = { + component: 'span', + prefixCls: 'rc-upload', + data: {}, + headers: {}, + name: 'file', + multipart: false, + onStart: empty, + onError: empty, + onSuccess: empty, + multiple: false, + beforeUpload: null, + customRequest: null, + withCredentials: false, + openFileDialogOnClick: true, + }; + + private uploader: any; + + abort(file) { + this.uploader.abort(file); + } + + saveUploader = node => { + this.uploader = node; + }; + + render() { + return ; + } +} + +export default Upload; From d32f18b949010f8d0b5ac747b8b739b6305b4dd2 Mon Sep 17 00:00:00 2001 From: Kermit Date: Sat, 29 Aug 2020 22:40:15 +0800 Subject: [PATCH 16/21] refactor: change tsx --- src/AjaxUploader.tsx | 68 ++++++++++--------- src/Upload.tsx | 34 +--------- src/{attr-accept.js => attr-accept.ts} | 4 +- src/{index.js => index.ts} | 1 - src/interface.tsx | 58 ++++++++++++++++ src/{request.js => request.ts} | 27 +++----- ...raverseFileTree.js => traverseFileTree.ts} | 22 ++++-- src/{uid.js => uid.ts} | 0 8 files changed, 123 insertions(+), 91 deletions(-) rename src/{attr-accept.js => attr-accept.ts} (86%) rename src/{index.js => index.ts} (65%) create mode 100644 src/interface.tsx rename src/{request.js => request.ts} (81%) rename src/{traverseFileTree.js => traverseFileTree.ts} (69%) rename src/{uid.js => uid.ts} (100%) diff --git a/src/AjaxUploader.tsx b/src/AjaxUploader.tsx index c4ae20e..c5d7f38 100644 --- a/src/AjaxUploader.tsx +++ b/src/AjaxUploader.tsx @@ -1,13 +1,13 @@ /* eslint react/no-is-mounted:0,react/sort-comp:0,react/prop-types:0 */ -import React, { Component } from 'react'; +import React, { Component, ReactElement } from 'react'; import classNames from 'classnames'; import defaultRequest from './request'; import getUid from './uid'; import attrAccept from './attr-accept'; import traverseFileTree from './traverseFileTree'; -import { UploadProps } from './Upload'; +import { UploadProps, UploadProgressEvent, UploadRequestError } from './interface'; -const dataOrAriaAttributeProps = props => { +const dataOrAriaAttributeProps = (props: React.AriaAttributes | React.DataHTMLAttributes) => { return Object.keys(props).reduce((acc, key) => { if (key.substr(0, 5) === 'data-' || key.substr(0, 5) === 'aria-' || key === 'role') { acc[key] = props[key]; @@ -19,27 +19,28 @@ const dataOrAriaAttributeProps = props => { class AjaxUploader extends Component { state = { uid: getUid() }; - reqs = {}; + reqs: any = {}; - private fileInput: any; + private fileInput: HTMLInputElement; private _isMounted: boolean; - onChange = e => { + onChange = (e: React.ChangeEvent) => { const { files } = e.target; this.uploadFiles(files); this.reset(); }; - onClick = e => { + onClick = (e: React.MouseEvent | React.KeyboardEvent) => { const el = this.fileInput; if (!el) { return; } const { children, onClick } = this.props; - if (children && children.type === 'button') { - el.parentNode.focus(); - el.parentNode.querySelector('button').blur(); + if (children && (children as ReactElement).type === 'button') { + const parent = el.parentNode as HTMLInputElement; + parent.focus(); + parent.querySelector('button').blur(); } el.click(); if (onClick) { @@ -47,13 +48,13 @@ class AjaxUploader extends Component { } }; - onKeyDown = e => { + onKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { this.onClick(e); } }; - onFileDrop = e => { + onFileDrop = (e: React.DragEvent) => { const { multiple } = this.props; e.preventDefault(); @@ -63,13 +64,15 @@ class AjaxUploader extends Component { } if (this.props.directory) { - traverseFileTree(Array.prototype.slice.call(e.dataTransfer.items), this.uploadFiles, _file => - attrAccept(_file, this.props.accept), + traverseFileTree( + Array.prototype.slice.call(e.dataTransfer.items), + this.uploadFiles, + (_file: File) => attrAccept(_file, this.props.accept), ); } else { let files = Array.prototype.slice .call(e.dataTransfer.files) - .filter(file => attrAccept(file, this.props.accept)); + .filter((file: File) => attrAccept(file, this.props.accept)); if (multiple === false) { files = files.slice(0, 1); @@ -88,10 +91,10 @@ class AjaxUploader extends Component { this.abort(''); } - uploadFiles = files => { - const postFiles = Array.prototype.slice.call(files); + uploadFiles = (files: FileList) => { + const postFiles: Array = Array.prototype.slice.call(files); postFiles - .map(file => { + .map((file: File & { uid?: string }) => { // eslint-disable-next-line no-param-reassign file.uid = getUid(); return file; @@ -101,7 +104,7 @@ class AjaxUploader extends Component { }); }; - upload(file, fileList) { + upload(file: File, fileList: Array) { const { props } = this; if (!props.beforeUpload) { // always async in case use react state to keep fileList @@ -109,7 +112,7 @@ class AjaxUploader extends Component { } const before = props.beforeUpload(file, fileList); - if (before && before.then) { + if (before && typeof before !== 'boolean' && before.then) { before .then(processedFile => { const processedFileType = Object.prototype.toString.call(processedFile); @@ -128,7 +131,7 @@ class AjaxUploader extends Component { return undefined; } - post(file) { + post(file: File) { if (!this._isMounted) { return; } @@ -141,8 +144,8 @@ class AjaxUploader extends Component { action = action(file); } return resolve(action); - }).then(action => { - const { uid } = file; + }).then((action: string) => { + const { uid } = file as any; const request = props.customRequest || defaultRequest; const transform = Promise.resolve(transformFile(file)) .then(transformedFile => { @@ -156,7 +159,7 @@ class AjaxUploader extends Component { console.error(e); // eslint-disable-line no-console }); - transform.then(([transformedFile, data]) => { + transform.then(([transformedFile, data]: [File, object]) => { const requestOption = { action, filename: props.name, @@ -166,15 +169,15 @@ class AjaxUploader extends Component { withCredentials: props.withCredentials, method: props.method || 'post', onProgress: onProgress - ? e => { + ? (e: UploadProgressEvent) => { onProgress(e, file); } : null, - onSuccess: (ret, xhr) => { + onSuccess: (ret: any, xhr: XMLHttpRequest) => { delete this.reqs[uid]; props.onSuccess(ret, file, xhr); }, - onError: (err, ret) => { + onError: (err: UploadRequestError, ret: any) => { delete this.reqs[uid]; props.onError(err, ret, file); }, @@ -192,7 +195,7 @@ class AjaxUploader extends Component { }); } - abort(file) { + abort(file: any) { const { reqs } = this; if (file) { const uid = file && file.uid ? file.uid : file; @@ -210,7 +213,7 @@ class AjaxUploader extends Component { } } - saveFileInput = node => { + saveFileInput = (node: HTMLInputElement) => { this.fileInput = node; }; @@ -236,6 +239,10 @@ class AjaxUploader extends Component { [`${prefixCls}-disabled`]: disabled, [className]: className, }); + // because input don't have directory/webkitdirectory type declaration + const dirProps: any = directory + ? { directory: 'directory', webkitdirectory: 'webkitdirectory' } + : {}; const events = disabled ? {} : { @@ -258,8 +265,7 @@ class AjaxUploader extends Component { key={this.state.uid} style={{ display: 'none' }} accept={accept} - directory={directory ? 'directory' : null} - webkitdirectory={directory ? 'webkitdirectory' : null} + {...dirProps} multiple={multiple} onChange={this.onChange} /> diff --git a/src/Upload.tsx b/src/Upload.tsx index b2d9238..254ce4c 100644 --- a/src/Upload.tsx +++ b/src/Upload.tsx @@ -1,42 +1,10 @@ /* eslint react/prop-types:0 */ import React, { Component } from 'react'; import AjaxUpload from './AjaxUploader'; +import { UploadProps } from './interface'; function empty() {} -export interface HttpRequestHeader { - [key: string]: string; -} - -export interface UploadProps { - name?: string; - style?: React.CSSProperties; - className?: string; - disabled?: boolean; - component?: string; - action?: string | ((file: File) => string); - method?: 'POST' | 'PUT' | 'PATCH' | 'post' | 'put' | 'patch'; - directory?: boolean; - data?: object | ((file: File | string | Blob) => object); - headers?: HttpRequestHeader; - accept?: string; - multiple?: boolean; - onStart?: (file: File) => void; - onError?: (error: Error, ret: object, file: File) => void; - onSuccess?: (response: object, file: File, xhr: object) => void; - onProgress?: (event: { percent: number }, file: File) => void; - beforeUpload?: (file: File, FileList: File[]) => boolean | PromiseLike; - customRequest?: () => void; - withCredentials?: boolean; - openFileDialogOnClick?: boolean; - transformFile?: (file: File) => string | Blob | File | PromiseLike; - prefixCls?: string; - id?: string; - onMouseEnter?: () => void; - onMouseLeave?: () => void; - onClick?: (e) => void; -} - class Upload extends Component { static defaultProps = { component: 'span', diff --git a/src/attr-accept.js b/src/attr-accept.ts similarity index 86% rename from src/attr-accept.js rename to src/attr-accept.ts index 1dafef1..c595885 100644 --- a/src/attr-accept.js +++ b/src/attr-accept.ts @@ -1,8 +1,8 @@ -function endsWith(str, suffix) { +function endsWith(str: string, suffix: string) { return str.indexOf(suffix, str.length - suffix.length) !== -1; } -export default (file, acceptedFiles) => { +export default (file: File, acceptedFiles: string | Array) => { if (file && acceptedFiles) { const acceptedFilesArray = Array.isArray(acceptedFiles) ? acceptedFiles diff --git a/src/index.js b/src/index.ts similarity index 65% rename from src/index.js rename to src/index.ts index 1eeceb0..9679701 100644 --- a/src/index.js +++ b/src/index.ts @@ -1,4 +1,3 @@ -// export this package's api import Upload from './Upload'; export default Upload; diff --git a/src/interface.tsx b/src/interface.tsx new file mode 100644 index 0000000..b1fa417 --- /dev/null +++ b/src/interface.tsx @@ -0,0 +1,58 @@ +export interface UploadProps + extends Omit, 'onError' | 'onProgress'> { + name?: string; + style?: React.CSSProperties; + className?: string; + disabled?: boolean; + component?: React.JSXElementConstructor; + action?: string | ((file: File) => string); + method?: UploadRequestMethod; + directory?: boolean; + data?: object | ((file: File | string | Blob) => object); + headers?: UploadRequestHeader; + accept?: string; + multiple?: boolean; + onStart?: (file: File) => void; + onError?: (error: Error, ret: object, file: File) => void; + onSuccess?: (response: object, file: File, xhr: object) => void; + onProgress?: (event: UploadProgressEvent, file: File) => void; + beforeUpload?: (file: File, FileList: File[]) => boolean | Promise; + customRequest?: () => void; + withCredentials?: boolean; + openFileDialogOnClick?: boolean; + transformFile?: (file: File) => string | Blob | File | PromiseLike; + prefixCls?: string; + id?: string; + onMouseEnter?: (e: React.MouseEvent) => void; + onMouseLeave?: (e: React.MouseEvent) => void; + onClick?: (e: React.MouseEvent | React.KeyboardEvent) => void; +} + +export interface UploadProgressEvent extends ProgressEvent { + percent: number; +} + +export type UploadRequestMethod = 'POST' | 'PUT' | 'PATCH' | 'post' | 'put' | 'patch'; + +export interface UploadRequestHeader { + [key: string]: string; +} + +export interface UploadRequestError extends Error { + status?: number; + method?: UploadRequestMethod; + url?: string; +} + +export interface UploadRequestOption { + onProgress?: (event: UploadProgressEvent) => void; + onError?: (event: UploadRequestError | ProgressEvent, body?: T) => void; + onSuccess?: (body: T, xhr: XMLHttpRequest) => void; + data?: object; + filename?: string; + file: File; + withCredentials?: boolean; + action: string; + headers?: UploadRequestHeader; + method: UploadRequestMethod; +} diff --git a/src/request.js b/src/request.ts similarity index 81% rename from src/request.js rename to src/request.ts index 38b4c89..d41b38e 100644 --- a/src/request.js +++ b/src/request.ts @@ -1,13 +1,15 @@ -function getError(option, xhr) { +import { UploadRequestOption, UploadRequestError, UploadProgressEvent } from './interface'; + +function getError(option: UploadRequestOption, xhr: XMLHttpRequest) { const msg = `cannot ${option.method} ${option.action} ${xhr.status}'`; - const err = new Error(msg); + const err = new Error(msg) as UploadRequestError; err.status = xhr.status; err.method = option.method; err.url = option.action; return err; } -function getBody(xhr) { +function getBody(xhr: XMLHttpRequest) { const text = xhr.responseText || xhr.response; if (!text) { return text; @@ -20,25 +22,14 @@ function getBody(xhr) { } } -// option { -// onProgress: (event: { percent: number }): void, -// onError: (event: Error, body?: Object): void, -// onSuccess: (body: Object): void, -// data: Object, -// filename: String, -// file: File, -// withCredentials: Boolean, -// action: String, -// headers: Object, -// } -export default function upload(option) { +export default function upload(option: UploadRequestOption) { // eslint-disable-next-line no-undef const xhr = new XMLHttpRequest(); if (option.onProgress && xhr.upload) { - xhr.upload.onprogress = function progress(e) { + xhr.upload.onprogress = function progress(e: UploadProgressEvent) { if (e.total > 0) { - e.percent = e.loaded / e.total * 100; + e.percent = (e.loaded / e.total) * 100; } option.onProgress(e); }; @@ -103,7 +94,7 @@ export default function upload(option) { Object.keys(headers).forEach(h => { if (headers[h] !== null) { xhr.setRequestHeader(h, headers[h]); - } + } }); xhr.send(formData); diff --git a/src/traverseFileTree.js b/src/traverseFileTree.ts similarity index 69% rename from src/traverseFileTree.js rename to src/traverseFileTree.ts index bbfbc9f..bfef99e 100644 --- a/src/traverseFileTree.js +++ b/src/traverseFileTree.ts @@ -1,9 +1,19 @@ -function loopFiles(item, callback) { +interface InternalDataTransferItem extends DataTransferItem { + isFile: boolean; + file: (cd: (file: File & { webkitRelativePath?: string }) => void) => void; + createReader: () => any; + fullPath: string; + isDirectory: boolean; + name: string; + path: string; +} + +function loopFiles(item: InternalDataTransferItem, callback) { const dirReader = item.createReader(); let fileList = []; function sequence() { - dirReader.readEntries(entries => { + dirReader.readEntries((entries: Array) => { const entryList = Array.prototype.slice.apply(entries); fileList = fileList.concat(entryList); @@ -21,11 +31,11 @@ function loopFiles(item, callback) { sequence(); } -const traverseFileTree = (files, callback, isAccepted) => { +const traverseFileTree = (files: Array, callback, isAccepted) => { // eslint-disable-next-line @typescript-eslint/naming-convention - const _traverseFileTree = (item, path) => { + const _traverseFileTree = (item: InternalDataTransferItem, path?: string) => { // eslint-disable-next-line no-param-reassign - path = path || ''; + item.path = path || ''; if (item.isFile) { item.file(file => { if (isAccepted(file)) { @@ -48,7 +58,7 @@ const traverseFileTree = (files, callback, isAccepted) => { } }); } else if (item.isDirectory) { - loopFiles(item, entries => { + loopFiles(item, (entries: Array) => { entries.forEach(entryItem => { _traverseFileTree(entryItem, `${path}${item.name}/`); }); diff --git a/src/uid.js b/src/uid.ts similarity index 100% rename from src/uid.js rename to src/uid.ts From 1e515bad09554ef9c2ee09ddb080d4ad7a6cf08c Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Mon, 31 Aug 2020 10:38:36 +0800 Subject: [PATCH 17/21] add UploadProps export --- src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/index.ts b/src/index.ts index 9679701..1cbbd3e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,6 @@ import Upload from './Upload'; +import { UploadProps } from './interface'; + +export { UploadProps }; export default Upload; From ef2ebbc2663f68e845d39b4ff739092bed7c5ca7 Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Mon, 31 Aug 2020 11:13:28 +0800 Subject: [PATCH 18/21] RCFile & pickAttrs --- package.json | 5 +++-- src/AjaxUploader.tsx | 32 ++++++++++++-------------------- src/attr-accept.ts | 4 +++- src/interface.tsx | 26 +++++++++++++++++--------- src/traverseFileTree.ts | 4 +++- 5 files changed, 38 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index 012cac1..da896af 100644 --- a/package.json +++ b/package.json @@ -41,9 +41,9 @@ "@types/react-dom": "^16.9.0", "@umijs/fabric": "^2.0.0", "axios": "^0.20.0", - "cross-env": "^7.0.0", "co-busboy": "^1.3.0", "coveralls": "^3.0.3", + "cross-env": "^7.0.0", "enzyme": "^3.1.1", "enzyme-adapter-react-16": "^1.0.1", "enzyme-to-json": "^3.1.2", @@ -61,7 +61,8 @@ }, "dependencies": { "@babel/runtime": "^7.10.1", - "classnames": "^2.2.5" + "classnames": "^2.2.5", + "rc-util": "^5.2.0" }, "jest": { "collectCoverageFrom": [ diff --git a/src/AjaxUploader.tsx b/src/AjaxUploader.tsx index c5d7f38..88f268b 100644 --- a/src/AjaxUploader.tsx +++ b/src/AjaxUploader.tsx @@ -1,20 +1,12 @@ /* eslint react/no-is-mounted:0,react/sort-comp:0,react/prop-types:0 */ import React, { Component, ReactElement } from 'react'; import classNames from 'classnames'; +import pickAttrs from 'rc-util/lib/pickAttrs'; import defaultRequest from './request'; import getUid from './uid'; import attrAccept from './attr-accept'; import traverseFileTree from './traverseFileTree'; -import { UploadProps, UploadProgressEvent, UploadRequestError } from './interface'; - -const dataOrAriaAttributeProps = (props: React.AriaAttributes | React.DataHTMLAttributes) => { - return Object.keys(props).reduce((acc, key) => { - if (key.substr(0, 5) === 'data-' || key.substr(0, 5) === 'aria-' || key === 'role') { - acc[key] = props[key]; - } - return acc; - }, {}); -}; +import { UploadProps, UploadProgressEvent, UploadRequestError, RcFile } from './interface'; class AjaxUploader extends Component { state = { uid: getUid() }; @@ -67,12 +59,12 @@ class AjaxUploader extends Component { traverseFileTree( Array.prototype.slice.call(e.dataTransfer.items), this.uploadFiles, - (_file: File) => attrAccept(_file, this.props.accept), + (_file: RcFile) => attrAccept(_file, this.props.accept), ); } else { let files = Array.prototype.slice .call(e.dataTransfer.files) - .filter((file: File) => attrAccept(file, this.props.accept)); + .filter((file: RcFile) => attrAccept(file, this.props.accept)); if (multiple === false) { files = files.slice(0, 1); @@ -88,13 +80,13 @@ class AjaxUploader extends Component { componentWillUnmount() { this._isMounted = false; - this.abort(''); + this.abort(); } uploadFiles = (files: FileList) => { - const postFiles: Array = Array.prototype.slice.call(files); + const postFiles: Array = Array.prototype.slice.call(files); postFiles - .map((file: File & { uid?: string }) => { + .map((file: RcFile & { uid?: string }) => { // eslint-disable-next-line no-param-reassign file.uid = getUid(); return file; @@ -104,7 +96,7 @@ class AjaxUploader extends Component { }); }; - upload(file: File, fileList: Array) { + upload(file: RcFile, fileList: Array) { const { props } = this; if (!props.beforeUpload) { // always async in case use react state to keep fileList @@ -131,7 +123,7 @@ class AjaxUploader extends Component { return undefined; } - post(file: File) { + post(file: RcFile) { if (!this._isMounted) { return; } @@ -159,7 +151,7 @@ class AjaxUploader extends Component { console.error(e); // eslint-disable-line no-console }); - transform.then(([transformedFile, data]: [File, object]) => { + transform.then(([transformedFile, data]: [RcFile, object]) => { const requestOption = { action, filename: props.name, @@ -195,7 +187,7 @@ class AjaxUploader extends Component { }); } - abort(file: any) { + abort(file?: any) { const { reqs } = this; if (file) { const uid = file && file.uid ? file.uid : file; @@ -257,7 +249,7 @@ class AjaxUploader extends Component { return ( ) => { +export default (file: RcFile, acceptedFiles: string | Array) => { if (file && acceptedFiles) { const acceptedFilesArray = Array.isArray(acceptedFiles) ? acceptedFiles diff --git a/src/interface.tsx b/src/interface.tsx index b1fa417..a8a42a6 100644 --- a/src/interface.tsx +++ b/src/interface.tsx @@ -1,3 +1,5 @@ +import * as React from 'react'; + export interface UploadProps extends Omit, 'onError' | 'onProgress'> { name?: string; @@ -5,22 +7,22 @@ export interface UploadProps className?: string; disabled?: boolean; component?: React.JSXElementConstructor; - action?: string | ((file: File) => string); + action?: string | ((file: RcFile) => string); method?: UploadRequestMethod; directory?: boolean; - data?: object | ((file: File | string | Blob) => object); + data?: object | ((file: RcFile | string | Blob) => object); headers?: UploadRequestHeader; accept?: string; multiple?: boolean; - onStart?: (file: File) => void; - onError?: (error: Error, ret: object, file: File) => void; - onSuccess?: (response: object, file: File, xhr: object) => void; - onProgress?: (event: UploadProgressEvent, file: File) => void; - beforeUpload?: (file: File, FileList: File[]) => boolean | Promise; + onStart?: (file: RcFile) => void; + onError?: (error: Error, ret: object, file: RcFile) => void; + onSuccess?: (response: object, file: RcFile, xhr: object) => void; + onProgress?: (event: UploadProgressEvent, file: RcFile) => void; + beforeUpload?: (file: RcFile, FileList: RcFile[]) => boolean | Promise; customRequest?: () => void; withCredentials?: boolean; openFileDialogOnClick?: boolean; - transformFile?: (file: File) => string | Blob | File | PromiseLike; + transformFile?: (file: RcFile) => string | Blob | RcFile | PromiseLike; prefixCls?: string; id?: string; onMouseEnter?: (e: React.MouseEvent) => void; @@ -50,9 +52,15 @@ export interface UploadRequestOption { onSuccess?: (body: T, xhr: XMLHttpRequest) => void; data?: object; filename?: string; - file: File; + file: RcFile; withCredentials?: boolean; action: string; headers?: UploadRequestHeader; method: UploadRequestMethod; } + +export interface RcFile extends File { + uid: string; + readonly lastModifiedDate: Date; + readonly webkitRelativePath: string; +} diff --git a/src/traverseFileTree.ts b/src/traverseFileTree.ts index bfef99e..9de92cc 100644 --- a/src/traverseFileTree.ts +++ b/src/traverseFileTree.ts @@ -1,6 +1,8 @@ +import { RcFile } from './interface'; + interface InternalDataTransferItem extends DataTransferItem { isFile: boolean; - file: (cd: (file: File & { webkitRelativePath?: string }) => void) => void; + file: (cd: (file: RcFile & { webkitRelativePath?: string }) => void) => void; createReader: () => any; fullPath: string; isDirectory: boolean; From cfcf2d215d3d762c1ae58bd880f345bb59d7e578 Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Mon, 31 Aug 2020 11:25:24 +0800 Subject: [PATCH 19/21] fix lint --- src/Upload.tsx | 6 +++--- src/interface.tsx | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Upload.tsx b/src/Upload.tsx index 254ce4c..ba89349 100644 --- a/src/Upload.tsx +++ b/src/Upload.tsx @@ -1,7 +1,7 @@ /* eslint react/prop-types:0 */ import React, { Component } from 'react'; import AjaxUpload from './AjaxUploader'; -import { UploadProps } from './interface'; +import { UploadProps, RcFile } from './interface'; function empty() {} @@ -25,11 +25,11 @@ class Upload extends Component { private uploader: any; - abort(file) { + abort(file: RcFile) { this.uploader.abort(file); } - saveUploader = node => { + saveUploader = (node: AjaxUpload) => { this.uploader = node; }; diff --git a/src/interface.tsx b/src/interface.tsx index a8a42a6..a919680 100644 --- a/src/interface.tsx +++ b/src/interface.tsx @@ -61,6 +61,4 @@ export interface UploadRequestOption { export interface RcFile extends File { uid: string; - readonly lastModifiedDate: Date; - readonly webkitRelativePath: string; } From a58f85999fada77011a49680ee2fa8db7a8f3a63 Mon Sep 17 00:00:00 2001 From: xrkffgg Date: Mon, 31 Aug 2020 11:29:29 +0800 Subject: [PATCH 20/21] fix alert --- src/AjaxUploader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AjaxUploader.tsx b/src/AjaxUploader.tsx index 88f268b..b1d387a 100644 --- a/src/AjaxUploader.tsx +++ b/src/AjaxUploader.tsx @@ -190,7 +190,7 @@ class AjaxUploader extends Component { abort(file?: any) { const { reqs } = this; if (file) { - const uid = file && file.uid ? file.uid : file; + const uid = file.uid ? file.uid : file; if (reqs[uid] && reqs[uid].abort) { reqs[uid].abort(); } From b8538d6eee362d0e6a293cf243bbccc2dc481b46 Mon Sep 17 00:00:00 2001 From: Kermit Date: Mon, 31 Aug 2020 11:48:25 +0800 Subject: [PATCH 21/21] fix: types of upload --- src/AjaxUploader.tsx | 2 +- src/Upload.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AjaxUploader.tsx b/src/AjaxUploader.tsx index b1d387a..c19fade 100644 --- a/src/AjaxUploader.tsx +++ b/src/AjaxUploader.tsx @@ -137,7 +137,7 @@ class AjaxUploader extends Component { } return resolve(action); }).then((action: string) => { - const { uid } = file as any; + const { uid } = file; const request = props.customRequest || defaultRequest; const transform = Promise.resolve(transformFile(file)) .then(transformedFile => { diff --git a/src/Upload.tsx b/src/Upload.tsx index ba89349..10df10a 100644 --- a/src/Upload.tsx +++ b/src/Upload.tsx @@ -23,7 +23,7 @@ class Upload extends Component { openFileDialogOnClick: true, }; - private uploader: any; + private uploader: AjaxUpload; abort(file: RcFile) { this.uploader.abort(file);