UNPKG

react-swipeable

Version:
486 lines (417 loc) 17.7 kB
/* global document, jest, expect, beforeAll, afterAll */ import React from 'react' import Enzyme from 'enzyme' import Adapter from 'enzyme-adapter-react-16' import { Swipeable, useSwipeable, LEFT, RIGHT, UP, DOWN } from '../index' import { createTouchEventObject as cte, createMouseEventObject as cme } from './helpers/events' const { mount } = Enzyme Enzyme.configure({ adapter: new Adapter() }) const DIRECTIONS = [LEFT, RIGHT, UP, DOWN] const USESWIPEABLE = 'useSwipeable' const SWIPEABLE = 'Swipeable' function getMockedSwipeFunctions() { return DIRECTIONS.reduce((acc, dir) => ({ ...acc, [`onSwiped${dir}`]: jest.fn() }), { onSwiping: jest.fn(), onSwiped: jest.fn() }) } function expectSwipingDir(fns, dir) { fns.mock.calls.forEach(call => { expect(call[0].dir).toBe(dir) }) } const expectSwipeFuncsDir = (sf, dir) => Object.keys(sf).forEach(s => { if (s.endsWith(dir) || s === 'onSwiped') { expect(sf[s]).toHaveBeenCalled() } else if (s === 'onSwiping') { expectSwipingDir(sf[s], dir) } else { expect(sf[s]).not.toHaveBeenCalled() } }) function mockListenersSetup(el) { // track eventListener adds to trigger later // idea from - https://github.com/airbnb/enzyme/issues/426#issuecomment-228601631 const eventListenerMap = {} el.addEventListener = jest.fn((event, cb) => { eventListenerMap[event] = cb }) el.removeEventListener = jest.fn((event, cb) => { if (eventListenerMap[event] === cb) delete eventListenerMap[event] }) return eventListenerMap } /* * Wrapping component for the hook. */ function SwipeableUsingHook(props) { const eventHandlers = useSwipeable(props) // Use innerRef prop to access the mounted div for testing. const ref = el => (props.innerRef && props.innerRef(el), eventHandlers.ref(el)) // eslint-disable-line return ( <div {...eventHandlers} ref={ref}> {props.children} </div> ) } function setupGetMountedComponent(TYPE, mockListeners = mockListenersSetup) { return props => { let wrapper let eventListenerMap const innerRef = el => { // don't re-assign eventlistener map if (!eventListenerMap) eventListenerMap = mockListeners(el) } if (TYPE === SWIPEABLE) { wrapper = mount( <Swipeable {...props} innerRef={innerRef}> <span>Touch Here</span> </Swipeable> ) } else if (TYPE === USESWIPEABLE) { wrapper = mount( <SwipeableUsingHook {...props} innerRef={innerRef}> <span>Touch Here</span> </SwipeableUsingHook> ) } return { eventListenerMap, wrapper } } } ;[USESWIPEABLE, SWIPEABLE].forEach(TYPE => { describe(`${TYPE}`, () => { let origAddEventListener let origRemoveEventListener let eventListenerMapDocument const getMountedComponent = setupGetMountedComponent(TYPE) beforeAll(() => { origAddEventListener = document.addEventListener origRemoveEventListener = document.removeEventListener }) beforeEach(() => { // track eventListener adds to trigger later // idea from - https://github.com/airbnb/enzyme/issues/426#issuecomment-228601631 eventListenerMapDocument = mockListenersSetup(document) }) afterAll(() => { document.eventListener = origAddEventListener document.removeEventListener = origRemoveEventListener }) it('handles touch events and fires correct props', () => { const swipeFuncs = getMockedSwipeFunctions() const { eventListenerMap } = getMountedComponent({ ...swipeFuncs }) eventListenerMap.touchstart(cte({ x: 100, y: 100, timeStamp: 8077.299999946263 })) eventListenerMap.touchmove(cte({ x: 100, y: 125, timeStamp: 8100.999999966007 })) eventListenerMap.touchmove(cte({ x: 100, y: 150, timeStamp: 8116.899999964517 })) eventListenerMap.touchmove(cte({ x: 100, y: 175, timeStamp: 8122.799999953713 })) eventListenerMap.touchmove(cte({ x: 100, y: 200, timeStamp: 8130.199999955433 })) eventListenerMap.touchend(cte({})) expect(swipeFuncs.onSwipedDown).toHaveBeenCalled() expect(swipeFuncs.onSwipedUp).not.toHaveBeenCalled() expect(swipeFuncs.onSwipedLeft).not.toHaveBeenCalled() expect(swipeFuncs.onSwipedRight).not.toHaveBeenCalled() expect(swipeFuncs.onSwiped.mock.calls).toMatchSnapshot(`${TYPE} onSwiped trackTouch`) expect(swipeFuncs.onSwiping.mock.calls).toMatchSnapshot(`${TYPE} onSwiping trackTouch`) }) it('handles mouse events with trackMouse prop and fires correct props', () => { const swipeFuncs = getMockedSwipeFunctions() const preventDefault = jest.fn() const e = { preventDefault } const { wrapper } = getMountedComponent({ ...swipeFuncs, trackMouse: true, trackTouch: false, preventDefaultTouchmoveEvent: true }) const touchHere = wrapper.find('span') touchHere.simulate('mouseDown', cme({ x: 100, y: 100, timeStamp: 1374809.499999974 })) eventListenerMapDocument.mousemove( cme({ x: 125, y: 100, timeStamp: 1374825.199999963, ...e }) ) eventListenerMapDocument.mousemove( cme({ x: 150, y: 100, timeStamp: 1374841.3999999757, ...e }) ) eventListenerMapDocument.mousemove( cme({ x: 175, y: 100, timeStamp: 1374857.399999979, ...e }) ) eventListenerMapDocument.mousemove( cme({ x: 200, y: 100, timeStamp: 1374873.499999987, ...e }) ) eventListenerMapDocument.mouseup({}) expect(preventDefault).not.toHaveBeenCalled() expect(swipeFuncs.onSwipedRight).toHaveBeenCalled() expect(swipeFuncs.onSwipedUp).not.toHaveBeenCalled() expect(swipeFuncs.onSwipedDown).not.toHaveBeenCalled() expect(swipeFuncs.onSwipedLeft).not.toHaveBeenCalled() expect(swipeFuncs.onSwiped.mock.calls).toMatchSnapshot(`${TYPE} onSwiped trackMouse`) expect(swipeFuncs.onSwiping.mock.calls).toMatchSnapshot(`${TYPE} onSwiping trackMouse`) }) it('calls preventDefault when swiping in direction that has a callback', () => { const onSwipedDown = jest.fn() const preventDefault = jest.fn() const e = { preventDefault } const { eventListenerMap } = getMountedComponent({ onSwipedDown, preventDefaultTouchmoveEvent: true }) eventListenerMap.touchstart(cte({ x: 100, y: 100, ...e })) eventListenerMap.touchmove(cte({ x: 100, y: 125, ...e })) eventListenerMap.touchmove(cte({ x: 100, y: 150, ...e })) eventListenerMap.touchmove(cte({ x: 100, y: 175, ...e })) eventListenerMap.touchmove(cte({ x: 100, y: 200, ...e })) eventListenerMap.touchend({ ...e }) expect(onSwipedDown).toHaveBeenCalled() expect(preventDefault).toHaveBeenCalledTimes(4) }) it('does not call preventDefault when false', () => { const onSwipedUp = jest.fn() const preventDefault = jest.fn() const e = { preventDefault } const { eventListenerMap } = getMountedComponent({ onSwipedUp }) eventListenerMap.touchstart(cte({ x: 100, y: 100, ...e })) eventListenerMap.touchmove(cte({ x: 100, y: 75, ...e })) eventListenerMap.touchmove(cte({ x: 100, y: 50, ...e })) eventListenerMap.touchmove(cte({ x: 100, y: 25, ...e })) eventListenerMap.touchmove(cte({ x: 100, y: 5, ...e })) eventListenerMap.touchend({ ...e }) expect(onSwipedUp).toHaveBeenCalled() expect(preventDefault).not.toHaveBeenCalled() }) it('calls preventDefault when onSwiping is present', () => { const onSwiping = jest.fn() const preventDefault = jest.fn() const { eventListenerMap } = getMountedComponent({ onSwiping, preventDefaultTouchmoveEvent: true }) eventListenerMap.touchstart(cte({ x: 100, y: 100, preventDefault })) eventListenerMap.touchmove(cte({ x: 100, y: 50, preventDefault })) eventListenerMap.touchmove(cte({ x: 100, y: 5, preventDefault })) eventListenerMap.touchend({ preventDefault }) expect(onSwiping).toHaveBeenCalled() expect(preventDefault).toHaveBeenCalled() }) it('calls preventDefault when onSwiped is present', () => { const onSwiped = jest.fn() const preventDefault = jest.fn() const { eventListenerMap } = getMountedComponent({ onSwiped, preventDefaultTouchmoveEvent: true }) eventListenerMap.touchstart(cte({ x: 100, y: 100, preventDefault })) eventListenerMap.touchmove(cte({ x: 100, y: 50, preventDefault })) eventListenerMap.touchmove(cte({ x: 100, y: 5, preventDefault })) eventListenerMap.touchend({ preventDefault }) expect(onSwiped).toHaveBeenCalled() expect(preventDefault).toHaveBeenCalled() }) it('does not re-check delta when swiping already in progress', () => { const onSwiping = jest.fn() const onSwipedRight = jest.fn() const onSwipedLeft = jest.fn() const { eventListenerMap } = getMountedComponent({ onSwiping, onSwipedRight, onSwipedLeft, trackMouse: true, delta: 40 }) eventListenerMap.touchstart(cte({ x: 100, y: 100 })) eventListenerMap.touchmove(cte({ x: 145, y: 100 })) eventListenerMap.touchmove(cte({ x: 80, y: 100 })) eventListenerMap.touchend({}) expect(onSwiping).toHaveBeenCalledTimes(2) expect(onSwiping.mock.calls[0][0].dir).toBe(RIGHT) expect(onSwiping.mock.calls[1][0].dir).toBe(LEFT) expect(onSwipedLeft).toHaveBeenCalledTimes(1) expect(onSwipedRight).not.toHaveBeenCalled() }) it('Handle Rotation by 90 degree', () => { const swipeFuncsRight = getMockedSwipeFunctions() const { eventListenerMap, wrapper } = getMountedComponent({ ...swipeFuncsRight, rotationAngle: 90 }) // check right eventListenerMap.touchstart(cte({ x: 100, y: 100 })) eventListenerMap.touchmove(cte({ x: 100, y: 125 })) eventListenerMap.touchmove(cte({ x: 100, y: 150 })) eventListenerMap.touchend({}) expectSwipeFuncsDir(swipeFuncsRight, RIGHT) // check left const swipeFuncsLeft = getMockedSwipeFunctions() wrapper.setProps({ ...swipeFuncsLeft, rotationAngle: 90 }) eventListenerMap.touchstart(cte({ x: 100, y: 100 })) eventListenerMap.touchmove(cte({ x: 100, y: 75 })) eventListenerMap.touchmove(cte({ x: 100, y: 50 })) eventListenerMap.touchend({}) expectSwipeFuncsDir(swipeFuncsLeft, LEFT) // check up const swipeFuncsUp = getMockedSwipeFunctions() wrapper.setProps({ ...swipeFuncsUp, rotationAngle: 90 }) eventListenerMap.touchstart(cte({ x: 100, y: 100 })) eventListenerMap.touchmove(cte({ x: 125, y: 100 })) eventListenerMap.touchmove(cte({ x: 150, y: 100 })) eventListenerMap.touchend({}) expectSwipeFuncsDir(swipeFuncsUp, UP) // check down const swipeFuncsDown = getMockedSwipeFunctions() wrapper.setProps({ ...swipeFuncsDown, rotationAngle: 90 }) eventListenerMap.touchstart(cte({ x: 100, y: 100 })) eventListenerMap.touchmove(cte({ x: 75, y: 100 })) eventListenerMap.touchmove(cte({ x: 50, y: 100 })) eventListenerMap.touchend({}) expectSwipeFuncsDir(swipeFuncsDown, DOWN) }) it('Handle "odd" rotations', () => { const swipeFuncsNegativeRotation = getMockedSwipeFunctions() const { eventListenerMap, wrapper } = getMountedComponent({ ...swipeFuncsNegativeRotation, rotationAngle: -90 }) // check -90 eventListenerMap.touchstart(cte({ x: 100, y: 100 })) eventListenerMap.touchmove(cte({ x: 100, y: 125 })) eventListenerMap.touchmove(cte({ x: 100, y: 150 })) eventListenerMap.touchend({}) expectSwipeFuncsDir(swipeFuncsNegativeRotation, 'Left') // check 360 + 270 const swipeFuncsLargeRotation = getMockedSwipeFunctions() wrapper.setProps({ ...swipeFuncsLargeRotation, rotationAngle: 360 + 270 }) eventListenerMap.touchstart(cte({ x: 100, y: 100 })) eventListenerMap.touchmove(cte({ x: 100, y: 125 })) eventListenerMap.touchmove(cte({ x: 100, y: 150 })) eventListenerMap.touchend({}) expectSwipeFuncsDir(swipeFuncsLargeRotation, 'Left') }) it('Handle Rotation that changes so keep the direction the same', () => { const swipeFuncs = getMockedSwipeFunctions() const { eventListenerMap, wrapper } = getMountedComponent({ ...swipeFuncs }) // check 0 eventListenerMap.touchstart(cte({ x: 100, y: 100 })) eventListenerMap.touchmove(cte({ x: 125, y: 100 })) eventListenerMap.touchmove(cte({ x: 150, y: 100 })) eventListenerMap.touchend({}) expect(swipeFuncs.onSwiped).toHaveBeenCalledTimes(1) expect(swipeFuncs.onSwipedRight).toHaveBeenCalledTimes(1) // check 90 wrapper.setProps({ rotationAngle: 90 }) eventListenerMap.touchstart(cte({ x: 100, y: 100 })) eventListenerMap.touchmove(cte({ x: 100, y: 125 })) eventListenerMap.touchmove(cte({ x: 100, y: 150 })) eventListenerMap.touchend({}) expect(swipeFuncs.onSwiped).toHaveBeenCalledTimes(2) expect(swipeFuncs.onSwipedRight).toHaveBeenCalledTimes(2) // check 180 wrapper.setProps({ rotationAngle: 180 }) eventListenerMap.touchstart(cte({ x: 100, y: 100 })) eventListenerMap.touchmove(cte({ x: 75, y: 100 })) eventListenerMap.touchmove(cte({ x: 50, y: 100 })) eventListenerMap.touchend({}) expect(swipeFuncs.onSwiped).toHaveBeenCalledTimes(3) expect(swipeFuncs.onSwipedRight).toHaveBeenCalledTimes(3) // check 270 wrapper.setProps({ rotationAngle: 270 }) eventListenerMap.touchstart(cte({ x: 100, y: 100 })) eventListenerMap.touchmove(cte({ x: 100, y: 75 })) eventListenerMap.touchmove(cte({ x: 100, y: 50 })) eventListenerMap.touchend({}) expect(swipeFuncs.onSwiped).toHaveBeenCalledTimes(4) expect(swipeFuncs.onSwipedRight).toHaveBeenCalledTimes(4) expect(swipeFuncs.onSwiping).toHaveBeenCalledTimes(8) ;[LEFT, UP, DOWN].forEach(dir => { expect(swipeFuncs[`onSwiped${dir}`]).not.toHaveBeenCalled() }) }) it('Cleans up and re-attaches touch event listeners', () => { let spies const mockListeners = el => { // already spying if (spies) return spies = {} spies.addEventListener = jest.spyOn(el, 'addEventListener') spies.removeEventListener = jest.spyOn(el, 'removeEventListener') } const { wrapper } = setupGetMountedComponent(TYPE, mockListeners)({}) expect(spies.addEventListener).toHaveBeenCalledTimes(3) expect(spies.removeEventListener).not.toHaveBeenCalled() wrapper.setProps({ trackTouch: false }) expect(spies.addEventListener).toHaveBeenCalledTimes(3) expect(spies.removeEventListener).toHaveBeenCalledTimes(3) // VERIFY REMOVED HANDLERS ARE THE SAME ONES THAT WERE ADDED! expect(spies.addEventListener.mock.calls.length).toBe(3) spies.addEventListener.mock.calls.forEach((call, idx) => { expect(spies.removeEventListener.mock.calls[idx][0]).toBe(call[0]) expect(spies.removeEventListener.mock.calls[idx][1]).toBe(call[1]) }) wrapper.setProps({ trackTouch: true }) expect(spies.addEventListener).toHaveBeenCalledTimes(6) expect(spies.removeEventListener).toHaveBeenCalledTimes(3) }) }) it(`${TYPE} handles updated prop swipe callbacks`, () => { let eventListenerMap const innerRef = el => { // don't re-assign eventlistener map if (!eventListenerMap) eventListenerMap = mockListenersSetup(el) } const onSwipedLeft = jest.fn() function TestComponent() { const [page, setPage] = React.useState(0) const next = () => (setPage(page + 1), onSwipedLeft(page + 1)) if (TYPE === USESWIPEABLE) { const handlers = useSwipeable({ onSwipedLeft: next }) // Use innerRef to access the mounted div for testing. const ref = el => (innerRef(el), handlers.ref(el)) return <div {...handlers} ref={ref} /> } if (TYPE === SWIPEABLE) { // Use innerRef to access the mounted div for testing. const ref = el => innerRef(el) return <Swipeable onSwipedLeft={next} innerRef={ref} /> } } mount(<TestComponent />) const pages = [1, 2, 3] // swipe left 3 times pages.forEach(() => { eventListenerMap.touchstart(cte({ x: 100, y: 100 })) eventListenerMap.touchmove(cte({ x: 75, y: 100 })) eventListenerMap.touchend({}) }) pages.forEach((page, idx) => { expect(onSwipedLeft.mock.calls[idx][0]).toBe(page) }) }) }) describe('Swipeable Specific', () => { it('renders children', () => { const wrapper = mount( <Swipeable> <div data-testref="child">One</div> <div data-testref="child">Two</div> </Swipeable> ) expect(wrapper.find({ 'data-testref': 'child' })).toHaveLength(2) wrapper.unmount() }) it("should pass ref to Swipeable's div", () => { const WrapperComp = class extends React.Component { static displayName = 'WrapperComp' constructor(props) { super(props) } assignRef = el => { this.testRef = el } render() { return <Swipeable innerRef={this.assignRef} /> } } const wrapper = mount(<WrapperComp />) const swipeableDiv = wrapper.find('div').instance() expect(wrapper.instance().testRef).toBe(swipeableDiv) wrapper.unmount() }) })