From a622d0d3e9672d6c2a38e999639fb14697414304 Mon Sep 17 00:00:00 2001
From:
Date: Thu, 24 Aug 2017 17:49:22 -0400
Subject: [PATCH] add tests
---
package.json | 2 +-
src/components/paint-editor.jsx | 2 +-
src/containers/line-mode.jsx | 284 ++++++++++--------
src/reducers/line-mode.js | 28 +-
.../unit/components/eraser-mode.test copy.jsx | 15 -
5 files changed, 185 insertions(+), 146 deletions(-)
delete mode 100644 test/unit/components/eraser-mode.test copy.jsx
diff --git a/package.json b/package.json
index 1d7fd76a..9725942a 100644
--- a/package.json
+++ b/package.json
@@ -44,7 +44,7 @@
"eslint-config-import": "^0.13.0",
"eslint-config-scratch": "^3.0.0",
"eslint-plugin-import": "^2.7.0",
- "eslint-plugin-react": "^7.0.1",
+ "eslint-plugin-react": "^7.3.0",
"gh-pages": "github:rschamp/gh-pages#publish-branch-to-subfolder",
"html-webpack-plugin": "2.28.0",
"jest": "^20.0.4",
diff --git a/src/components/paint-editor.jsx b/src/components/paint-editor.jsx
index 8f7ff57f..090b9bed 100644
--- a/src/components/paint-editor.jsx
+++ b/src/components/paint-editor.jsx
@@ -24,7 +24,7 @@ class PaintEditorComponent extends React.Component {
-
+
);
}
diff --git a/src/containers/line-mode.jsx b/src/containers/line-mode.jsx
index 0b892bfd..dd9216e8 100644
--- a/src/containers/line-mode.jsx
+++ b/src/containers/line-mode.jsx
@@ -3,6 +3,7 @@ import React from 'react';
import {connect} from 'react-redux';
import bindAll from 'lodash.bindall';
import Modes from '../modes/modes';
+import {changeLineWidth} from '../reducers/line-mode';
import LineModeComponent from '../components/line-mode.jsx';
import {changeMode} from '../reducers/modes';
import paper from 'paper';
@@ -16,8 +17,13 @@ class LineMode extends React.Component {
bindAll(this, [
'activateTool',
'deactivateTool',
+ 'onMouseDown',
+ 'onMouseMove',
+ 'onMouseDrag',
+ 'onMouseUp',
'toleranceSquared',
- 'findLineEnd'
+ 'findLineEnd',
+ 'onScroll'
]);
}
componentDidMount () {
@@ -30,8 +36,6 @@ class LineMode extends React.Component {
this.activateTool();
} else if (!nextProps.isLineModeActive && this.props.isLineModeActive) {
this.deactivateTool();
- } else if (nextProps.isLineModeActive && this.props.isLineModeActive) {
- this.blob.setOptions(nextProps.lineModeState);
}
}
shouldComponentUpdate () {
@@ -40,7 +44,7 @@ class LineMode extends React.Component {
activateTool () {
// TODO add back selection
// pg.selection.clearSelection();
-
+ this.props.canvas.addEventListener('mousewheel', this.onScroll);
this.tool = new paper.Tool();
this.path = null;
@@ -56,58 +60,103 @@ class LineMode extends React.Component {
const lineMode = this;
this.tool.onMouseDown = function (event) {
if (event.event.button > 0) return; // only first mouse button
-
- if (this.path) {
- this.path.setSelected(false);
- this.path = null;
- }
-
- // If you click near a point, continue that line instead of making a new line
- this.hitResult = lineMode.findLineEnd(event.point);
- if (this.hitResult) {
- this.path = this.hitResult.path;
- if (this.hitResult.isFirst) {
- this.path.reverse();
- }
- this.path.lastSegment.setSelected(true);
- this.path.add(this.hitResult.segment); // Add second point, which is what will move when dragged
- this.path.lastSegment.handleOut = null; // Make sure line isn't curvy
- this.path.lastSegment.handleIn = null;
- }
-
- // If not near other path, start a new path
- if (!this.path) {
- this.path = new paper.Path();
-
- // TODO add back style
- // this.path = pg.stylebar.applyActiveToolbarStyle(path);
- this.path.setStrokeColor('black');
-
- this.path.setSelected(true);
- this.path.add(event.point);
- this.path.add(event.point); // Add second point, which is what will move when dragged
- paper.view.draw();
- }
+ lineMode.onMouseDown(event);
+ };
+ this.tool.onMouseMove = function (event) {
+ lineMode.onMouseMove(event);
+ };
+ this.tool.onMouseDrag = function (event) {
+ if (event.event.button > 0) return; // only first mouse button
+ lineMode.onMouseDrag(event);
+ };
+ this.tool.onMouseUp = function (event) {
+ if (event.event.button > 0) return; // only first mouse button
+ lineMode.onMouseUp(event);
};
- this.tool.onMouseMove = function (event) {
- // If near another path's endpoint, or this path's beginpoint, clip to it to suggest
- // joining/closing the paths.
- if (this.hitResult) {
- this.hitResult.path.setSelected(false);
- this.hitResult = null;
- }
+ this.tool.activate();
+ }
+ onMouseDown (event) {
+ // Deselect old path
+ if (this.path) {
+ this.path.setSelected(false);
+ this.path = null;
+ }
- if (this.path && !this.path.closed && this.path.firstSegment.point.getDistance(event.point, true) < lineMode.toleranceSquared()) {
- this.hitResult = {
- path: this.path,
- segment: this.path.firstSegment,
- isFirst: true
- };
+ // If you click near a point, continue that line instead of making a new line
+ this.hitResult = this.findLineEnd(event.point);
+ if (this.hitResult) {
+ this.path = this.hitResult.path;
+ if (this.hitResult.isFirst) {
+ this.path.reverse();
+ }
+ this.path.lastSegment.setSelected(true);
+ this.path.add(this.hitResult.segment); // Add second point, which is what will move when dragged
+ this.path.lastSegment.handleOut = null; // Make sure line isn't curvy
+ this.path.lastSegment.handleIn = null;
+ }
+
+ // If not near other path, start a new path
+ if (!this.path) {
+ this.path = new paper.Path();
+
+ // TODO add back style
+ // this.path = pg.stylebar.applyActiveToolbarStyle(path);
+ this.path.setStrokeColor('black');
+ this.path.setStrokeWidth(this.props.lineModeState.lineWidth);
+
+ this.path.setSelected(true);
+ this.path.add(event.point);
+ this.path.add(event.point); // Add second point, which is what will move when dragged
+ paper.view.draw();
+ }
+ }
+ onMouseMove (event) {
+ // If near another path's endpoint, or this path's beginpoint, clip to it to suggest
+ // joining/closing the paths.
+ if (this.hitResult) {
+ this.hitResult.path.setSelected(false);
+ this.hitResult = null;
+ }
+
+ if (this.path &&
+ !this.path.closed &&
+ this.path.firstSegment.point.getDistance(event.point, true) < this.toleranceSquared()) {
+ this.hitResult = {
+ path: this.path,
+ segment: this.path.firstSegment,
+ isFirst: true
+ };
+ } else {
+ this.hitResult = this.findLineEnd(event.point);
+ }
+
+ if (this.hitResult) {
+ const hitPath = this.hitResult.path;
+ hitPath.setSelected(true);
+ if (this.hitResult.isFirst) {
+ hitPath.firstSegment.setSelected(true);
} else {
- this.hitResult = lineMode.findLineEnd(event.point);
+ hitPath.lastSegment.setSelected(true);
}
+ }
+ }
+ onMouseDrag (event) {
+ // If near another path's endpoint, or this path's beginpoint, clip to it to suggest
+ // joining/closing the paths.
+ if (this.hitResult && this.hitResult.path !== this.path) this.hitResult.path.setSelected(false);
+ this.hitResult = null;
+ if (this.path &&
+ this.path.segments.length > 3 &&
+ this.path.firstSegment.point.getDistance(event.point, true) < this.toleranceSquared()) {
+ this.hitResult = {
+ path: this.path,
+ segment: this.path.firstSegment,
+ isFirst: true
+ };
+ } else {
+ this.hitResult = this.findLineEnd(event.point, this.path);
if (this.hitResult) {
const hitPath = this.hitResult.path;
hitPath.setSelected(true);
@@ -117,84 +166,54 @@ class LineMode extends React.Component {
hitPath.lastSegment.setSelected(true);
}
}
- };
-
- this.tool.onMouseDrag = function (event) {
- if (event.event.button > 0) return; // only first mouse button
- // If near another path's endpoint, or this path's beginpoint, clip to it to suggest
- // joining/closing the paths.
- if (this.hitResult && this.hitResult.path !== this.path) this.hitResult.path.setSelected(false);
- this.hitResult = null;
+ }
- if (this.path && this.path.segments.length > 3 && this.path.firstSegment.point.getDistance(event.point, true) < lineMode.toleranceSquared()) {
- this.hitResult = {
- path: this.path,
- segment: this.path.firstSegment,
- isFirst: true
- };
- } else {
- this.hitResult = lineMode.findLineEnd(event.point, this.path);
- if (this.hitResult) {
- const hitPath = this.hitResult.path;
- hitPath.setSelected(true);
- if (this.hitResult.isFirst) {
- hitPath.firstSegment.setSelected(true);
- } else {
- hitPath.lastSegment.setSelected(true);
- }
- }
- }
-
- // snapping
- if (this.path) {
- if (this.hitResult) {
- this.path.lastSegment.point = this.hitResult.segment.point;
- } else {
- this.path.lastSegment.point = event.point;
- }
- }
- };
-
-
- this.tool.onMouseUp = function (event) {
- if (event.event.button > 0) return; // only first mouse button
-
- // If I single clicked, don't do anything
- if (this.path.segments.length < 2 || (this.path.segments.length === 2 && this.path.firstSegment.point.getDistance(event.point, true) < lineMode.toleranceSquared())) {
- this.path.remove();
- this.path = null;
- // TODO don't erase the line if both ends are snapped to different points
- return;
- } else if (this.path.lastSegment.point.getDistance(this.path.segments[this.path.segments.length - 2].point, true) < lineMode.toleranceSquared()) {
- this.path.removeSegment(this.path.segments.length - 1);
- return;
- }
-
- // If I intersect other line end points, join or close
+ // snapping
+ if (this.path) {
if (this.hitResult) {
- this.path.removeSegment(this.path.segments.length - 1);
- if (this.path.firstSegment === this.hitResult.segment) {
- // close path
- this.path.closed = true;
- this.path.setSelected(false);
- } else {
- // joining two paths
- if (!this.hitResult.isFirst) {
- this.hitResult.path.reverse();
- }
- this.path.join(this.hitResult.path);
- }
- this.hitResult = null;
+ this.path.lastSegment.point = this.hitResult.segment.point;
+ } else {
+ this.path.lastSegment.point = event.point;
}
+ }
+ }
+ onMouseUp (event) {
+ // If I single clicked, don't do anything
+ if (this.path.segments.length < 2 ||
+ (this.path.segments.length === 2 &&
+ this.path.firstSegment.point.getDistance(event.point, true) < this.toleranceSquared())) {
+ this.path.remove();
+ this.path = null;
+ // TODO don't erase the line if both ends are snapped to different points
+ return;
+ } else if (
+ this.path.lastSegment.point.getDistance(this.path.segments[this.path.segments.length - 2].point, true) <
+ this.toleranceSquared()) {
+ this.path.removeSegment(this.path.segments.length - 1);
+ return;
+ }
+
+ // If I intersect other line end points, join or close
+ if (this.hitResult) {
+ this.path.removeSegment(this.path.segments.length - 1);
+ if (this.path.firstSegment === this.hitResult.segment) {
+ // close path
+ this.path.closed = true;
+ this.path.setSelected(false);
+ } else {
+ // joining two paths
+ if (!this.hitResult.isFirst) {
+ this.hitResult.path.reverse();
+ }
+ this.path.join(this.hitResult.path);
+ }
+ this.hitResult = null;
+ }
- // TODO add back undo
- // if (this.path) {
- // pg.undo.snapshot('line');
- // }
-
- };
-
- this.tool.activate();
+ // TODO add back undo
+ // if (this.path) {
+ // pg.undo.snapshot('line');
+ // }
}
toleranceSquared () {
return Math.pow(LineMode.SNAP_TOLERANCE / paper.view.zoom, 2);
@@ -211,7 +230,8 @@ class LineMode extends React.Component {
if (excludePath && lines[i] === excludePath) {
continue;
}
- if (lines[i].firstSegment && lines[i].firstSegment.point.getDistance(point, true) < this.toleranceSquared()) {
+ if (lines[i].firstSegment &&
+ lines[i].firstSegment.point.getDistance(point, true) < this.toleranceSquared()) {
return {
path: lines[i],
segment: lines[i].firstSegment,
@@ -229,11 +249,20 @@ class LineMode extends React.Component {
return null;
}
deactivateTool () {
+ this.props.canvas.removeEventListener('mousewheel', this.onScroll);
if (this.path) {
this.path.setSelected(false);
this.path = null;
}
}
+ onScroll (event) {
+ if (event.deltaY < 0) {
+ this.props.changeLineWidth(this.props.lineModeState.lineWidth + 1);
+ } else if (event.deltaY > 0 && this.props.lineModeState.lineWidth > 1) {
+ this.props.changeLineWidth(this.props.lineModeState.lineWidth - 1);
+ }
+ return true;
+ }
render () {
return (
@@ -242,6 +271,8 @@ class LineMode extends React.Component {
}
LineMode.propTypes = {
+ canvas: PropTypes.instanceOf(Element).isRequired,
+ changeLineWidth: PropTypes.func.isRequired,
handleMouseDown: PropTypes.func.isRequired,
isLineModeActive: PropTypes.bool.isRequired,
lineModeState: PropTypes.shape({
@@ -254,6 +285,9 @@ const mapStateToProps = state => ({
isLineModeActive: state.mode === Modes.LINE
});
const mapDispatchToProps = dispatch => ({
+ changeLineWidth: lineWidth => {
+ dispatch(changeLineWidth(lineWidth));
+ },
handleMouseDown: () => {
dispatch(changeMode(Modes.LINE));
}
diff --git a/src/reducers/line-mode.js b/src/reducers/line-mode.js
index e2c780fe..8845ace4 100644
--- a/src/reducers/line-mode.js
+++ b/src/reducers/line-mode.js
@@ -1,11 +1,31 @@
+import log from '../log/log';
+
+const CHANGE_LINE_WIDTH = 'scratch-paint/line-mode/CHANGE_LINE_WIDTH';
const initialState = {lineWidth: 2};
-const reducer = function (state) {
+const reducer = function (state, action) {
if (typeof state === 'undefined') state = initialState;
- return state;
+ switch (action.type) {
+ case CHANGE_LINE_WIDTH:
+ if (isNaN(action.lineWidth)) {
+ log.warn(`Invalid line width: ${action.lineWidth}`);
+ return state;
+ }
+ return {lineWidth: Math.max(1, action.lineWidth)};
+ default:
+ return state;
+ }
};
// Action creators ==================================
+const changeLineWidth = function (lineWidth) {
+ return {
+ type: CHANGE_LINE_WIDTH,
+ lineWidth: lineWidth
+ };
+};
-
-export default reducer;
+export {
+ reducer as default,
+ changeLineWidth
+};
diff --git a/test/unit/components/eraser-mode.test copy.jsx b/test/unit/components/eraser-mode.test copy.jsx
deleted file mode 100644
index 4772581c..00000000
--- a/test/unit/components/eraser-mode.test copy.jsx
+++ /dev/null
@@ -1,15 +0,0 @@
-/* eslint-env jest */
-import React from 'react'; // eslint-disable-line no-unused-vars
-import {shallow} from 'enzyme';
-import EraserModeComponent from '../../../src/components/eraser-mode.jsx'; // eslint-disable-line no-unused-vars
-
-describe('EraserModeComponent', () => {
- test('triggers callback when clicked', () => {
- const onClick = jest.fn();
- const componentShallowWrapper = shallow(
-
- );
- componentShallowWrapper.simulate('click');
- expect(onClick).toHaveBeenCalled();
- });
-});