diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..683e503 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - 6.6 diff --git a/package.json b/package.json index bf86ab3..5355369 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,9 @@ "clean": "rimraf dist/*", "copy": "copyfiles -f ./src/index.html ./dist && copyfiles -u 1 \"./src/static/**\" ./dist", "dist": "npm run clean && npm run copy && webpack --progress --bail --env dist -p", - "lint": "eslint ./src", + "lint": "npm run lint:src && npm run lint:test", + "lint:src": "eslint ./src", + "lint:test": "eslint ./test", "posttest": "npm run lint", "release:major": "npm version prerelease && git push --follow-tags && npm publish --tag beta", "release:minor": "npm version prerelease && git push --follow-tags && npm publish --tag beta", @@ -61,7 +63,7 @@ "phantomjs-prebuilt": "^2.1.7", "react-addons-test-utils": "^15.0.1", "rimraf": "^2.5.2", - "sinon": "^1.17.3", + "sinon": "^2.3.2", "style-loader": "^0.13.1", "stylus": "^0.54.5", "stylus-loader": "^2.1.0", diff --git a/src/containers/Palette.js b/src/containers/Palette.js index 54c7d2a..ff3eb7a 100644 --- a/src/containers/Palette.js +++ b/src/containers/Palette.js @@ -5,7 +5,7 @@ import { bindActionCreators } from 'redux'; import { changeColor, changeWidth } from '../actions/'; import Main from '../components/Palette'; -class Palette extends Component { +export class Palette extends Component { render() { return
; } @@ -19,11 +19,11 @@ Palette.propTypes = { palette: PropTypes.object.isRequired }; -function mapStateToProps(state) { +export function mapStateToProps(state) { return { palette: state.palette }; } -function mapDispatchToProps(dispatch) { +export function mapDispatchToProps(dispatch) { const actions = { changeColor, changeWidth }; return { actions: bindActionCreators(actions, dispatch) }; } diff --git a/test/actions/changeColorTest.js b/test/actions/changeColorTest.js new file mode 100644 index 0000000..56afc7b --- /dev/null +++ b/test/actions/changeColorTest.js @@ -0,0 +1,12 @@ +import action from 'actions/changeColor'; +import { CHANGE_COLOR } from 'actions/const'; + +describe('changeColor', () => { + it('should create an action to change the color', () => { + const color = '#010101'; + expect(action(color)).to.deep.equal({ + type: CHANGE_COLOR, + color + }); + }); +}); \ No newline at end of file diff --git a/test/actions/changeWidthTest.js b/test/actions/changeWidthTest.js new file mode 100644 index 0000000..366f4bb --- /dev/null +++ b/test/actions/changeWidthTest.js @@ -0,0 +1,12 @@ +import action from 'actions/changeWidth'; +import { CHANGE_WIDTH } from 'actions/const'; + +describe('changeWidth', () => { + it('should create an action to change the width', () => { + const lineWidth = 40; + expect(action(lineWidth)).to.deep.equal({ + type: CHANGE_WIDTH, + width: lineWidth + }); + }); +}); \ No newline at end of file diff --git a/test/all/changeColorTest.js b/test/all/changeColorTest.js new file mode 100644 index 0000000..b7079c7 --- /dev/null +++ b/test/all/changeColorTest.js @@ -0,0 +1,65 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import sinon from 'sinon'; +import { shallow, mount } from 'enzyme'; +import Pen from 'components/Pen'; +import App from 'containers/App'; +import configureStore from 'stores'; +import action from 'actions/changeColor'; +import { CHANGE_COLOR } from 'actions/const'; +import reducer from 'reducers/palette'; + +describe('changeColor', () => { + describe('all', () => { + let app; + const color = '#795548'; + beforeEach(() => { + app = mount( + + + + ); + }); + it('the canvas should change color when a pen is clicked', () => { + const canvas = app.find('Canvas'); + const pen = app.find('Pen').filterWhere(n => n.prop('color') === color); + expect(canvas.node.ctx.strokeStyle).to.not.equal(color); + pen.simulate('mousedown'); + canvas.node.penDown({ clientX: 0, clientY: 0 }); + expect(canvas.node.ctx.strokeStyle).to.equal(color); + }); + it('the pen should become active when the pen is clicked', () => { + const pen = app.find('Pen').filterWhere(n => n.prop('color') === color); + expect(pen.node.state.styleName).to.not.have.include('pen-active'); + pen.simulate('mousedown'); + expect(pen.node.state.styleName).to.have.include('pen-active'); + }); + }); + it('the action should be created when a pen is clicked', () => { + let color = '#010101'; + let currentColor = '#020202'; + let onChangeColor = sinon.spy(); + let pen = shallow( + + ); + pen.simulate('mousedown'); + expect(onChangeColor.withArgs(color).calledOnce).to.equal(true); + }); + it('should create an action to change the color', () => { + const color = '#010101'; + expect(action(color)).to.deep.equal({ + type: CHANGE_COLOR, + color + }); + }); + it('should handle CHANGE_COLOR', () => { + const color = '#010101'; + expect(reducer(void 0, { + type: CHANGE_COLOR, + color + })).to.have.property('color', color); + }); +}); \ No newline at end of file diff --git a/test/components/AppTest.js b/test/components/AppTest.js index 649c300..a444356 100644 --- a/test/components/AppTest.js +++ b/test/components/AppTest.js @@ -1,17 +1,32 @@ import React from 'react'; import { shallow } from 'enzyme'; import App from 'components/App'; +import Palette from 'containers/Palette'; +import Canvas from 'components/Canvas'; describe('', function () { + const colorTest = 'red'; + const widthTest = 20; + const paletteTest = { + color: colorTest, + width: widthTest + }; beforeEach(function () { - this.component = shallow(); + this.component = shallow(); }); describe('when rendering the component', function () { - it('should have a className of "index"', function () { expect(this.component.hasClass('index')).to.equal(true); }); + it('should have a palette', function () { + expect(this.component.contains()).to.equal(true); + }); + it('should have a canvas', function () { + expect(this.component.contains( + + )).to.equal(true); + }); }); }); diff --git a/test/components/CanvasTest.js b/test/components/CanvasTest.js index f977d00..c88fbe4 100644 --- a/test/components/CanvasTest.js +++ b/test/components/CanvasTest.js @@ -1,18 +1,190 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; +import sinon from 'sinon'; import Canvas from 'components/Canvas.js'; describe('', function () { + let testColor; + let testWidth; let component; beforeEach(function () { - component = shallow(); + testColor = 'red'; + testWidth = 20; + component = mount(); }); describe('when rendering the component', function () { + it('should have a canvas', function () { + expect(component.find('canvas')).to.have.length(1); + }); + }); + + describe('#getPosition', function () { + const clientX = 10; + const clientY = 20; + it('should return when clientX and clientY', function () { + expect(component.instance().getPosition({ + clientX, clientY + })).to.deep.equal({ + x: clientX, + y: clientY + }); + }); + it('should return when touches', function () { + expect(component.instance().getPosition({ + touches: [{ + clientX, clientY + }] + })).to.deep.equal({ + x: clientX, + y: clientY + }); + }); + }); + + describe('#pushPosition', function () { + it('should push position to positions', function () { + const pos = { x: 10, y: 20 }; + component.instance().pushPosition(pos.x, pos.y); + expect(component.instance().positions).to.have.length(1); + expect(component.instance().positions[0]).to.deep.equal(pos); + }); + }); + + describe('#penDown', function () { + it('should change strokeStyle', function () { + const color = '#008000'; + component.setProps({ color }); + component.instance().penDown({ + clientX: 0, + clientY: 0 + }); + expect(component.instance().ctx.strokeStyle).to.equal(color); + }); + it('should change lineWidth', function () { + const width = 200; + component.setProps({ width }); + component.instance().penDown({ + clientX: 0, + clientY: 0 + }); + expect(component.instance().ctx.lineWidth).to.equal(width); + }); + it('should set isDownPen to true', function () { + component.instance().penDown({ + clientX: 0, + clientY: 0 + }); + expect(component.instance().isDownPen).to.equal(true); + }); + it('should update ctx settings', function () { + component.instance().penDown({ + clientX: 0, + clientY: 0 + }); + expect(component.instance().ctx.lineCap).to.equal('round'); + expect(component.instance().ctx.lineJoin).to.equal('round'); + }); + }); + + describe('#penMove', function () { + it('should draw a line', function () { + const lineToSpy = sinon.spy(component.instance().ctx, 'lineTo'); + const x = 20; + const y = 30; + component.instance().isDownPen = true; + component.instance().penMove({ + clientX: x, + clientY: y + }); + expect(lineToSpy.withArgs(x, y).calledOnce).to.equal(true); + lineToSpy.restore(); + }); + }); + + describe('#penUp', function () { + it('should set isDownPen to false', function () { + component.instance().isDownPen = true; + component.instance().penUp(); + expect(component.instance().isDownPen).to.equal(false); + }); + it('should clear positions', function () { + component.instance().positions = [{ x: 100, y: 100 }]; + component.instance().penUp(); + expect(component.instance().positions).to.have.length(0); + }); + }); + + // check overall flow + + describe('mousedown', function () { + it('should change strokeStyle', function () { + const color = '#010101'; + component.setProps({ color }); + component.simulate('mousedown', { + clientX: 100, + clientY: 100 + }); + expect(component.instance().ctx.strokeStyle).to.equal(color); + }); + }); + + describe('touchstart', function () { + it('should change strokeStyle', function () { + const color = '#010101'; + component.setProps({ color }); + component.simulate('touchstart', { + touches: [{ + clientX: 100, + clientY: 100 + }] + }); + expect(component.instance().ctx.strokeStyle).to.equal(color); + }); + }); + + describe('mousemove', function () { + it('should call lineTo', function () { + const x = 100; + const y = 200; + const lineToSpy = sinon.spy(component.instance().ctx, 'lineTo'); + component.instance().isDownPen = true; + component.simulate('mousemove', { + clientX: x, clientY: y + }); + expect(lineToSpy.withArgs(x, y).calledOnce).to.equal(true); + }); + }); + + describe('touchmove', function () { + it('should call lineTo', function () { + const x = 100; + const y = 200; + const lineToSpy = sinon.spy(component.instance().ctx, 'lineTo'); + component.instance().isDownPen = true; + component.simulate('touchmove', { + touches: [{ + clientX: x, clientY: y + }] + }); + expect(lineToSpy.withArgs(x, y).calledOnce).to.equal(true); + }); + }); + + describe('mouseup', function () { + it('should set isDownPen to false', function () { + component.instance().isDownPen = true; + component.simulate('mouseup'); + expect(component.instance().isDownPen).to.equal(false); + }); + }); - it('should have a className of "canvas-component"', function () { - expect(component.hasClass('canvas-component')).to.equal(true); + describe('touchend', function () { + it('should set isDownPen to false', function () { + component.instance().isDownPen = true; + component.simulate('touchend'); + expect(component.instance().isDownPen).to.equal(false); }); }); }); diff --git a/test/components/LineWidthTest.js b/test/components/LineWidthTest.js index b21c890..a9c1917 100644 --- a/test/components/LineWidthTest.js +++ b/test/components/LineWidthTest.js @@ -1,18 +1,39 @@ import React from 'react'; import { shallow } from 'enzyme'; +import sinon from 'sinon'; import LineWidth from 'components/LineWidth.js'; describe('', function () { let component; + let testWidth; + let testOnChangeWidth; beforeEach(function () { - component = shallow(); + testWidth = 10; + testOnChangeWidth = () => {}; + component = shallow(); }); describe('when rendering the component', function () { + it('should have an input', function () { + expect(component.find('input')).to.have.length(1); + }); + it('should set default value by width', function () { + const width = 30; + component.setProps({ width }); + expect(component.find('input').prop('defaultValue')).to.equal(width); + }); + }); - it('should have a className of "linewidth-component"', function () { - expect(component.hasClass('linewidth-component')).to.equal(true); + describe('mouseup', function () { + it('should call onChangeWidth with the value', function () { + const onChangeWidth = sinon.spy(); + const value = '40'; + component.setProps({ onChangeWidth }); + component.find('input').simulate('mouseup', { + target: { value } + }); + expect(onChangeWidth.withArgs(parseInt(value)).calledOnce).to.equal(true); }); }); }); diff --git a/test/components/PaletteTest.js b/test/components/PaletteTest.js index 42949d3..3210ebe 100644 --- a/test/components/PaletteTest.js +++ b/test/components/PaletteTest.js @@ -1,18 +1,29 @@ import React from 'react'; import { shallow } from 'enzyme'; import Palette from 'components/Palette.js'; +import colors from 'constants/colors'; describe('', function () { let component; + const testActions = { + changeWidth() {}, + changeColor() {} + }; + const testPalette = { + color: 'red', + width: 30 + }; beforeEach(function () { - component = shallow(); + component = shallow(); }); describe('when rendering the component', function () { - - it('should have a className of "palette-component"', function () { - expect(component.hasClass('palette-component')).to.equal(true); + it('should make colors of pens', function () { + expect(component.find('Pen')).to.have.length(colors.length); + }); + it('should have a ', function () { + expect(component.find('LineWidth')).to.have.length(1); }); }); }); diff --git a/test/components/PenTest.js b/test/components/PenTest.js index ba00240..b8300cf 100644 --- a/test/components/PenTest.js +++ b/test/components/PenTest.js @@ -1,18 +1,44 @@ import React from 'react'; import { shallow } from 'enzyme'; +import sinon from 'sinon'; import Pen from 'components/Pen.js'; describe('', function () { let component; + let testColor; + let testCurrentColor; + let testOnChangeColor; beforeEach(function () { - component = shallow(); + testColor = '#010101'; + testCurrentColor = '#404040'; + testOnChangeColor = () => {}; + component = shallow( + + ); }); describe('when rendering the component', function () { + it('should set backgroundColor', function () { + expect(component.prop('style')).to.have.property('backgroundColor', testColor); + }); + it('should set pen-active when active', function () { + const currentColor = testColor; + expect(component.prop('className')).to.not.contain('pen-active'); + component.setProps({ currentColor }); + expect(component.prop('className')).to.contain('pen-active'); + }); + }); - it('should have a className of "pen-component"', function () { - expect(component.hasClass('pen-component')).to.equal(true); + describe('mousedown', function () { + it('should call callback', function () { + const onChangeColor = sinon.spy(); + component.setProps({ onChangeColor }); + component.simulate('mousedown'); + expect(onChangeColor.withArgs(testColor).calledOnce).to.equal(true); }); }); }); diff --git a/test/containers/PaletteTest.js b/test/containers/PaletteTest.js new file mode 100644 index 0000000..5aba9e3 --- /dev/null +++ b/test/containers/PaletteTest.js @@ -0,0 +1,35 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { + Palette as PaletteContainer, + mapStateToProps, + mapDispatchToProps +} from 'containers/Palette'; +import Palette from 'components/Palette'; + +describe('containers/Palette', () => { + describe('component', () => { + const actions = { + changeColor() {}, + changeWidth() {} + }; + const palette = {}; + const component = shallow(); + expect(component.contains()).to.equal(true); + }); + describe('#mapStateToProps', () => { + it('should return only palette', () => { + const palette = { color: 'red' }; + expect(mapStateToProps({ + palette, + other: {} + })).to.deep.equal({ palette }); + }); + }); + describe('#mapDispatchToProps', () => { + it('should return actions', () => { + expect(mapDispatchToProps({})).to.have.property('actions'); + expect(Object.keys(mapDispatchToProps({}).actions)).to.have.lengthOf(2); + }); + }); +}); \ No newline at end of file diff --git a/test/reducers/paletteTest.js b/test/reducers/paletteTest.js index 0c8e817..f47580b 100644 --- a/test/reducers/paletteTest.js +++ b/test/reducers/paletteTest.js @@ -1,12 +1,26 @@ -var reducer = require('../../src/reducers/palette'); +import reducer from 'reducers/palette'; +import { CHANGE_COLOR, CHANGE_WIDTH } from 'actions/const'; +import config from 'config'; describe('palette', () => { - - it('should not change the passed state', (done) => { - - const state = Object.freeze({}); - reducer(state, {type: 'INVALID'}); - - done(); + it('should return the initial state', () => { + expect(reducer(void 0, { })).to.deep.equal({ + color: config.defaultColor, + width: config.defaultWidth + }); + }); + it('should handle CHANGE_COLOR', () => { + const color = '#010101'; + expect(reducer(void 0, { + type: CHANGE_COLOR, + color + })).to.have.property('color', color); + }); + it('should handle CHANGE_WIDTH', () => { + const width = 40; + expect(reducer(void 0, { + type: CHANGE_WIDTH, + width + })).to.have.property('width', width); }); });