diff --git a/.babelrc b/.babelrc index 6306550..30366ba 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,3 @@ { - "presets": ['es2015', 'react', 'stage-0'] + "presets": ['es2015', 'react'] } \ No newline at end of file diff --git a/.eslintrc b/.eslintrc index e89ba50..d6a0709 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,3 +1,21 @@ { - "extends": "airbnb" + "env": { + "browser": true, + "node": true, + "es6": true + }, + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "plugins": [ + "react" + ], + "extends": [ + "eslint:recommended", + "plugin:react/recommended" + ] } \ No newline at end of file diff --git a/examples/basic/client.js b/examples/basic/client.js index 80a5a79..8a2129e 100644 --- a/examples/basic/client.js +++ b/examples/basic/client.js @@ -1,162 +1,167 @@ -var React = require('react'); -var ReactDOM = require('react-dom'); -var FileUploader = require('../../lib'); -var _ = require('lodash'); - -var MyComponent = React.createClass({ - getInitialState: function() { - return { - isPanelOpen: false, - isDragOver: false, - files: [] - } - }, - - openPanel: function() { - this.setState({ isPanelOpen: true }); - }, - - closePanel: function() { - this.setState({ isPanelOpen: false }); - }, - - onDragOver: function(e) { - // your codes here: - // if you want to check if the files are dragged over - // a specific DOM node - //if (e.target === node) { - // this.setState({ - // isDragOver: true - // }); - //} - }, - - onFileDrop: function({ target }, files) { - let node = ReactDOM.findDOMNode(this.refs.uploadPanel); - if (target != node) { - return false; - } - - files.map(function(_file) { - if (_file.size > 1000 * 1000) { - _file.status = FileUploader.status.FAILED; - _file.error = 'file size exceeded maximum' - } - }); +import React, { Component } from 'react'; +import ReactDOM from 'react-dom'; +import _ from 'lodash'; +import * as FileUploader from '../../src/index'; + +class MyComponent extends Component { + constructor(props) { + super(props); + + this.state = { + isPanelOpen: false, + isDragOver: false, + files: [], + }; + + this.uploadPanel = undefined; + this.openPanel = this.openPanel.bind(this); + this.closePanel = this.closePanel.bind(this); + this.onDragOver = this.onDragOver.bind(this); + this.onFileDrop = this.onFileDrop.bind(this); + this.onFileProgress = this.onFileProgress.bind(this); + this.onFileUpdate = this.onFileUpdate.bind(this); + this.setUploadPanelRef = this.setUploadPanelRef.bind(this); + } + + openPanel() { + this.setState({ isPanelOpen: true }); + } + + closePanel() { + this.setState({ isPanelOpen: false }); + } + + onDragOver(e) { + // your codes here: + // if you want to check if the files are dragged over + // a specific DOM node + const node = ReactDOM.findDOMNode(this.uploadPanel); + + if (this.state.isDragOver !== (e.target === node)) { + this.setState({ + isDragOver: e.target === node, + }); + } + } - this.setState({ - files: this.state.files.concat(files) - }); + onFileDrop({ target }, files) { + const node = ReactDOM.findDOMNode(this.uploadPanel); - // if you want to close the panel upon file drop - this.closePanel(); - }, + if (target !== node) { + this.closePanel(); + return false; + } - onFileProgress: function(file) { - var files = this.state.files; + files.forEach(item => { + if (item.size > 1000 * 1000) { + item.status = FileUploader.status.FAILED; + item.error = 'file size exceeded maximum'; + } + }); - files.map(function(_file) { - if (_file.id === file.id) { - _file = file; - } + this.setState({ + files: this.state.files.concat(files), }); + // if you want to close the panel upon file drop + this.closePanel(); + } + + onFileProgress(file) { + const { files = [] } = this.state; + const newFiles = files.map(item => item.id === file.id ? file : item); + this.setState({ - files: files + files: newFiles, }); -}, + } - onFileUpdate: function(file) { - var files = this.state.files; + onFileUpdate(file) { + const { files = [] } = this.state; + const newFiles = files.map(item => item.id === file.id ? file : item); - files.map(function(_file) { - if (_file.id === file.id) { - _file = file; - } - }); - - this.setState({ - files: files - }); - }, - - _getStatusString: function(status) { - switch (status) { - case -1: - return 'failed'; - break; - - case 0: - return 'pending'; - break; - - case 1: - return 'uploading'; - break; - - case 2: - return 'uploaded'; - break; - } - }, - - render: function() { - var _this = this; - - return ( -
-

{ this.props.title }

-

You can upload files with size with 1 MB at maximum

- -

- { - !this.state.isDragOver ? 'Drop here' : 'Files detected' - } -

-
-
-

Upload List

- - { - this.state.files.map(function(file, index) { - return ( - -
- { file.name } -
- { file.id } - { file.type } - { file.size / 1000 / 1000 } MB - { file.progress }% - - {_this._getStatusString(file.status)} - - { file.error } -
-
-
- ) - }) - } -
-
-
- ); - } -}); + this.setState({ + files: newFiles, + }); + } + + setUploadPanelRef(ref) { + this.uploadPanel = ref; + } + + getStatusString(status) { + switch (status) { + case -1: + return 'failed'; + + case 0: + return 'pending'; + + case 1: + return 'uploading'; -ReactDOM.render(, document.getElementById('app')); \ No newline at end of file + case 2: + return 'uploaded'; + + default: + return ''; + } + } + + render() { + return ( +
+

{ this.props.title }

+

You can upload files with size with 1 MB at maximum

+ +

+ { + !this.state.isDragOver ? 'Drop here' : 'Files detected' + } +

+
+
+

Upload List

+ + { + this.state.files.map((file, index) => ( + +
+
{file.name}
+
+ {file.id} + {file.type} + {file.size / 1000 / 1000} MB + {file.progress}% + + {this.getStatusString(file.status)} + + {file.error} +
+
+
+ )) + } +
+
+
+ ); + } +} + +ReactDOM.render(, document.getElementById('app')); diff --git a/examples/basic/package.json b/examples/basic/package.json index dbfed66..5846c8d 100644 --- a/examples/basic/package.json +++ b/examples/basic/package.json @@ -1,15 +1,15 @@ { "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "browserify client.js -o bundle.js -t [ babelify --presets [ es2015 react] ]" + "build": "browserify client.js -o bundle.js -t [ babelify --presets [ es2015 react ] ]" }, "dependencies": { "connect-multiparty": "^2.0.0", "express": "^4.13.3", "forever": "^0.15.1", "lodash": "^4.13.1", - "react": "^0.14.3", - "react-dom": "^0.14.3" + "react": "^16.0.0", + "react-dom": "^16.0.0" }, "devDependencies": { "babel-preset-es2015": "^6.1.18", diff --git a/package.json b/package.json index 0cb7986..ca26bc0 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "react-file-uploader", - "version": "0.3.7", + "version": "0.4.0", "description": "A set of file-upload-components with React.js.", "main": "lib/index.js", "scripts": { "clean": "rm -rf lib", - "test": "jest fake-test.js", - "test:report": "jest fake-test.js --coverage", + "test": "jest", + "test:report": "jest --coverage", "build:lib": "babel src --out-dir lib", "build": "npm run eslint && npm run test && npm run clean && npm run build:lib", "eslint": "eslint ./src/*.js ./src/**/*.js", @@ -36,22 +36,17 @@ "babel-jest": "*", "babel-preset-es2015": "^6.1.18", "babel-preset-react": "^6.1.18", - "babel-preset-stage-0": "^6.1.18", - "eslint": "^2.11.1", - "eslint-config-airbnb": "^9.0.1", - "eslint-plugin-import": "^1.8.1", - "eslint-plugin-jsx-a11y": "^1.3.0", - "eslint-plugin-react": "^5.1.1", + "enzyme": "^3.3.0", + "enzyme-adapter-react-16": "^1.1.1", + "eslint": "^4.19.1", + "eslint-plugin-react": "^7.7.0", "jest-cli": "*", "jsdom": "^7.0.2", "nock": "^8.0.0", - "prop-types": "^15.5.10", - "react": "^0.14.8 || ^15.0.0", - "react-addons-test-utils": "^0.14.8 || ^15.0.0", - "react-dom": "^0.14.8 || ^15.0.0" + "react-dom": "^15.0.0 || ^16.0.0" }, "peerDependencies": { - "react": "^0.14.8 || ^15.0.0" + "react": "^15.0.0 || ^16.0.0" }, "jest": { "scriptPreprocessor": "/node_modules/babel-jest", @@ -74,6 +69,8 @@ "debug": "^2.2.0", "invariant": "^2.2.0", "lodash": ">=3.10.1", + "prop-types": "^15.5.10", + "react": "^15.0.0 || ^16.0.0", "shortid": "^2.2.6", "superagent": "^1.4.0" } diff --git a/src/Receiver.js b/src/Receiver.js index 64fafc1..fcf10e8 100644 --- a/src/Receiver.js +++ b/src/Receiver.js @@ -9,7 +9,16 @@ class Receiver extends Component { constructor(props) { super(props); - this.wrapper = document.getElementById(this.props.wrapperId) || window; + this.wrapper = window; + + if (props.wrapperId) { + this.wrapper = document.getElementById(props.wrapperId); + } + + if (!this.wrapper) { + throw new Error(`wrapper element with Id ${props.wrapperId} not found.`); + } + this.onDragEnter = this.onDragEnter.bind(this); this.onDragOver = this.onDragOver.bind(this); this.onDragLeave = this.onDragLeave.bind(this); @@ -24,8 +33,8 @@ class Receiver extends Component { componentDidMount() { invariant( - !!window.DragEvent && !!window.DataTransfer, - 'Upload end point must be provided to upload files' + (window.DragEvent || window.Event) && window.DataTransfer, + 'Browser does not support DnD events or File API.' ); this.wrapper.addEventListener('dragenter', this.onDragEnter); @@ -34,6 +43,13 @@ class Receiver extends Component { this.wrapper.addEventListener('drop', this.onFileDrop); } + componentWillReceiveProps(nextProps) { + if (nextProps.wrapperId !== this.props.wrapperId) { + // eslint-disable-next-line no-console + console.warn('[Receiver.js] Change in props.wrapperId is unexpected, no new event listeners will be created.'); + } + } + componentWillUnmount() { this.wrapper.removeEventListener('dragenter', this.onDragEnter); this.wrapper.removeEventListener('dragleave', this.onDragLeave); @@ -42,6 +58,10 @@ class Receiver extends Component { } onDragEnter(e) { + if (!e.dataTransfer.types.includes('Files')) { + return; + } + const dragLevel = this.state.dragLevel + 1; this.setState({ dragLevel }); @@ -73,10 +93,10 @@ class Receiver extends Component { const files = []; - if (!!e.dataTransfer) { + if (e.dataTransfer) { const fileList = e.dataTransfer.files || []; - for (let i = 0; i < fileList.length; i ++) { + for (let i = 0; i < fileList.length; i++) { fileList[i].id = shortid.generate(); fileList[i].status = status.PENDING; fileList[i].progress = 0; @@ -122,4 +142,8 @@ Receiver.propTypes = { wrapperId: PropTypes.string, }; +Receiver.defaultProps = { + isOpen: false +}; + export default Receiver; diff --git a/src/UploadHandler.js b/src/UploadHandler.js index bb015ae..8183b37 100644 --- a/src/UploadHandler.js +++ b/src/UploadHandler.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import uploadStatus from './constants/status'; -const debug = require('debug')('react-file-upload:UploadHandler'); +const debug = require('debug')('react-file-uploader:UploadHandler'); class UploadHandler extends Component { componentDidMount() { @@ -35,11 +35,11 @@ class UploadHandler extends Component { } render() { - const { component, key, customClass, style } = this.props; + const { component, customClass, style } = this.props; return React.createElement( component, - { key, className: classNames(customClass), style }, + { className: classNames(customClass), style }, this.props.children ); } @@ -57,7 +57,6 @@ UploadHandler.propTypes = { PropTypes.arrayOf(PropTypes.string), ]), file: PropTypes.object.isRequired, - key: PropTypes.string, style: PropTypes.object, upload: PropTypes.func, }; diff --git a/src/UploadManager.js b/src/UploadManager.js index fffb115..7778c12 100644 --- a/src/UploadManager.js +++ b/src/UploadManager.js @@ -2,13 +2,12 @@ import React, { Component, cloneElement } from 'react'; import PropTypes from 'prop-types'; import invariant from 'invariant'; import classNames from 'classnames'; -import assign from 'lodash/assign'; import bindKey from 'lodash/bindKey'; import clone from 'lodash/clone'; -import request from 'superagent'; +import superagent from 'superagent'; import uploadStatus from './constants/status'; -const debug = require('debug')('react-file-upload:UploadManager'); +const debug = require('debug')('react-file-uploader:UploadManager'); class UploadManager extends Component { constructor(props) { @@ -31,30 +30,46 @@ class UploadManager extends Component { upload(url, file) { const { + reqConfigs: { + accept = 'application/json', + method = 'post', + timeout, + withCredentials = false, + }, onUploadStart, onUploadEnd, onUploadProgress, + formDataParser, uploadErrorHandler, uploadHeader = {}, } = this.props; if (typeof onUploadStart === 'function') { - onUploadStart(assign(file, { status: uploadStatus.UPLOADING })); + onUploadStart(Object.assign(file, { status: uploadStatus.UPLOADING })); } - const formData = new FormData(); - formData.append('file', file); + let formData = new FormData(); + formData = formDataParser(formData, file); + + const request = superagent[method.toLowerCase()](url) + .accept(accept) + .set(uploadHeader); + + if (timeout) { + request.timeout(timeout); + } + + if (withCredentials) { + request.withCredentials(); + } debug(`start uploading file#${file.id} to ${url}`, file); request - .post(url) - .accept('application/json') - .set(uploadHeader) .send(formData) .on('progress', ({ percent }) => { if (typeof onUploadProgress === 'function') { - onUploadProgress(assign(file, { + onUploadProgress(Object.assign(file, { progress: percent, status: uploadStatus.UPLOADING, })); @@ -65,18 +80,17 @@ class UploadManager extends Component { if (error) { debug('failed to upload file', error); - - if (typeof onUploadEnd === 'function') { - onUploadEnd(assign(file, { error, status: uploadStatus.FAILED })); - } - - return; + } else { + debug('succeeded to upload file', result); } - debug('succeeded to upload file', res); - if (typeof onUploadEnd === 'function') { - onUploadEnd(assign(file, { result, status: uploadStatus.UPLOADED })); + onUploadEnd(Object.assign(file, { + progress: error && 0 || 100, + error, + result: error && undefined || result, + status: error && uploadStatus.FAILED || uploadStatus.UPLOADED + })); } }); } @@ -87,7 +101,7 @@ class UploadManager extends Component { return React.createElement( component, { className: classNames(customClass), style }, - React.Children.map(children, child => cloneElement(child, assign({ + React.Children.map(children, child => cloneElement(child, Object.assign({ upload: bindKey(this, 'upload', uploadUrl, child.props.file), }, child.props))) ); @@ -104,18 +118,33 @@ UploadManager.propTypes = { PropTypes.string, PropTypes.arrayOf(PropTypes.string), ]), + formDataParser: PropTypes.func, onUploadStart: PropTypes.func, onUploadProgress: PropTypes.func, onUploadEnd: PropTypes.func.isRequired, + reqConfigs: PropTypes.shape({ + accept: PropTypes.string, + method: PropTypes.string, + timeout: PropTypes.shape({ + response: PropTypes.number, + deadline: PropTypes.number, + }), + withCredentials: PropTypes.bool, + }), style: PropTypes.object, - uploadErrorHandler: PropTypes.func.isRequired, + uploadErrorHandler: PropTypes.func, uploadUrl: PropTypes.string.isRequired, uploadHeader: PropTypes.object, }; UploadManager.defaultProps = { component: 'ul', - uploadErrorHandler: (err, res) => { + formDataParser: (formData, file) => { + formData.append('file', file); + return formData; + }, + reqConfigs: {}, + uploadErrorHandler: (err, res = {}) => { let error = null; const body = clone(res.body); diff --git a/src/__tests__/Receiver-test.js b/src/__tests__/Receiver-test.js index 1b9ffc0..84a36dc 100644 --- a/src/__tests__/Receiver-test.js +++ b/src/__tests__/Receiver-test.js @@ -4,212 +4,254 @@ jest.dontMock('../index'); jest.dontMock('classnames'); import React from 'react'; -import ReactDOM from 'react-dom'; +import { shallow, configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; import { jsdom } from 'jsdom'; const FileUploader = require('../index'); const { PENDING } = FileUploader.status; const Receiver = FileUploader.Receiver; -const createComponent = (onDragEnter, onDragOver, onDragLeave, onFileDrop) => ( - -); - -// eslint-disable-next-line react/prefer-es6-class -const createTemplate = (initialState, props, Component) => React.createClass({ - getInitialState() { return initialState; }, - render() { - return ( - -

Test

-
- ); - }, -}); +configure({ adapter: new Adapter() }); + +const testFile = { + lastModified: 1465229147840, + lastModifiedDate: 'Tue Jun 07 2016 00:05:47 GMT+0800 (HKT)', + name: 'test.jpg', + size: 1024, + type: 'image/jpg', + webkitRelativePath: '', +}; + +const files = [testFile]; + +const createEvent = (eventName) => { + const event = document.createEvent('HTMLEvents'); + event.initEvent(eventName, false, true); + event.preventDefault = jest.genMockFn(); + event.dataTransfer = { + files, + setData: jest.genMockFunction(), + types: ['Files'] + }; + + return event; +}; describe('Receiver', () => { - beforeEach(function setting() { + let dragEnterEvent, + dragOverEvent, + dragLeaveEvent, + dropEvent, + stringClass = 'receiver', + arrayClass = ['react', 'receiver']; + + beforeEach(() => { global.document = jsdom(); global.window = document.parentWindow; + global.window.DragEvent = 'DragEvent'; + global.window.DataTransfer = 'DataTransfer'; + + dragEnterEvent = createEvent('dragenter'); + dragOverEvent = createEvent('dragover'); + dragLeaveEvent = createEvent('dragleave'); + dropEvent = createEvent('drop'); + }); + + describe('constructor', () => { + let emptyFn = () => {}, + component = ( + + ); + + it('should throw an error if DnD or File API is not supported', () => { + global.window.DragEvent = undefined; + global.window.DataTransfer = undefined; + + expect(() => shallow(component)).toThrow('Browser does not support DnD events or File API.'); + }); - const testFile = { - lastModified: 1465229147840, - lastModifiedDate: 'Tue Jun 07 2016 00:05:47 GMT+0800 (HKT)', - name: 'test.jpg', - size: 1024, - type: 'image/jpg', - webkitRelativePath: '', - }; - const files = [testFile]; - - const mockDT = { - files, - setData: jest.genMockFunction(), - }; - - this.stringClass = 'receiver'; - this.arrayClass = ['react', 'receiver']; - - this.dragEnterEvent = document.createEvent('HTMLEvents'); - this.dragEnterEvent.initEvent('dragenter', false, true); - - this.dragOverEvent = document.createEvent('HTMLEvents'); - this.dragOverEvent.initEvent('dragover', false, true); - this.dragOverEvent.preventDefault = jest.genMockFn(); - - this.dragLeaveEvent = document.createEvent('HTMLEvents'); - this.dragLeaveEvent.initEvent('dragleave', false, true); - - this.dropEvent = document.createEvent('HTMLEvents'); - this.dropEvent.initEvent('drop', false, true); - this.dropEvent.preventDefault = jest.genMockFn(); - this.dropEvent.dataTransfer = mockDT; + it('should assign window to this.wrapper if no wrapperId is provided', () => { + const receiver = shallow(component); + expect(receiver.instance().wrapper).toEqual(global.window); + }); + + it('should throw an error if wrapperId is given but element is not found', () => { + expect(() => shallow( + + )).toThrow(); + }); }); describe('state of dragLevel', () => { - beforeEach(function setting() { - const onDragEnter = jest.genMockFn(); - const onDragOver = jest.genMockFn(); - const onDragLeave = jest.genMockFn(); - const onFileDrop = jest.genMockFn(); - - this.onDragEnter = onDragEnter; - this.onDragOver = onDragOver; - this.onDragLeave = onDragLeave; - this.onFileDrop = onFileDrop; - - const Component = createComponent(onDragEnter, onDragOver, onDragLeave, onFileDrop); - const template = createTemplate({ isOpen: false, files: [] }, {}, Component); - - this.createTestParent = React.createFactory(template); - this.ParentComponent = this.createTestParent(); - this.container = document.createElement('div'); - this.instance = ReactDOM.render(this.ParentComponent, this.container); - this.receiver = this.instance.refs.receiver; + let receiver, + onDragEnter, + onDragOver, + onDragLeave, + onFileDrop; + + beforeEach(() => { + const mockOnDragEnter = jest.genMockFn(); + const mockOnDragOver = jest.genMockFn(); + const mockOnDragLeave = jest.genMockFn(); + const mockOnFileDrop = jest.genMockFn(); + + onDragEnter = mockOnDragEnter; + onDragOver = mockOnDragOver; + onDragLeave = mockOnDragLeave; + onFileDrop = mockOnFileDrop; + + const component = ( + + ); + + receiver = shallow(component); }); - it('should increase state of dragLevel by 1 with dragEnter event', function test() { - const oldDragLevel = this.receiver.state.dragLevel; - window.dispatchEvent(this.dragEnterEvent); - const newDragLevel = this.receiver.state.dragLevel; + it('should increase state of dragLevel by 1 with dragEnter event', () => { + const oldDragLevel = receiver.state().dragLevel; + window.dispatchEvent(dragEnterEvent); + const newDragLevel = receiver.state().dragLevel; expect(newDragLevel).toEqual(oldDragLevel + 1); }); - it('should call onDragEnter with dragEnter event if isOpen is false', function test() { - window.dispatchEvent(this.dragEnterEvent); - expect(this.onDragEnter).toBeCalled(); + it('should call onDragEnter with dragEnter event if isOpen is false', () => { + window.dispatchEvent(dragEnterEvent); + expect(onDragEnter).toBeCalled(); }); - it('should not call onDragEnter with dragEnter event if isOpen is true', function test() { - this.instance.setState({ isOpen: true }); - window.dispatchEvent(this.dragEnterEvent); - expect(this.onDragEnter).not.toBeCalled(); + it('should not call onDragEnter with dragEnter event if isOpen is true', () => { + receiver.setProps({ isOpen: true }); + window.dispatchEvent(dragEnterEvent); + expect(onDragEnter).not.toBeCalled(); }); - it('should call event.preventDefault with dragOver event', function test() { - window.dispatchEvent(this.dragOverEvent); - expect(this.dragOverEvent.preventDefault).toBeCalled(); + it('should not call onDragEnter callback with dragEnter event if dataTransfer.types does not include `Files`', () => { + onDragEnter = jest.genMockFn(); + dragEnterEvent.dataTransfer.types = ['HTMLElement']; + + receiver.setProps({ onDragEnter }); + + window.dispatchEvent(dragEnterEvent); + expect(onDragEnter).not.toBeCalled(); + }); + + it('should call event.preventDefault with dragOver event', () => { + window.dispatchEvent(dragOverEvent); + expect(dragOverEvent.preventDefault).toBeCalled(); }); - it('should call onDragOver with dragOver event', function test() { - window.dispatchEvent(this.dragOverEvent); - expect(this.onDragOver).toBeCalled(); + it('should call onDragOver with dragOver event', () => { + window.dispatchEvent(dragOverEvent); + expect(onDragOver).toBeCalled(); }); - it('should decrease state of dragLevel by 1 with dragLeave event', function test() { - const oldDragLevel = this.receiver.state.dragLevel; - window.dispatchEvent(this.dragEnterEvent); - const newDragLevel = this.receiver.state.dragLevel; + it('should decrease state of dragLevel by 1 with dragLeave event', () => { + const oldDragLevel = receiver.state().dragLevel; + window.dispatchEvent(dragEnterEvent); + const newDragLevel = receiver.state().dragLevel; expect(newDragLevel).toEqual(oldDragLevel + 1); - window.dispatchEvent(this.dragLeaveEvent); - const finalDragLevel = this.receiver.state.dragLevel; + window.dispatchEvent(dragLeaveEvent); + const finalDragLevel = receiver.state().dragLevel; expect(finalDragLevel).toEqual(newDragLevel - 1); - expect(this.onDragLeave).toBeCalled(); + expect(onDragLeave).toBeCalled(); }); - it('should call onDragLeave if state of dragLevel is not 0', function test() { - const oldDragLevel = this.receiver.state.dragLevel; - window.dispatchEvent(this.dragEnterEvent); - const newDragLevel = this.receiver.state.dragLevel; + it('should call onDragLeave if state of dragLevel is not 0', () => { + const oldDragLevel = receiver.state().dragLevel; + window.dispatchEvent(dragEnterEvent); + const newDragLevel = receiver.state().dragLevel; expect(newDragLevel).toEqual(oldDragLevel + 1); - window.dispatchEvent(this.dragEnterEvent); - const newerDragLevel = this.receiver.state.dragLevel; + window.dispatchEvent(dragEnterEvent); + const newerDragLevel = receiver.state().dragLevel; expect(newerDragLevel).toEqual(newDragLevel + 1); - window.dispatchEvent(this.dragLeaveEvent); - const finalDragLevel = this.receiver.state.dragLevel; + window.dispatchEvent(dragLeaveEvent); + const finalDragLevel = receiver.state().dragLevel; expect(finalDragLevel).toEqual(newerDragLevel - 1); - expect(this.onDragLeave).not.toBeCalled(); + expect(onDragLeave).not.toBeCalled(); - window.dispatchEvent(this.dragLeaveEvent); - const endDragLevel = this.receiver.state.dragLevel; + window.dispatchEvent(dragLeaveEvent); + const endDragLevel = receiver.state().dragLevel; expect(endDragLevel).toEqual(finalDragLevel - 1); - expect(this.onDragLeave).toBeCalled(); + expect(onDragLeave).toBeCalled(); }); - it('should call event.preventDefault with drop event', function test() { - window.dispatchEvent(this.dropEvent); + it('should call event.preventDefault with drop event', () => { + window.dispatchEvent(dropEvent); // eslint-disable-next-line no-undef - expect(this.dropEvent.preventDefault).toBeCalled(); + expect(dropEvent.preventDefault).toBeCalled(); }); - it('should call onFileDrop with drop event', function test() { - window.dispatchEvent(this.dropEvent); - expect(this.onFileDrop).toBeCalled(); + it('should call onFileDrop with drop event', () => { + window.dispatchEvent(dropEvent); + expect(onFileDrop).toBeCalled(); }); - it('should set state of dragLevel to 0 with dragEnter event', function test() { - const oldDragLevel = this.receiver.state.dragLevel; - window.dispatchEvent(this.dragEnterEvent); - const newDragLevel = this.receiver.state.dragLevel; + it('should set state of dragLevel to 0 with dragEnter event', () => { + const oldDragLevel = receiver.state().dragLevel; + window.dispatchEvent(dragEnterEvent); + const newDragLevel = receiver.state().dragLevel; expect(newDragLevel).toEqual(oldDragLevel + 1); - window.dispatchEvent(this.dropEvent); - const finalDragLevel = this.receiver.state.dragLevel; + window.dispatchEvent(dropEvent); + const finalDragLevel = receiver.state().dragLevel; expect(finalDragLevel).toEqual(0); }); - it('should not call any callback after Receiver did unmount', function test() { - ReactDOM.unmountComponentAtNode(this.container); - window.dispatchEvent(this.dragEnterEvent); - expect(this.onDragEnter).not.toBeCalled(); + it('should not call any callback after Receiver did unmount', () => { + receiver.unmount(); + window.dispatchEvent(dragEnterEvent); + expect(onDragEnter).not.toBeCalled(); - window.dispatchEvent(this.dragOverEvent); - expect(this.onDragOver).not.toBeCalled(); + window.dispatchEvent(dragOverEvent); + expect(onDragOver).not.toBeCalled(); - window.dispatchEvent(this.dragLeaveEvent); - expect(this.onDragLeave).not.toBeCalled(); + window.dispatchEvent(dragLeaveEvent); + expect(onDragLeave).not.toBeCalled(); - window.dispatchEvent(this.dropEvent); - expect(this.onFileDrop).not.toBeCalled(); + window.dispatchEvent(dropEvent); + expect(onFileDrop).not.toBeCalled(); }); }); describe('callbacks and callback arguments', () => { - beforeEach(function setting() { - const onDragEnter = (e) => { + let onDragEnter, + onDragOver, + onDragLeave, + onFileDrop; + + beforeEach(() => { + const mockOnDragEnter = (e) => { expect(e.type).toBe('dragenter'); }; - const onDragOver = (e) => { + const mockOnDragOver = (e) => { expect(e.type).toBe('dragover'); }; - const onDragLeave = (e) => { + const mockOnDragLeave = (e) => { expect(e.type).toBe('dragleave'); }; - const onFileDrop = (e, _files) => { + const mockOnFileDrop = (e, _files) => { expect(e.type).toBe('drop'); const file = _files[0]; expect(file.lastModified).toBe(testFile.lastModified); @@ -223,90 +265,90 @@ describe('Receiver', () => { expect(file.src).toBe(null); }; - this.onDragEnter = onDragEnter; - this.onDragOver = onDragOver; - this.onDragLeave = onDragLeave; - this.onFileDrop = onFileDrop; - - const Component = createComponent(onDragEnter, onDragOver, onDragLeave, onFileDrop); - const template = createTemplate({ isOpen: false, files: [] }, {}, Component); - - this.createTestParent = React.createFactory(template); - this.ParentComponent = this.createTestParent(); - this.container = document.createElement('div'); - this.instance = ReactDOM.render(this.ParentComponent, this.container); - this.receiver = this.instance.refs.receiver; + onDragEnter = mockOnDragEnter; + onDragOver = mockOnDragOver; + onDragLeave = mockOnDragLeave; + onFileDrop = mockOnFileDrop; + + const component = ( + + ); + + shallow(component); }); - it('should execute the onDragEnter callback with a DragEvent with type `dragenter` as argument', function test() { - window.dispatchEvent(this.dragEnterEvent); + it('should execute the onDragEnter callback with a DragEvent with type `dragenter` as argument', () => { + window.dispatchEvent(dragEnterEvent); }); - it('should execute the onDragOver callback with a DragEvent with type `dragover` as argument', function test() { - window.dispatchEvent(this.dragOverEvent); + it('should execute the onDragOver callback with a DragEvent with type `dragover` as argument', () => { + window.dispatchEvent(dragOverEvent); }); - it('should execute the onDragLeave callback with a DragEvent with type `dragleave` as argument', function test() { - window.dispatchEvent(this.dragLeaveEvent); + it('should execute the onDragLeave callback with a DragEvent with type `dragleave` as argument', () => { + window.dispatchEvent(dragLeaveEvent); }); - it('should execute the onFileDrop callback with a DragEvent with type `drop` as argument', function test() { - window.dispatchEvent(this.dropEvent); + it('should execute the onFileDrop callback with a DragEvent with type `drop` as argument', () => { + window.dispatchEvent(dropEvent); }); }); - describe('#render', () => { - beforeEach(function setting() { - const Component = createComponent(); - const template = createTemplate({ isOpen: false, files: [] }, {}, Component); - - this.createTestParent = React.createFactory(template); - this.ParentComponent = this.createTestParent(); - this.container = document.createElement('div'); - this.instance = ReactDOM.render(this.ParentComponent, this.container); - this.receiver = this.instance.refs.receiver; + describe('render()', () => { + let receiver, + childrenItems = Array(5).fill().map((value, index) => (
  • {index}
  • )); + + beforeEach(() => { + const mockOnDragEnter = jest.genMockFn(); + const mockOnDragOver = jest.genMockFn(); + const mockOnDragLeave = jest.genMockFn(); + const mockOnFileDrop = jest.genMockFn(); + + const component = ( + + {childrenItems} + + ); + + receiver = shallow(component); }); - it('should render nothing if isOpen is false', function test() { - const receiver = ReactDOM.findDOMNode(this.receiver); - expect(receiver).toBeNull(); - this.instance.setState({ isOpen: true }); + it('should render nothing if isOpen is false', () => { + expect(receiver.type()).toEqual(null); + expect(receiver.children().exists()).toBe(false); }); - it('should render a div wrapper with children if isOpen is true', function test() { - this.instance.setState({ isOpen: true }); - const receiver = ReactDOM.findDOMNode(this.receiver); - expect(receiver).toEqual(jasmine.any(HTMLDivElement)); - expect(receiver.firstElementChild).toEqual(jasmine.any(HTMLHeadingElement)); + it('should render a div wrapper with children if isOpen is true', () => { + receiver.setProps({ isOpen: true }); + expect(receiver.type()).toEqual('div'); + expect(receiver.children().length).toEqual(childrenItems.length); }); - it('should render a div wrapper with customClass in string', function test() { - const Component = createComponent(); - const template = createTemplate({ isOpen: true, files: [] }, { customClass: this.stringClass }, Component); - - this.createTestParent = React.createFactory(template); - this.ParentComponent = this.createTestParent(); - this.container = document.createElement('div'); - this.instance = ReactDOM.render(this.ParentComponent, this.container); - this.receiver = this.instance.refs.receiver; - - const receiver = ReactDOM.findDOMNode(this.receiver); - expect(receiver.className).toEqual(this.stringClass); + it('should render a div wrapper with customClass in string', () => { + receiver.setProps({ isOpen: true, customClass: stringClass }); + expect(receiver.hasClass(stringClass)).toBe(true); }); - it('should render a div wrapper with customClass in array', function test() { - const Component = createComponent(); - const template = createTemplate({ isOpen: true, files: [] }, { customClass: this.arrayClass }, Component); - - this.createTestParent = React.createFactory(template); - this.ParentComponent = this.createTestParent(); - this.container = document.createElement('div'); - this.instance = ReactDOM.render(this.ParentComponent, this.container); - this.receiver = this.instance.refs.receiver; - - const receiver = ReactDOM.findDOMNode(this.receiver); - expect(receiver.className).toEqual(this.arrayClass.join(' ')); + it('should render a div wrapper with customClass in array', () => { + receiver.setProps({ isOpen: true, customClass: arrayClass }); + arrayClass.forEach((classname) => { + expect(receiver.hasClass(classname)).toBe(true); + }); }); }); }); -/* eslint-enable no-undef */ +/* eslint-enable no-undef, max-len */ diff --git a/src/__tests__/UploadManager-test.js b/src/__tests__/UploadManager-test.js index f610cf7..4097477 100644 --- a/src/__tests__/UploadManager-test.js +++ b/src/__tests__/UploadManager-test.js @@ -5,7 +5,8 @@ jest.dontMock('classnames'); jest.dontMock('lodash'); import React from 'react'; -import ReactDOM from 'react-dom'; +import { shallow, configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; import { jsdom } from 'jsdom'; import nock from 'nock'; @@ -13,142 +14,110 @@ const FileUploader = require('../index'); const UploadManager = FileUploader.UploadManager; const uploadStatus = FileUploader.status; +configure({ adapter: new Adapter() }); + describe('UploadManager', () => { - beforeEach(function setting() { + let stringClass = 'receiver', + arrayClass = ['react', 'receiver'], + uploadPath = 'http://localhost:3000/api/upload', + children = (

    children

    ), + uploadManager, + onUploadStart, + onUploadProgress, + onUploadEnd, + formDataParser, + err, + errorResponse, + successResponse, + errorHandler; + + beforeEach(() => { global.document = jsdom(); global.window = document.parentWindow; - this.stringClass = 'receiver'; - this.arrayClass = ['react', 'receiver']; - this.style = { display: 'block' }; - this.uploadPath = 'http://localhost:3000/api/upload'; - this.onUploadStart = jest.fn(); - this.onUploadProgress = jest.fn(); - this.onUploadEnd = jest.fn(); - - this.children =

    children

    ; - this.container = document.createElement('div'); - this.instance = ReactDOM.render( + onUploadStart = jest.genMockFn(); + onUploadProgress = jest.genMockFn(); + onUploadEnd = jest.genMockFn(); + formDataParser = jest.genMockFn(); + + err = new Error('not found'); + errorResponse = { body: { success: false, errors: { message: 'not found' } } }; + successResponse = { body: { success: true } }; + errorHandler = UploadManager.defaultProps.uploadErrorHandler; + + uploadManager = shallow( - {this.children} - , - this.container - ); + {children} + + ) }); - afterEach(function setting() { - this.container = null; - this.instance = null; + afterEach(() => { + uploadManager = null; }); - describe('#render()', () => { - it('should render ul element by default', function test() { - const node = ReactDOM.findDOMNode(this.instance); - expect(node).toEqual(jasmine.any(HTMLUListElement)); - expect(node.firstElementChild).toEqual(jasmine.any(HTMLParagraphElement)); + describe('render()', () => { + it('should render ul element by default', () => { + expect(uploadManager.type()).toEqual('ul'); + expect(uploadManager.childAt(0).type()).toEqual('p'); }); - it('should render wrapper element according to component props', function test() { - this.instance = ReactDOM.render( - - {this.children} - , - this.container - ); - const node = ReactDOM.findDOMNode(this.instance); - expect(node).toEqual(jasmine.any(HTMLDivElement)); + it('should render wrapper element according to component props', () => { + uploadManager.setProps({ component: 'div' }); + expect(uploadManager.type()).toEqual('div'); }); - it('should render a wrapper with customClass in string', function test() { - this.instance = ReactDOM.render( - - {this.children} - , - this.container - ); - const node = ReactDOM.findDOMNode(this.instance); - expect(node.className).toEqual(this.stringClass); + it('should render a wrapper with customClass in string', () => { + expect(uploadManager.hasClass(stringClass)).toBe(true); }); - it('should render a wrapper with customClass in array', function test() { - this.instance = ReactDOM.render( - - {this.children} - , - this.container - ); - const node = ReactDOM.findDOMNode(this.instance); - expect(node.className).toEqual(this.arrayClass.join(' ')); - }); - }); + it('should render a wrapper with customClass in array', () => { + uploadManager.setProps({ customClass: arrayClass }); - describe('#uploadErrorHandler()', () => { - beforeEach(function setting() { - this.err = new Error('not found'); - this.errorResponse = { body: { success: false, errors: { message: 'not found' } } }; - this.successResponse = { body: { success: true } }; - this.errorHandler = this.instance.props.uploadErrorHandler; + arrayClass.forEach((classname) => { + expect(uploadManager.hasClass(classname)).toBe(true); + }); }); + }); - it('should return an object contains key of `error` and `result`', function test() { - const result = this.errorHandler(null, this.successResponse); + describe('uploadErrorHandler()', () => { + it('should return an object contains key of `error` and `result`', () => { + const result = errorHandler(null, successResponse); expect(result.error).toBeNull(); - expect(result.result).toEqual(this.successResponse.body); + expect(result.result).toEqual(successResponse.body); }); - it('should return an object with key of `error` with value equals to the first argument if it is not empty', function test() { - const result = this.errorHandler(this.err, this.successResponse); - expect(result.error).toEqual(this.err.message); - expect(result.result).toEqual(this.successResponse.body); + it('should return an object with key of `error` with value equals to the first argument if it is not empty', () => { + const result = errorHandler(err, successResponse); + expect(result.error).toEqual(err.message); + expect(result.result).toEqual(successResponse.body); }); - it('should return an object with key of `error` with value equals to the value of `body.error` of the second argument if it is not empty', function test() { - const result = this.errorHandler(null, this.errorResponse); - expect(result.error).toEqual(this.errorResponse.body.errors); - delete this.errorResponse.body.errors; - expect(result.result).toEqual(this.errorResponse.body); + it('should return an object with key of `error` with value equals to the value of `body.error` of the second argument if it is not empty', () => { + const result = errorHandler(null, errorResponse); + expect(result.error).toEqual(errorResponse.body.errors); + delete errorResponse.body.errors; + expect(result.result).toEqual(errorResponse.body); }); }); - describe('#upload()', () => { - beforeEach(function setting() { + describe('upload()', () => { + let file; + + beforeEach(() => { + file = {}; + nock('http://localhost:3000') .filteringRequestBody(() => '*') .post('/api/upload', '*') - .reply(200, this.successResponse); - - this.instance = ReactDOM.render( - - {this.children} - , - this.container - ); - this.errorResponse = { success: false, errors: { message: 'not found' } }; - this.successResponse = { success: true }; + .reply(200, successResponse); }); afterEach(() => { @@ -156,9 +125,18 @@ describe('UploadManager', () => { nock.enableNetConnect(); }); - it('should call onUploadStart prop functions if it is given', function test() { - this.instance.upload(this.instance.props.uploadUrl, {}); - expect(this.onUploadStart).toBeCalledWith({ status: uploadStatus.UPLOADING }); + it('should call `props.onUploadStart` function if it is given', () => { + const instance = uploadManager.instance(); + instance.upload(instance.props.uploadUrl, file); + expect(onUploadStart).toBeCalledWith(Object.assign({}, file, { status: uploadStatus.UPLOADING })); + expect(file).toEqual({ status: uploadStatus.UPLOADING }); + }); + + it('should call `props.formDataParser` function if it is given', () => { + const instance = uploadManager.instance(); + instance.upload(instance.props.uploadUrl, {}); + expect(formDataParser).toBeCalledWith(new FormData(), { status: uploadStatus.UPLOADING }); }); }); }); +/* eslint-enable no-undef, max-len */ diff --git a/src/__tests__/fake-test.js b/src/__tests__/fake-test.js deleted file mode 100644 index 3c8eb54..0000000 --- a/src/__tests__/fake-test.js +++ /dev/null @@ -1,6 +0,0 @@ -/* eslint-disable no-undef */ -describe('Fake Test', () => { - it('should pass this fake test', () => { - expect(1).toEqual(1); - }); -});