Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pull request #103

Merged
merged 10 commits into from
May 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ module.exports = {
"expect": false,
"beforeAll": false,
"beforeEach": false,
"afterAll": false
"afterAll": false,
"afterEach": false
},
"extends": "airbnb",
"plugins": [
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ as well as the x distance, + or -, from where the swipe started to where it ende

**`innerRef`** will allow access to the Swipeable's inner dom node element react ref. See [#81](https://github.com/dogfessional/react-swipeable/issues/81) for more details. Example usage `<Swipeable innerRef={(el) => this.swipeableEl = el} >`. Then you'll have access to the dom element that Swipeable uses internally.

**`rotationAngle`** will allow to set a rotation angle, e.g. for a four-player game on a tablet, where each player has a 90° turned view. The default value is `0`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we "lock" this to a range of 0 - 360? via a new proptype?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We thought about the same, but in the end it does not matter for the calculation - so why lock the range.


**None of the props are required.**
### PropType Definitions

Expand Down Expand Up @@ -116,6 +118,7 @@ as well as the x distance, + or -, from where the swipe started to where it ende
trackMouse: PropTypes.bool, // default: false
disabled: PropTypes.bool, // default: false
innerRef: PropTypes.func,
rotationAngle: PropTypes.number // default: 0
```

## Development
Expand All @@ -139,11 +142,11 @@ You can now make updates/changes to `src/Swipeable.js` and webpack will rebuild,
## Notes
### Chrome 56 and later, warning with preventDefault
`swipeable` version `>=4.2.0` should fix this issue. [PR here](https://github.com/dogfessional/react-swipeable/pull/88).

The issue still exists in versions `<4.2.0`:
- When this library tries to call `e.preventDefault()` in Chrome 56+ a warning is logged:
- `Unable to preventDefault inside passive event listener due to target being treated as passive.`

This warning is because this [change](https://developers.google.com/web/updates/2017/01/scrolling-intervention) to Chrome 56+ and the way the synthetic events are setup in reactjs.

Follow reacts handling of this issue here: [facebook/react#8968](https://github.com/facebook/react/issues/8968)
Expand Down
23 changes: 20 additions & 3 deletions src/Swipeable.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,21 @@ function getPosition(e) {
: { x: e.clientX, y: e.clientY };
}

function rotateByAngle(pos, angle) {
if (angle === 0) {
return pos;
}

const { x, y } = pos;

const angleInRadians = (Math.PI / 180) * angle;
const rotatedX = x * Math.cos(angleInRadians) + y * Math.sin(angleInRadians);
const rotatedY = y * Math.cos(angleInRadians) - x * Math.sin(angleInRadians);
return { x: rotatedX, y: rotatedY };
}

function calculatePos(e, state) {
const { x, y } = getMovingPosition(e);
const { x, y } = rotateByAngle(getMovingPosition(e), state.rotationAngle);

const deltaX = state.x - x;
const deltaY = state.y - y;
Expand Down Expand Up @@ -146,11 +159,12 @@ class Swipeable extends React.Component {
// if more than a single touch don't track, for now...
if (e.touches && e.touches.length > 1) return;

const { x, y } = getPosition(e);
const { rotationAngle } = this.props;
const { x, y } = rotateByAngle(getPosition(e), rotationAngle);

if (this.props.stopPropagation) e.stopPropagation();

this.swipeable = { start: Date.now(), x, y, swiping: false };
this.swipeable = { start: Date.now(), x, y, swiping: false, rotationAngle };
}

eventMove(e) {
Expand Down Expand Up @@ -290,6 +304,7 @@ class Swipeable extends React.Component {
delete newProps.trackMouse;
delete newProps.disabled;
delete newProps.innerRef;
delete newProps.rotationAngle;

return React.createElement(
this.props.nodeName,
Expand Down Expand Up @@ -320,6 +335,7 @@ Swipeable.propTypes = {
disabled: PropTypes.bool,
innerRef: PropTypes.func,
children: PropTypes.node,
rotationAngle: PropTypes.number,
};

Swipeable.defaultProps = {
Expand All @@ -329,6 +345,7 @@ Swipeable.defaultProps = {
stopPropagation: false,
nodeName: 'div',
disabled: false,
rotationAngle: 0,
};

module.exports = Swipeable;
12 changes: 12 additions & 0 deletions src/__mocks__/react.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* global window */
const react = require('react');
// Resolution for requestAnimationFrame not supported in jest error :
// https://github.com/facebook/react/issues/9102#issuecomment-283873039
global.window = global;

window.addEventListener = () => {};
window.requestAnimationFrame = () => {
throw new Error('requestAnimationFrame is not supported in Node');
};

module.exports = react;
206 changes: 206 additions & 0 deletions src/__tests__/Swipeable.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ describe('Swipeable', () => {
wrapper.unmount();
});


describe('preventDefaultTouchmoveEvent and passive support eventListener option', () => {
beforeAll(() => {
DetectPassiveEvents.hasSupport = true;
Expand Down Expand Up @@ -379,4 +380,209 @@ describe('Swipeable', () => {
expect(swipeableDiv.prop('onTouchMove')).toBe(instance.eventMove);
});
});

describe('Handle Rotation by 90 degree: ', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for adding tests!!!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'course - won't work without. ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, a question: the tests are mainly data-driven. Is it okay to refactor them into data-driven tests with less duplication?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm all for cleaning up the tests and if you'd like to do that, could it be another PR?

Copy link
Contributor Author

@LarsKumbier LarsKumbier May 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, external PR would be best

let wrapper = {};
let touchHere = {};
let swipeFuncs = {};
let onTap = () => {};

beforeEach(() => {
swipeFuncs = getMockedSwipeFunctions();
onTap = jest.fn();

wrapper = mount((
<Swipeable
{...swipeFuncs}
onTap={onTap}
rotationAngle={90}
>
<span>Touch Here</span>
</Swipeable>
));

touchHere = wrapper.find('span');
});

afterEach(() => {
wrapper.unmount();
touchHere = {};
});

it('handles swipe direction to the right', () => {
touchHere.simulate('touchStart', createStartTouchEventObject({ x: 100, y: 100 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 125 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 150 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 175 }));
touchHere.simulate('touchEnd', createMoveTouchEventObject({ x: 100, y: 200 }));

expect(swipeFuncs.onSwipedDown).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingDown).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipedUp).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingUp).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipedLeft).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingLeft).not.toHaveBeenCalled();

expect(swipeFuncs.onSwipedRight).toHaveBeenCalled();
expect(swipeFuncs.onSwipingRight).toHaveBeenCalledTimes(3);

expect(onTap).not.toHaveBeenCalled();

expect(swipeFuncs.onSwiped).toHaveBeenCalled();
expect(swipeFuncs.onSwiping).toHaveBeenCalledTimes(3);
});

it('handles swipe direction to the left', () => {
touchHere.simulate('touchStart', createStartTouchEventObject({ x: 100, y: 100 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 75 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 50 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 25 }));
touchHere.simulate('touchEnd', createMoveTouchEventObject({ x: 100, y: 0 }));

expect(swipeFuncs.onSwipedDown).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingDown).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipedUp).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingUp).not.toHaveBeenCalled();

expect(swipeFuncs.onSwipedLeft).toHaveBeenCalled();
expect(swipeFuncs.onSwipingLeft).toHaveBeenCalledTimes(3);

expect(swipeFuncs.onSwipedRight).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingRight).not.toHaveBeenCalled();
expect(onTap).not.toHaveBeenCalled();

expect(swipeFuncs.onSwiped).toHaveBeenCalled();
expect(swipeFuncs.onSwiping).toHaveBeenCalledTimes(3);
});

it('handles swipe direction upwards', () => {
touchHere.simulate('touchStart', createStartTouchEventObject({ x: 100, y: 100 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 125, y: 100 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 150, y: 100 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 175, y: 100 }));
touchHere.simulate('touchEnd', createMoveTouchEventObject({ x: 200, y: 100 }));

expect(swipeFuncs.onSwipedDown).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingDown).not.toHaveBeenCalled();

expect(swipeFuncs.onSwipedUp).toHaveBeenCalled();
expect(swipeFuncs.onSwipingUp).toHaveBeenCalledTimes(3);

expect(swipeFuncs.onSwipedLeft).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingLeft).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipedRight).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingRight).not.toHaveBeenCalled();
expect(onTap).not.toHaveBeenCalled();

expect(swipeFuncs.onSwiped).toHaveBeenCalled();
expect(swipeFuncs.onSwiping).toHaveBeenCalledTimes(3);
});

it('handles swipe direction downwards', () => {
touchHere.simulate('touchStart', createStartTouchEventObject({ x: 100, y: 100 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 75, y: 100 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 50, y: 100 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 25, y: 100 }));
touchHere.simulate('touchEnd', createMoveTouchEventObject({ x: 0, y: 100 }));

expect(swipeFuncs.onSwipedDown).toHaveBeenCalled();
expect(swipeFuncs.onSwipingDown).toHaveBeenCalledTimes(3);

expect(swipeFuncs.onSwipedUp).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingUp).not.toHaveBeenCalled();

expect(swipeFuncs.onSwipedLeft).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingLeft).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipedRight).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingRight).not.toHaveBeenCalled();
expect(onTap).not.toHaveBeenCalled();

expect(swipeFuncs.onSwiped).toHaveBeenCalled();
expect(swipeFuncs.onSwiping).toHaveBeenCalledTimes(3);
});
});


it('Handle Rotation by negative 90 degree', () => {
const swipeFuncs = getMockedSwipeFunctions();
const onTap = jest.fn();

const wrapper = mount((
<Swipeable
{...swipeFuncs}
onTap={onTap}
rotationAngle={-90}
>
<span>Touch Here</span>
</Swipeable>
));

const touchHere = wrapper.find('span');

touchHere.simulate('touchStart', createStartTouchEventObject({ x: 100, y: 100 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 125 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 150 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 175 }));
touchHere.simulate('touchEnd', createMoveTouchEventObject({ x: 100, y: 200 }));

expect(swipeFuncs.onSwipedDown).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingDown).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipedUp).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingUp).not.toHaveBeenCalled();

expect(swipeFuncs.onSwipedLeft).toHaveBeenCalled();
expect(swipeFuncs.onSwipingLeft).toHaveBeenCalled();

expect(swipeFuncs.onSwipedRight).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingRight).not.toHaveBeenCalledTimes(3);

expect(onTap).not.toHaveBeenCalled();

expect(swipeFuncs.onSwiped).toHaveBeenCalled();
expect(swipeFuncs.onSwiping).toHaveBeenCalledTimes(3);

wrapper.unmount();
});


it('Handle Rotation by more than 360 degree', () => {
const swipeFuncs = getMockedSwipeFunctions();
const onTap = jest.fn();

const wrapper = mount((
<Swipeable
{...swipeFuncs}
onTap={onTap}
rotationAngle={360 + 270}
>
<span>Touch Here</span>
</Swipeable>
));

const touchHere = wrapper.find('span');

touchHere.simulate('touchStart', createStartTouchEventObject({ x: 100, y: 100 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 125 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 150 }));
touchHere.simulate('touchMove', createMoveTouchEventObject({ x: 100, y: 175 }));
touchHere.simulate('touchEnd', createMoveTouchEventObject({ x: 100, y: 200 }));

expect(swipeFuncs.onSwipedDown).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingDown).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipedUp).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingUp).not.toHaveBeenCalled();

expect(swipeFuncs.onSwipedLeft).toHaveBeenCalled();
expect(swipeFuncs.onSwipingLeft).toHaveBeenCalled();

expect(swipeFuncs.onSwipedRight).not.toHaveBeenCalled();
expect(swipeFuncs.onSwipingRight).not.toHaveBeenCalledTimes(3);

expect(onTap).not.toHaveBeenCalled();

expect(swipeFuncs.onSwiped).toHaveBeenCalled();
expect(swipeFuncs.onSwiping).toHaveBeenCalledTimes(3);

wrapper.unmount();
});
});