diff --git a/src/components/Movable/Movable.test.js b/src/components/Movable/Movable.test.js index 9301264..e0fdc2b 100644 --- a/src/components/Movable/Movable.test.js +++ b/src/components/Movable/Movable.test.js @@ -7,6 +7,7 @@ import {NAMESPACE} from './Movable'; import Movable from './'; describe('', () => { + const _addEventListener = document.addEventListener; describe('HTML structure', () => { it('should render a Movable', () => { @@ -23,16 +24,19 @@ describe('', () => { const preventDefault = sinon.spy(); const wrapper = mount(); - document.addEventListener = sinon.spy(); + const addEventListenerSpy = sinon.spy(document, 'addEventListener'); + wrapper.simulate('mousedown', {stopPropagation, preventDefault}); expect(handleOnBeginMove.calledOnce).toEqual(true); - expect(document.addEventListener.callCount).toEqual(2); + expect(addEventListenerSpy.callCount).toEqual(2); const event = handleOnBeginMove.args[0][0]; event.stopPropagation(); event.preventDefault(); expect(stopPropagation.calledOnce).toEqual(true); expect(preventDefault.calledOnce).toEqual(true); + + addEventListenerSpy.restore(); }); it('onMove()', () => { @@ -41,7 +45,9 @@ describe('', () => { const handlers = {}; let event; + const _addEventListener = document.addEventListener; document.addEventListener = (type, handler) => {handlers[type] = handler}; + wrapper.simulate('mousedown', {clientX: 10, clientY: 10}); wrapper.simulate('touchstart', {changedTouches: [{clientX: 10, clientY: 10}]}); @@ -80,6 +86,8 @@ describe('', () => { expect(event.cy).toEqual(-30); expect(event.dx).toEqual(-10); expect(event.dy).toEqual(-10); + + document.addEventListener = _addEventListener; }); it('onEndMove()', () => { @@ -102,6 +110,8 @@ describe('', () => { expect(event.cy).toEqual(10); expect(event.dx).toEqual(10); expect(event.dy).toEqual(10); + + document.addEventListener = _addEventListener; }); }); diff --git a/src/components/Poppable/Poppable.test.js b/src/components/Poppable/Poppable.test.js index 850ef93..e814def 100644 --- a/src/components/Poppable/Poppable.test.js +++ b/src/components/Poppable/Poppable.test.js @@ -1,8 +1,11 @@ import React from 'react'; +import sinon from 'sinon'; import {mount} from 'enzyme'; import Poppable, {NAMESPACE} from './Poppable'; +import PoppableStateful from './Poppable.stateful'; import defaultStrategy, {reposition, hide, trap} from './strategies'; import {sortPlacements, filterPlacements} from './strategies/reposition'; +import {usePosition} from './Poppable.hooks'; import {getBoundingRects} from './Poppable.utils'; import {vbefore, vcenter, vafter, hbefore, hcenter, hafter} from './Poppable.placements'; import {HIDDEN_PLACEMENT} from './Poppable.constants'; @@ -19,9 +22,39 @@ describe('', () => { }); }); + describe('Stateful', () => { + it('should render a stateful Poppable', () => { + const wrapper = mount(); + expect(wrapper.find('.' + NAMESPACE).hostNodes()).toHaveLength(1); + }); + }); + describe('Hooks', () => { - it.todo('updatePosition()'); - it.todo('usePositioning()'); + global.window.requestAnimationFrame.resetHistory(); + + const Elem = (props) => { + usePosition(props); + return null; + }; + + describe('usePosition', () => { + const props = { + target: new DOMRect(10, 10), + container: new DOMRect(), + reference: new DOMRect(30, 30), + placements: () => [{top: 0, left: 0}], + default: 0, + onPlacement: sinon.spy(), + strategy: defaultStrategy, + }; + + mount(); + + const lastCall = global.window.requestAnimationFrame.lastCall; + lastCall.args[0](); + + expect(props.onPlacement.calledWith({top: 0, left: 0, name: 'hidden'})).toEqual(true); + }); }); describe('Utils', () => { diff --git a/src/components/Resizable/Resizable.test.js b/src/components/Resizable/Resizable.test.js index 6aaedf8..6634c4f 100644 --- a/src/components/Resizable/Resizable.test.js +++ b/src/components/Resizable/Resizable.test.js @@ -19,16 +19,15 @@ describe('', () => { }); }); describe('Methods', () => { - let addEventListener; + const _addEventListener = document.addEventListener; const events = {}; beforeAll(() => { - addEventListener = document.addEventListener; document.addEventListener = (type, callback) => events[type] = callback; }); afterAll(() => { - document.addEventListener = addEventListener; + document.addEventListener = _addEventListener; }); it('onBeginResize onResize onEndResize', () => { diff --git a/src/hooks/useClickOutside/useClickOutside.test.js b/src/hooks/useClickOutside/useClickOutside.test.js index 2665dde..36c2ae2 100644 --- a/src/hooks/useClickOutside/useClickOutside.test.js +++ b/src/hooks/useClickOutside/useClickOutside.test.js @@ -5,31 +5,82 @@ import sinon from 'sinon'; import {mount} from 'enzyme'; import {useClickOutside} from './useClickOutside'; -const Elem = callback => { - useClickOutside(callback); +let clickInsideHandler; +const Elem = ({callback}) => { + clickInsideHandler = useClickOutside(callback); return
; }; describe('useClickOutside()', () => { it('Should add an event listener to the document', () => { - document.addEventListener = sinon.spy(); - document.removeEventListener = sinon.spy(); + const addEventListenerSpy = sinon.spy(document, 'addEventListener'); + const removeEventListenerSpy = sinon.spy(document, 'removeEventListener'); + let elem; act(() => {elem = mount()}); - expect(document.addEventListener.callCount).toEqual(4); // 2 calls are done regardless, not sure why, but it only happens during testing - expect(document.addEventListener.calledWith('mousedown')).toEqual(true); - expect(document.addEventListener.calledWith('mouseup')).toEqual(true); + + expect(addEventListenerSpy.callCount).toEqual(4); // 2 calls are done regardless, not sure why, but it only happens during testing + expect(addEventListenerSpy.calledWith('mousedown')).toEqual(true); + expect(addEventListenerSpy.calledWith('mouseup')).toEqual(true); + act(() => {elem.unmount()}); - expect(document.removeEventListener.calledTwice).toEqual(true); - expect(document.removeEventListener.calledWith('mousedown')).toEqual(true); - expect(document.removeEventListener.calledWith('mouseup')).toEqual(true); + expect(removeEventListenerSpy.calledTwice).toEqual(true); + expect(removeEventListenerSpy.calledWith('mousedown')).toEqual(true); + expect(removeEventListenerSpy.calledWith('mouseup')).toEqual(true); + + addEventListenerSpy.restore(); + removeEventListenerSpy.restore(); }); it('Should not trigger the callback on click inside', () => { const callback = sinon.spy(); const elem = mount(); + expect(elem.find('div')); elem.simulate('click'); expect(callback.callCount).toEqual(0); }); + + it('Should trigger the "click outside" callback', () => { + const callback = sinon.spy(); + const addEventListenerSpy = sinon.spy(document, 'addEventListener'); + const removeEventListenerSpy = sinon.spy(document, 'removeEventListener'); + + mount(); + + const mousedownCall = document.addEventListener.getCall(-2); + const mouseupCall = document.addEventListener.getCall(-1); + + // manually trigger the mousedown event callback with a fake event + mousedownCall.args[1]({isMouseDown: true}); + mouseupCall.args[1]({isMouseUp: true}); + expect(callback.callCount).toEqual(1); + expect(callback.calledWith({isMouseUp: true})).toEqual(true); + + addEventListenerSpy.restore(); + removeEventListenerSpy.restore(); + }); + + it('Should not trigger the "click outside" callback when clicked inside', () => { + const callback = sinon.spy(); + const addEventListenerSpy = sinon.spy(document, 'addEventListener'); + const removeEventListenerSpy = sinon.spy(document, 'removeEventListener'); + + mount(); + + const mousedownCall = document.addEventListener.getCall(-2); + const mouseupCall = document.addEventListener.getCall(-1); + + // 1 - simulate a mousedown event on the "outside" + clickInsideHandler(); + // 2 - simulate the internal, global, "mousedown" event + mousedownCall.args[1]({isMouseDown: true}); + // 3 - simulate the internal, global, "mouseup" event + mouseupCall.args[1]({isMouseUp: true}); + + expect(callback.callCount).toEqual(0); + + addEventListenerSpy.restore(); + removeEventListenerSpy.restore(); + }); }); diff --git a/src/hooks/useMounted/useMounted.test.js b/src/hooks/useMounted/useMounted.test.js index c172f02..f4d9699 100644 --- a/src/hooks/useMounted/useMounted.test.js +++ b/src/hooks/useMounted/useMounted.test.js @@ -4,19 +4,18 @@ import {mount} from 'enzyme'; import {useMounted} from './useMounted'; describe('useMounted()', () => { - it('Should return the previous value', () => { - const Elem = () => { - const mounted = useMounted(); - return ( -
- ); - }; + let mounted; + + const Elem = () => { + mounted = useMounted(); + return null; + }; + + it('Should be false on first render cycle and true afterwards', () => { let wrapper = null; act(() => {wrapper = mount()}); - expect(wrapper.find('.unmounted').length).toEqual(1); - expect(wrapper.find('.mounted').length).toEqual(0); + expect(mounted).toBeFalsy(); wrapper.setProps({foo: 'bar'}); // Force an update... - expect(wrapper.find('.mounted').length).toEqual(1); - expect(wrapper.find('.unmounted').length).toEqual(0); + expect(mounted).toBeTruthy(); }); }); diff --git a/src/hooks/useTimeout/useTimeout.test.js b/src/hooks/useTimeout/useTimeout.test.js index 9ec511d..7bd8181 100644 --- a/src/hooks/useTimeout/useTimeout.test.js +++ b/src/hooks/useTimeout/useTimeout.test.js @@ -1,31 +1,87 @@ -import React, {useState} from 'react'; +import React from 'react'; import {act} from 'react-dom/test-utils'; import {mount} from 'enzyme'; import useTimeout from './useTimeout'; const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay)); +let spy = 1, stopSpy; -const Elem = () => { - const [classname, setClassname] = useState('first'); - const {start} = useTimeout(() => { - setClassname('second'); - }, 0); - return ( -
- ); +const Timeout = ({delay, recurring}) => { + const {start, stop} = useTimeout(() => { spy += 1 }, delay, recurring); + start(); + stopSpy = stop; + return null; }; describe('useTimeout()', () => { - it('Should return the previous value', async () => { + beforeEach(() => { + spy = 1; + }); + + it('Should execute the timeout callback once, when no other prameters specified', async () => { + await act(async () => { + const wrapper = mount(); + + expect(spy).toEqual(1); + + await waitFor(0); // make sure the timeout has had enough time to fire + wrapper.update(); + expect(spy).toEqual(2); + }); + }); + + it('Should execute the timeout callback once', async () => { + await act(async () => { + const DELAY = 100; + const wrapper = mount(); + + expect(spy).toEqual(1); + + await waitFor(DELAY/2); + + // should remain "1" as the timeout has yet to be executed + expect(spy).toEqual(1); + + await waitFor(DELAY/2); + + wrapper.update(); + expect(spy).toEqual(2); + }); + }); + + it('Should not be recurring by default (called only once)', async () => { await act(async () => { - const wrapper = mount(); - expect(wrapper.find('.first').length).toEqual(1); - expect(wrapper.find('.second').length).toEqual(0); - wrapper.find('.first').simulate('click'); + const wrapper = await mount(); + + expect(spy).toEqual(1); + await waitFor(0); wrapper.update(); - expect(wrapper.find('.first').length).toEqual(0); - expect(wrapper.find('.second').length).toEqual(1); + expect(spy).toEqual(2); + + // wait some more time to make sure the timeout is not recurring + await waitFor(50); + wrapper.update(); + expect(spy).toEqual(2); + }); + }); + + it('Should stop recurring', async () => { + await act(async () => { + const DELAY = 50; + const wrapper = await mount(); + + expect(spy).toEqual(1); + + await waitFor(DELAY * 3); + wrapper.update(); + expect(spy).toEqual(3); + + stopSpy(); + await waitFor(DELAY * 2); + wrapper.update(); + // should remain unchanged since last time + expect(spy).toEqual(3); }); }); }); \ No newline at end of file diff --git a/src/tools/Puppeteer/Puppeteer.test.js b/src/tools/Puppeteer/Puppeteer.test.js index 11ccedf..aef236e 100644 --- a/src/tools/Puppeteer/Puppeteer.test.js +++ b/src/tools/Puppeteer/Puppeteer.test.js @@ -17,6 +17,7 @@ describe('Puppeteer', () => { expect(wrapper.find('puppet()')).toHaveLength(1); expect(wrapper.find('div').text()).toEqual('foobar'); }); + it('should override the puppet\'s props based on namespace', () => { const Container = ({children, value}) => ( { expect(wrapper.find('.a').text()).toEqual('foobar'); expect(wrapper.find('.b').text()).toEqual('bar'); }); + it('should override the puppet\'s props for global namespace', () => { const Container = ({children, value}) => ( { expect(wrapper.find('puppet()')).toHaveLength(1); expect(wrapper.find('div').text()).toEqual('foobar'); }); + it('should break the overriding of the puppet\'s props', () => { const Container = ({children, value}) => ( { wrapper.update(); expect(wrapper.find('div').text()).toEqual('bar'); }); + + it('should inherit props when nesting Puppeteered components', () => { + const Container = ({children, value}) => ( + value + props.value}} namespace='hello'>{children} + ); + const Puppet = puppet('hello')(({value}) =>
{value}
); + const wrapper = mount( + + + + + + ); + + expect(wrapper.find('div').text()).toEqual('barfoobaz'); + }); });