diff --git a/specs/Modal.spec.js b/specs/Modal.spec.js index 84918551..0fb47fc3 100644 --- a/specs/Modal.spec.js +++ b/specs/Modal.spec.js @@ -1,47 +1,37 @@ /* eslint-env mocha */ -import { renderModal, unmountModal } from './helper'; import TestUtils from 'react-addons-test-utils'; import React from 'react'; import ReactDOM from 'react-dom'; import Modal from '../lib/components/Modal'; -import ariaAppHider from '../lib/helpers/ariaAppHider'; +import * as ariaAppHider from '../lib/helpers/ariaAppHider'; +import { renderModal, unmountModal, emptyDOM } from './helper'; + const Simulate = TestUtils.Simulate; import sinon from 'sinon'; import expect from 'expect'; describe('Modal', () => { - afterEach('check if test cleaned up rendered modals', function () { - var overlay = document.querySelectorAll('.ReactModal__Overlay'); - var content = document.querySelectorAll('.ReactModal__Content'); - expect(overlay.length).toBe(0); - expect(content.length).toBe(0); - }); + afterEach('check if test cleaned up rendered modals', emptyDOM); it('scopes tab navigation to the modal'); it('focuses the last focused element when tabbing in from browser chrome'); - - it('can be open initially', function() { - var component = renderModal({isOpen: true}, 'hello'); + it('can be open initially', () => { + const component = renderModal({ isOpen: true }, 'hello'); expect(component.portal.refs.content.innerHTML.trim()).toEqual('hello'); - unmountModal(); }); it('can be closed initially', function() { var component = renderModal({}, 'hello'); expect(ReactDOM.findDOMNode(component.portal).innerHTML.trim()).toEqual(''); - unmountModal(); }); it('accepts appElement as a prop', function() { var el = document.createElement('div'); var node = document.createElement('div'); - ReactDOM.render( - - , node); + ReactDOM.render(( + + ), node); expect(el.getAttribute('aria-hidden')).toEqual('true'); ReactDOM.unmountComponentAtNode(node); }); @@ -50,13 +40,13 @@ describe('Modal', () => { var node = document.createElement('div'); var App = React.createClass({ render() { - return ( -
- - hello - -
- ); + return ( +
+ + hello + +
+ ); } }); Modal.setAppElement(node); @@ -66,25 +56,18 @@ describe('Modal', () => { ReactDOM.unmountComponentAtNode(node); }); - it('renders children', function() { - var child = 'I am a child of Modal, and he has sent me here...'; - var component = renderModal({isOpen: true}, child); - expect(component.portal.refs.content.innerHTML).toEqual(child); - unmountModal(); - }); + it('renders children [tested indirectly]'); it('renders the modal content with a dialog aria role when provided ', function () { var child = 'I am a child of Modal, and he has sent me here...'; var component = renderModal({isOpen: true, role: 'dialog'}, child); expect(component.portal.refs.content.getAttribute('role')).toEqual('dialog'); - unmountModal(); }); it('renders the modal with a aria-label based on the contentLabel prop', function () { var child = 'I am a child of Modal, and he has sent me here...'; var component = renderModal({isOpen: true, contentLabel: 'Special Modal'}, child); expect(component.portal.refs.content.getAttribute('aria-label')).toEqual('Special Modal'); - unmountModal(); }); it('has default props', function() { @@ -108,10 +91,10 @@ describe('Modal', () => { expect(!document.querySelector('.ReactModalPortal')).toExist(); }); - it('focuses the modal content', function() { + it('focuses the modal content', function(done) { renderModal({isOpen: true}, null, function () { expect(document.activeElement).toEqual(this.portal.refs.content); - unmountModal(); + done(); }); }); @@ -119,54 +102,45 @@ describe('Modal', () => { var modal = renderModal({ isOpen: true, onRequestClose: function () { - unmountModal(); - done(); + done(); } }, null); renderModal({ isOpen: true, onRequestClose: function () { - Simulate.keyDown(modal.portal.refs.content, { - // The keyCode is all that matters, so this works - key: 'FakeKeyToTestLater', - keyCode: 27, - which: 27 - }); - expect(document.activeElement).toEqual(modal.portal.refs.content); + Simulate.keyDown(modal.portal.refs.content, { + // The keyCode is all that matters, so this works + key: 'esc', + keyCode: 27, + which: 27 + }); + expect(document.activeElement).toEqual(modal.portal.refs.content); } }, null, function checkPortalFocus () { expect(document.activeElement).toEqual(this.portal.refs.content); Simulate.keyDown(this.portal.refs.content, { - // The keyCode is all that matters, so this works - key: 'FakeKeyToTestLater', - keyCode: 27, - which: 27 + // The keyCode is all that matters, so this works + key: 'esc', + keyCode: 27, + which: 27 }); }); }); - it('does not focus the modal content when a descendent is already focused', function() { var input = ( - { el && el.focus(); }} - /> + { el && el.focus(); }} /> ); renderModal({isOpen: true}, input, function () { expect(document.activeElement).toEqual(document.querySelector('.focus_input')); - unmountModal(); }); }); it('handles case when child has no tabbable elements', function() { var component = renderModal({isOpen: true}, 'hello'); - expect(function() { - Simulate.keyDown(component.portal.refs.content, {key: "Tab", keyCode: 9, which: 9}) - }).toNotThrow; - unmountModal(); + Simulate.keyDown(component.portal.refs.content, {key: "Tab", keyCode: 9, which: 9}); }); it('keeps focus inside the modal when child has no tabbable elements', function() { @@ -174,72 +148,71 @@ describe('Modal', () => { var modal = renderModal({isOpen: true}, 'hello'); expect(document.activeElement).toEqual(modal.portal.refs.content); Simulate.keyDown(modal.portal.refs.content, { - key: "Tab", - keyCode: 9, - which: 9, - preventDefault: function() { tabPrevented = true; } + key: "Tab", + keyCode: 9, + which: 9, + preventDefault: function() { tabPrevented = true; } }); expect(tabPrevented).toEqual(true); - unmountModal(); }); it('supports portalClassName', function () { var modal = renderModal({isOpen: true, portalClassName: 'myPortalClass'}); expect(modal.node.className).toEqual('myPortalClass'); - unmountModal(); }); - it('supports custom className', function() { - var modal = renderModal({isOpen: true, className: 'myClass'}); - expect(modal.portal.refs.content.className.indexOf('myClass')).toNotEqual(-1); - unmountModal(); + it('supports custom className', () => { + const modal = renderModal({ isOpen: true, className: 'myClass' }); + expect( + modal.portal.refs.content.className.indexOf('myClass') + ).toNotEqual(-1); }); - it('supports overlayClassName', function () { - var modal = renderModal({isOpen: true, overlayClassName: 'myOverlayClass'}); - expect(modal.portal.refs.overlay.className.indexOf('myOverlayClass')).toNotEqual(-1); - unmountModal(); + it('supports overlayClassName', () => { + const modal = renderModal({ isOpen: true, overlayClassName: 'myOverlayClass' }); + expect( + modal.portal.refs.overlay.className.indexOf('myOverlayClass') + ).toNotEqual(-1); }); - it('overrides the default styles when a custom classname is used', function () { - var modal = renderModal({isOpen: true, className: 'myClass'}); + it('overrides the default styles when a custom classname is used', () => { + const modal = renderModal({ isOpen: true, className: 'myClass' }); expect(modal.portal.refs.content.style.top).toEqual(''); - unmountModal(); }); - it('overrides the default styles when a custom overlayClassName is used', function () { - var modal = renderModal({isOpen: true, overlayClassName: 'myOverlayClass'}); + it('overrides the default styles when a custom overlayClassName is used', () => { + const modal = renderModal({ isOpen: true, overlayClassName: 'myOverlayClass' }); expect(modal.portal.refs.overlay.style.backgroundColor).toEqual(''); }); - it('supports adding style to the modal contents', function () { - var modal = renderModal({isOpen: true, style: {content: {width: '20px'}}}); + it('supports adding style to the modal contents', () => { + const modal = renderModal({ isOpen: true, style: { content: { width: '20px' } } }); expect(modal.portal.refs.content.style.width).toEqual('20px'); }); - it('supports overriding style on the modal contents', function() { - var modal = renderModal({isOpen: true, style: {content: {position: 'static'}}}); + it('supports overriding style on the modal contents', () => { + const modal = renderModal({ isOpen: true, style: { content: { position: 'static' } } }); expect(modal.portal.refs.content.style.position).toEqual('static'); }); - it('supports adding style on the modal overlay', function() { - var modal = renderModal({isOpen: true, style: {overlay: {width: '75px'}}}); + it('supports adding style on the modal overlay', () => { + const modal = renderModal({ isOpen: true, style: { overlay: { width: '75px' } } }); expect(modal.portal.refs.overlay.style.width).toEqual('75px'); }); - it('supports overriding style on the modal overlay', function() { - var modal = renderModal({isOpen: true, style: {overlay: {position: 'static'}}}); + it('supports overriding style on the modal overlay', () => { + const modal = renderModal({ isOpen: true, style: { overlay: { position: 'static' } } }); expect(modal.portal.refs.overlay.style.position).toEqual('static'); }); - it('supports overriding the default styles', function() { - var previousStyle = Modal.defaultStyles.content.position - //Just in case the default style is already relative, check that we can change it - var newStyle = previousStyle === 'relative' ? 'static': 'relative' - Modal.defaultStyles.content.position = newStyle - var modal = renderModal({isOpen: true}); + it('supports overriding the default styles', () => { + const previousStyle = Modal.defaultStyles.content.position; + // Just in case the default style is already relative, check that we can change it + const newStyle = previousStyle === 'relative' ? 'static' : 'relative'; + Modal.defaultStyles.content.position = newStyle; + const modal = renderModal({ isOpen: true }); expect(modal.portal.refs.content.style.position).toEqual(newStyle); - Modal.defaultStyles.content.position = previousStyle + Modal.defaultStyles.content.position = previousStyle; }); it('adds class to body when open', function() { @@ -280,19 +253,12 @@ describe('Modal', () => { var content = document.querySelector('.ReactModal__Content'); expect(overlay.className.match(/ReactModal__Overlay--after-open/)).toExist(); expect(content.className.match(/ReactModal__Content--after-open/)).toExist(); - unmountModal(); }); it('should trigger the onAfterOpen callback', function() { var afterOpenCallback = sinon.spy(); - renderModal({ - isOpen: true, - onAfterOpen: function() { - afterOpenCallback(); - } - }); + renderModal({ isOpen: true, onAfterOpen: afterOpenCallback }); expect(afterOpenCallback.called).toBeTruthy(); - unmountModal(); }); it('check the state of the modal after close with time out and reopen it', function() { @@ -305,121 +271,120 @@ describe('Modal', () => { modal.portal.open(); modal.portal.closeWithoutTimeout(); expect(!modal.portal.state.isOpen).toBeTruthy(); - unmountModal(); }); - describe('should close on overlay click', function() { - afterEach('Unmount modal', function() { - unmountModal(); - }); + describe('should close on overlay click', () => { + afterEach('Unmount modal', emptyDOM); - describe('verify props', function() { - it('verify default prop of shouldCloseOnOverlayClick', function () { - var modal = renderModal({isOpen: true}); - expect(modal.props.shouldCloseOnOverlayClick).toEqual(true); + describe('verify props', () => { + afterEach('Unmount modal', emptyDOM); + + it('verify default prop of shouldCloseOnOverlayClick', () => { + const modal = renderModal({ isOpen: true }); + expect(modal.props.shouldCloseOnOverlayClick).toEqual(true); }); it('verify prop of shouldCloseOnOverlayClick', function () { - var modal = renderModal({isOpen: true, shouldCloseOnOverlayClick: false}); - expect(modal.props.shouldCloseOnOverlayClick).toEqual(false); + var modal = renderModal({isOpen: true, shouldCloseOnOverlayClick: false}); + expect(modal.props.shouldCloseOnOverlayClick).toEqual(false); }); }); - describe('verify clicks', function() { - it('verify overlay click when shouldCloseOnOverlayClick sets to false', function () { - var requestCloseCallback = sinon.spy(); - var modal = renderModal({ - isOpen: true, - shouldCloseOnOverlayClick: false - }); - expect(modal.props.isOpen).toEqual(true); - var overlay = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Overlay'); - expect(overlay.length).toEqual(1); - Simulate.click(overlay[0]); // click the overlay - expect(!requestCloseCallback.called).toBeTruthy(); + describe('verify clicks', () => { + afterEach('Unmount modal', emptyDOM); + + it('verify overlay click when shouldCloseOnOverlayClick sets to false', () => { + const requestCloseCallback = sinon.spy(); + const modal = renderModal({ + isOpen: true, + shouldCloseOnOverlayClick: false + }); + expect(modal.props.isOpen).toEqual(true); + var overlay = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Overlay'); + expect(overlay.length).toEqual(1); + Simulate.click(overlay[0]); // click the overlay + expect(!requestCloseCallback.called).toBeTruthy(); }); it('verify overlay click when shouldCloseOnOverlayClick sets to true', function() { - var requestCloseCallback = sinon.spy(); - var modal = renderModal({ - isOpen: true, - shouldCloseOnOverlayClick: true, - onRequestClose: function() { - requestCloseCallback(); - } - }); - expect(modal.props.isOpen).toEqual(true); - var overlay = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Overlay'); - expect(overlay.length).toEqual(1); - Simulate.click(overlay[0]); // click the overlay - expect(requestCloseCallback.called).toBeTruthy(); + var requestCloseCallback = sinon.spy(); + var modal = renderModal({ + isOpen: true, + shouldCloseOnOverlayClick: true, + onRequestClose: function() { + requestCloseCallback(); + } + }); + expect(modal.props.isOpen).toEqual(true); + var overlay = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Overlay'); + expect(overlay.length).toEqual(1); + Simulate.click(overlay[0]); // click the overlay + expect(requestCloseCallback.called).toBeTruthy(); }); - it('verify overlay mouse down and content mouse up when shouldCloseOnOverlayClick sets to true', function() { - var requestCloseCallback = sinon.spy(); - var modal = renderModal({ - isOpen: true, - shouldCloseOnOverlayClick: true, - onRequestClose: function() { - requestCloseCallback(); - } - }); - expect(modal.props.isOpen).toEqual(true); - var overlay = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Overlay'); - var content = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Content'); - expect(overlay.length).toEqual(1); - expect(content.length).toEqual(1); - Simulate.mouseDown(overlay[0]); // click the overlay - Simulate.mouseUp(content[0]); - expect(!requestCloseCallback.called).toBeTruthy(); + it('verify overlay mouse down and content mouse up when shouldCloseOnOverlayClick sets to true', () => { + const requestCloseCallback = sinon.spy(); + const modal = renderModal({ + isOpen: true, + shouldCloseOnOverlayClick: true, + onRequestClose: requestCloseCallback + }); + expect(modal.props.isOpen).toEqual(true); + var overlay = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Overlay'); + var content = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Content'); + expect(overlay.length).toEqual(1); + expect(content.length).toEqual(1); + Simulate.mouseDown(overlay[0]); // click the overlay + Simulate.mouseUp(content[0]); + expect(!requestCloseCallback.called).toBeTruthy(); }); it('verify content mouse down and overlay mouse up when shouldCloseOnOverlayClick sets to true', function() { - var requestCloseCallback = sinon.spy(); - var modal = renderModal({ - isOpen: true, - shouldCloseOnOverlayClick: true, - onRequestClose: function() { - requestCloseCallback(); - } - }); - expect(modal.props.isOpen).toEqual(true); - var overlay = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Overlay'); - var content = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Content'); - expect(content.length).toEqual(1); - expect(overlay.length).toEqual(1); - Simulate.mouseDown(content[0]); // click the overlay - Simulate.mouseUp(overlay[0]); - expect(!requestCloseCallback.called).toBeTruthy(); + var requestCloseCallback = sinon.spy(); + var modal = renderModal({ + isOpen: true, + shouldCloseOnOverlayClick: true, + onRequestClose: function() { + requestCloseCallback(); + } + }); + expect(modal.props.isOpen).toEqual(true); + var overlay = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Overlay'); + var content = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Content'); + expect(content.length).toEqual(1); + expect(overlay.length).toEqual(1); + Simulate.mouseDown(content[0]); // click the overlay + Simulate.mouseUp(overlay[0]); + expect(!requestCloseCallback.called).toBeTruthy(); }); it('should not stop event propagation', function() { - var hasPropagated = false - var modal = renderModal({ - isOpen: true, - shouldCloseOnOverlayClick: true - }); - var overlay = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Overlay'); - window.addEventListener('click', function () { hasPropagated = true }) - overlay[0].dispatchEvent(new MouseEvent('click', { bubbles: true })) - expect(hasPropagated).toBeTruthy(); + var hasPropagated = false + var modal = renderModal({ + isOpen: true, + shouldCloseOnOverlayClick: true + }); + var overlay = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Overlay'); + window.addEventListener('click', function () { hasPropagated = true }) + overlay[0].dispatchEvent(new MouseEvent('click', { bubbles: true })) + expect(hasPropagated).toBeTruthy(); }); }); it('verify event passing on overlay click', function() { var requestCloseCallback = sinon.spy(); var modal = renderModal({ - isOpen: true, - shouldCloseOnOverlayClick: true, - onRequestClose: requestCloseCallback + isOpen: true, + shouldCloseOnOverlayClick: true, + onRequestClose: requestCloseCallback }); expect(modal.props.isOpen).toEqual(true); var overlay = TestUtils.scryRenderedDOMComponentsWithClass(modal.portal, 'ReactModal__Overlay'); expect(overlay.length).toEqual(1); // click the overlay Simulate.click(overlay[0], { - // Used to test that this was the event received - fakeData: 'ABC' + // Used to test that this was the event received + fakeData: 'ABC' }); expect(requestCloseCallback.called).toBeTruthy(); // Check if event is passed to onRequestClose callback. @@ -438,9 +403,7 @@ describe('Modal', () => { onRequestClose: requestCloseCallback }); expect(modal.props.isOpen).toEqual(true); - expect(function() { - Simulate.keyDown(modal.portal.refs.content, {key: "Esc", keyCode: 27, which: 27}) - }).toNotThrow(); + Simulate.keyDown(modal.portal.refs.content, {key: "Esc", keyCode: 27, which: 27}) expect(requestCloseCallback.called).toBeTruthy(); // Check if event is passed to onRequestClose callback. var event = requestCloseCallback.getCall(0).args[0]; @@ -455,7 +418,6 @@ describe('Modal', () => { isOpen: true, closeTimeoutMS }); - modal.portal.closeWithTimeout(); const overlay = TestUtils.findRenderedDOMComponentWithClass(modal.portal, 'ReactModal__Overlay'); @@ -465,33 +427,27 @@ describe('Modal', () => { expect(/ReactModal__Content--before-close/.test(content.className)).toBe(true); modal.portal.closeWithoutTimeout(); - unmountModal(); }); it('keeps the modal in the DOM until closeTimeoutMS elapses', (done) => { - const closeTimeoutMS = 50; + const closeTimeoutMS = 100; - renderModal({ - isOpen: true, - closeTimeoutMS - }); - - unmountModal(); + const modal = renderModal({ isOpen: true, closeTimeoutMS }); + modal.portal.closeWithTimeout(); - const checkDOM = (expectMounted) => { + function checkDOM(count) { const overlay = document.querySelectorAll('.ReactModal__Overlay'); const content = document.querySelectorAll('.ReactModal__Content'); - const numNodes = expectMounted ? 1 : 0; - expect(overlay.length).toBe(numNodes); - expect(content.length).toBe(numNodes); - }; + expect(overlay.length).toBe(count); + expect(content.length).toBe(count); + } // content is still mounted after modal is gone - checkDOM(true); + checkDOM(1); setTimeout(() => { // content is unmounted after specified timeout - checkDOM(false); + checkDOM(0); done(); }, closeTimeoutMS); }); diff --git a/specs/helper.js b/specs/helper.js index e21def25..f50fbab2 100644 --- a/specs/helper.js +++ b/specs/helper.js @@ -20,3 +20,9 @@ export const unmountModal = function() { ReactDOM.unmountComponentAtNode(currentDiv); document.body.removeChild(currentDiv); }; + +export const emptyDOM = () => { + while (divStack.length) { + unmountModal(); + } +};