diff --git a/specs/Modal.events.spec.js b/specs/Modal.events.spec.js
new file mode 100644
index 00000000..bca42ed4
--- /dev/null
+++ b/specs/Modal.events.spec.js
@@ -0,0 +1,137 @@
+import sinon from 'sinon';
+import expect from 'expect';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import TestUtils from 'react-addons-test-utils';
+import Modal from '../lib/components/Modal';
+import * as ariaAppHider from '../lib/helpers/ariaAppHider';
+import {
+ isBodyWithReactModalOpenClass, findDOMWithClass,
+ contentAttribute, overlayAttribute,
+ moverlay, mcontent,
+ clickAt, mouseDownAt, mouseUpAt, escKeyDown, tabKeyDown,
+ renderModal, unmountModal, emptyDOM
+} from './helper';
+
+describe('Events', () => {
+ afterEach('Unmount modal', emptyDOM);
+
+ it('should trigger the onAfterOpen callback', () => {
+ var afterOpenCallback = sinon.spy();
+ renderModal({ isOpen: true, onAfterOpen: afterOpenCallback });
+ expect(afterOpenCallback.called).toBeTruthy();
+ });
+
+ it('keeps focus inside the modal when child has no tabbable elements', () => {
+ var tabPrevented = false;
+ var modal = renderModal({ isOpen: true }, 'hello');
+ const content = mcontent(modal);
+ expect(document.activeElement).toEqual(content);
+ tabKeyDown(content, {
+ preventDefault: function() { tabPrevented = true; }
+ });
+ expect(tabPrevented).toEqual(true);
+ });
+
+ it('handles case when child has no tabbable elements', () => {
+ var modal = renderModal({ isOpen: true }, 'hello');
+ const content = mcontent(modal);
+ tabKeyDown(content);
+ expect(document.activeElement).toEqual(content);
+ });
+
+ it('should close on Esc key event', () => {
+ var requestCloseCallback = sinon.spy();
+ var modal = renderModal({
+ isOpen: true,
+ shouldCloseOnOverlayClick: true,
+ onRequestClose: requestCloseCallback
+ });
+ escKeyDown(mcontent(modal));
+ expect(requestCloseCallback.called).toBeTruthy();
+ // Check if event is passed to onRequestClose callback.
+ var event = requestCloseCallback.getCall(0).args[0];
+ expect(event).toExist();
+ });
+
+ it('verify overlay click when shouldCloseOnOverlayClick sets to false', () => {
+ const requestCloseCallback = sinon.spy();
+ const modal = renderModal({
+ isOpen: true,
+ shouldCloseOnOverlayClick: false
+ });
+ var overlay = moverlay(modal);
+ clickAt(overlay);
+ expect(!requestCloseCallback.called).toBeTruthy();
+ });
+
+ it('verify overlay click when shouldCloseOnOverlayClick sets to true', () => {
+ var requestCloseCallback = sinon.spy();
+ var modal = renderModal({
+ isOpen: true,
+ shouldCloseOnOverlayClick: true,
+ onRequestClose: function() {
+ requestCloseCallback();
+ }
+ });
+ clickAt(moverlay(modal));
+ 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
+ });
+ mouseDownAt(moverlay(modal));
+ mouseUpAt(mcontent(modal));
+ expect(!requestCloseCallback.called).toBeTruthy();
+ });
+
+ it('verify content mouse down and overlay mouse up when shouldCloseOnOverlayClick sets to true', () => {
+ var requestCloseCallback = sinon.spy();
+ var modal = renderModal({
+ isOpen: true,
+ shouldCloseOnOverlayClick: true,
+ onRequestClose: function() {
+ requestCloseCallback();
+ }
+ });
+ mouseDownAt(mcontent(modal));
+ mouseUpAt(moverlay(modal));
+ expect(!requestCloseCallback.called).toBeTruthy();
+ });
+
+ it('should not stop event propagation', () => {
+ var hasPropagated = false;
+ var modal = renderModal({
+ isOpen: true,
+ shouldCloseOnOverlayClick: true
+ });
+ window.addEventListener('click', () => {
+ hasPropagated = true;
+ });
+ moverlay(modal).dispatchEvent(new MouseEvent('click', { bubbles: true }));
+ expect(hasPropagated).toBeTruthy();
+ });
+
+ it('verify event passing on overlay click', () => {
+ var requestCloseCallback = sinon.spy();
+ var modal = renderModal({
+ isOpen: true,
+ shouldCloseOnOverlayClick: true,
+ onRequestClose: requestCloseCallback
+ });
+ // click the overlay
+ clickAt(moverlay(modal), {
+ // Used to test that this was the event received
+ fakeData: 'ABC'
+ });
+ expect(requestCloseCallback.called).toBeTruthy();
+ // Check if event is passed to onRequestClose callback.
+ var event = requestCloseCallback.getCall(0).args[0];
+ expect(event).toExist();
+ });
+});
diff --git a/specs/Modal.spec.js b/specs/Modal.spec.js
index 65e80c22..97d85494 100644
--- a/specs/Modal.spec.js
+++ b/specs/Modal.spec.js
@@ -1,32 +1,56 @@
/* eslint-env mocha */
-import TestUtils from 'react-addons-test-utils';
+import sinon from 'sinon';
+import expect from 'expect';
import React from 'react';
import ReactDOM from 'react-dom';
+import TestUtils from 'react-addons-test-utils';
import Modal from '../lib/components/Modal';
import * as ariaAppHider from '../lib/helpers/ariaAppHider';
-import { renderModal, unmountModal, emptyDOM } from './helper';
+import {
+ isBodyWithReactModalOpenClass, findDOMWithClass,
+ contentAttribute, overlayAttribute,
+ mcontent, moverlay,
+ clickAt, mouseDownAt, mouseUpAt, escKeyDown, tabKeyDown,
+ renderModal, unmountModal, emptyDOM
+} from './helper';
+
+import * as events from './Modal.events.spec';
+import * as styles from './Modal.style.spec';
const Simulate = TestUtils.Simulate;
-import sinon from 'sinon';
-import expect from 'expect';
-describe('Modal', () => {
+describe('State', () => {
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('renders children [tested indirectly]');
it('can be open initially', () => {
- const component = renderModal({ isOpen: true }, 'hello');
- expect(component.portal.refs.content.innerHTML.trim()).toEqual('hello');
+ const modal = renderModal({ isOpen: true }, 'hello');
+ expect(mcontent(modal)).toExist();
});
- it('can be closed initially', function() {
- var component = renderModal({}, 'hello');
- expect(ReactDOM.findDOMNode(component.portal).innerHTML.trim()).toEqual('');
+ it('can be closed initially', () => {
+ var modal = renderModal({}, 'hello');
+ expect(ReactDOM.findDOMNode(mcontent(modal))).toNotExist();
});
- it('accepts appElement as a prop', function() {
+ it('has default props', () => {
+ var node = document.createElement('div');
+ Modal.setAppElement(document.createElement('div'));
+ var modal = ReactDOM.render(, node);
+ var props = modal.props;
+ expect(props.isOpen).toBe(false);
+ expect(props.ariaHideApp).toBe(true);
+ expect(props.closeTimeoutMS).toBe(0);
+ expect(props.shouldCloseOnOverlayClick).toBe(true);
+ ReactDOM.unmountComponentAtNode(node);
+ ariaAppHider.resetForTesting();
+ Modal.setAppElement(document.body); // restore default
+ });
+
+ it('accepts appElement as a prop', () => {
var el = document.createElement('div');
var node = document.createElement('div');
ReactDOM.render((
@@ -36,247 +60,179 @@ describe('Modal', () => {
ReactDOM.unmountComponentAtNode(node);
});
- it('renders into the body, not in context', function() {
+ it('renders into the body, not in context', () => {
var node = document.createElement('div');
var App = React.createClass({
render() {
- return (
-
-
- hello
-
-
- );
+ return (
+
+
+ hello
+
+
+ );
}
});
Modal.setAppElement(node);
ReactDOM.render(, node);
- var modalParent = document.body.querySelector('.ReactModalPortal').parentNode;
- expect(modalParent).toEqual(document.body);
+ expect(
+ document.body.querySelector('.ReactModalPortal').parentNode
+ ).toEqual(
+ document.body
+ );
ReactDOM.unmountComponentAtNode(node);
});
- it('renders children [tested indirectly]');
-
- it('renders the modal content with a dialog aria role when provided ', function () {
+ it('renders the modal content with a dialog aria role when provided ', () => {
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');
+ var modal = renderModal({ isOpen: true, role: 'dialog' }, child);
+ expect(contentAttribute(modal, 'role')).toEqual('dialog');
});
- it('renders the modal with a aria-label based on the contentLabel prop', function () {
+ it('renders the modal with a aria-label based on the contentLabel prop', () => {
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');
- });
-
- it('has default props', function() {
- var node = document.createElement('div');
- Modal.setAppElement(document.createElement('div'));
- var component = ReactDOM.render(, node);
- var props = component.props;
- expect(props.isOpen).toBe(false);
- expect(props.ariaHideApp).toBe(true);
- expect(props.closeTimeoutMS).toBe(0);
- expect(props.shouldCloseOnOverlayClick).toBe(true);
- ReactDOM.unmountComponentAtNode(node);
- ariaAppHider.resetForTesting();
- Modal.setAppElement(document.body); // restore default
+ var modal = renderModal({ isOpen: true, contentLabel: 'Special Modal' }, child);
+ expect(contentAttribute(modal, 'aria-label')).toEqual('Special Modal');
});
- it('removes the portal node', function() {
- var component = renderModal({isOpen: true}, 'hello');
- expect(component.portal.refs.content.innerHTML.trim()).toEqual('hello');
+ it('removes the portal node', () => {
+ var modal = renderModal({ isOpen: true }, 'hello');
unmountModal();
- expect(!document.querySelector('.ReactModalPortal')).toExist();
+ expect(document.querySelector('.ReactModalPortal')).toNotExist();
});
- it('focuses the modal content', function(done) {
- renderModal({isOpen: true}, null, function () {
- expect(document.activeElement).toEqual(this.portal.refs.content);
- done();
- });
+ it('focuses the modal content', () => {
+ const modal = renderModal({ isOpen: true }, null);
+ expect(document.activeElement).toBe(mcontent(modal));
});
- it('give back focus to previous element or modal.', (done) => {
+ it('give back focus to previous element or modal.', done => {
function cleanup () {
unmountModal();
done();
}
- const modal = renderModal({
+ const modalA = renderModal({
isOpen: true,
+ className: 'modal-a',
onRequestClose () {
- cleanup();
+ cleanup();
}
}, null);
- renderModal({
+ const modalContent = mcontent(modalA);
+ expect(document.activeElement).toEqual(modalContent);
+
+ const modalB = renderModal({
isOpen: true,
- onRequestClose: function () {
- 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);
+ className: 'modal-b',
+ onRequestClose: function() {
+ const modalContent = mcontent(modalB);
+ expect(document.activeElement).toEqual(mcontent(modalA));
+ escKeyDown(modalContent);
+ expect(document.activeElement).toEqual(modalContent);
}
- }, 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: 'esc',
- keyCode: 27,
- which: 27
- });
- });
+ }, null);
+
+ escKeyDown(modalContent);
});
- it('does not focus the modal content when a descendent is already focused', function() {
+ it('does not steel focus when a descendent is already focused', () => {
+ var content;
var input = (
- { el && el.focus(); }} />
+ { el && el.focus(); content = el; }} />
);
-
- renderModal({isOpen: true}, input, function () {
- expect(document.activeElement).toEqual(document.querySelector('.focus_input'));
+ renderModal({ isOpen: true }, input, function () {
+ expect(document.activeElement).toEqual(content);
});
});
- it('handles case when child has no tabbable elements', function() {
- var component = renderModal({isOpen: true}, 'hello');
- 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() {
- var tabPrevented = false;
- 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; }
+ it('supports portalClassName', () => {
+ var modal = renderModal({
+ isOpen: true,
+ portalClassName: 'myPortalClass'
});
- expect(tabPrevented).toEqual(true);
- });
-
- it('supports portalClassName', function () {
- var modal = renderModal({isOpen: true, portalClassName: 'myPortalClass'});
- expect(modal.node.className).toEqual('myPortalClass');
+ expect(modal.node.className.includes('myPortalClass')).toBeTruthy();
});
it('supports custom className', () => {
const modal = renderModal({ isOpen: true, className: 'myClass' });
expect(
- modal.portal.refs.content.className.indexOf('myClass')
- ).toNotEqual(-1);
+ mcontent(modal).className.includes('myClass')
+ ).toBeTruthy();
});
it('supports overlayClassName', () => {
- const modal = renderModal({ isOpen: true, overlayClassName: 'myOverlayClass' });
+ 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', () => {
- const modal = renderModal({ isOpen: true, className: 'myClass' });
- expect(modal.portal.refs.content.style.top).toEqual('');
- });
-
- 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', () => {
- const modal = renderModal({ isOpen: true, style: { content: { width: '20px' } } });
- expect(modal.portal.refs.content.style.width).toEqual('20px');
+ moverlay(modal).className.includes('myOverlayClass')
+ ).toBeTruthy();
});
- 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', () => {
- 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', () => {
- const modal = renderModal({ isOpen: true, style: { overlay: { position: 'static' } } });
- expect(modal.portal.refs.overlay.style.position).toEqual('static');
- });
-
- 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;
+ it('don\'t append class to document.body if modal is not open', () => {
+ renderModal({ isOpen: false });
+ expect(!isBodyWithReactModalOpenClass()).toBeTruthy();
+ unmountModal();
});
- it('should remove class from body when no modals opened', () => {
- const findReactModalOpenClass = () => document.body.className.indexOf('ReactModal__Body--open');
- renderModal({ isOpen: true });
+ it('append class to document.body if modal is open', () => {
renderModal({ isOpen: true });
- expect(findReactModalOpenClass() > -1).toBe(true);
+ expect(isBodyWithReactModalOpenClass()).toBeTruthy();
unmountModal();
- expect(findReactModalOpenClass() > -1).toBe(true);
- unmountModal();
- expect(findReactModalOpenClass() === -1).toBe(true);
});
- it('adds class to body when open', function() {
- renderModal({ isOpen: false });
- expect(document.body.className.indexOf('ReactModal__Body--open') !== -1).toEqual(false);
- unmountModal();
-
+ it('removes class from document.body when unmounted without closing', () => {
renderModal({ isOpen: true });
- expect(document.body.className.indexOf('ReactModal__Body--open') !== -1).toEqual(true);
unmountModal();
-
- renderModal({ isOpen: false });
- expect(document.body.className.indexOf('ReactModal__Body--open') !== -1).toEqual(false);
+ expect(!isBodyWithReactModalOpenClass()).toBeTruthy();
});
- it('removes class from body when unmounted without closing', function() {
- renderModal({isOpen: true});
- expect(document.body.className.indexOf('ReactModal__Body--open') !== -1).toEqual(true);
+ it('remove class from document.body when no modals opened', () => {
+ renderModal({ isOpen: true });
+ renderModal({ isOpen: true });
+ expect(isBodyWithReactModalOpenClass()).toBeTruthy();
+ unmountModal();
+ expect(isBodyWithReactModalOpenClass()).toBeTruthy();
unmountModal();
- expect(document.body.className.indexOf('ReactModal__Body--open') !== -1).toEqual(false);
+ expect(!isBodyWithReactModalOpenClass()).toBeTruthy();
});
- it('removes aria-hidden from appElement when unmounted without closing', function() {
+ it('removes aria-hidden from appElement when unmounted without closing', () => {
var el = document.createElement('div');
var node = document.createElement('div');
- ReactDOM.render(React.createElement(Modal, {
- isOpen: true,
- appElement: el
- }), node);
+ ReactDOM.render((
+
+ ), node);
expect(el.getAttribute('aria-hidden')).toEqual('true');
ReactDOM.unmountComponentAtNode(node);
expect(el.getAttribute('aria-hidden')).toEqual(null);
});
- it('adds --after-open for animations', function() {
- renderModal({isOpen: true});
- var overlay = document.querySelector('.ReactModal__Overlay');
- var content = document.querySelector('.ReactModal__Content');
- expect(overlay.className.match(/ReactModal__Overlay--after-open/)).toExist();
- expect(content.className.match(/ReactModal__Content--after-open/)).toExist();
+ it('adds --after-open for animations', () => {
+ const modal = renderModal({ isOpen: true });
+ var rg = /--after-open/i;
+ expect(rg.test(mcontent(modal).className)).toBeTruthy();
+ expect(rg.test(moverlay(modal).className)).toBeTruthy();
});
- it('should trigger the onAfterOpen callback', function() {
- var afterOpenCallback = sinon.spy();
- renderModal({ isOpen: true, onAfterOpen: afterOpenCallback });
- expect(afterOpenCallback.called).toBeTruthy();
+ it('adds --before-close for animations', () => {
+ const closeTimeoutMS = 50;
+ const modal = renderModal({
+ isOpen: true,
+ closeTimeoutMS
+ });
+ modal.portal.closeWithTimeout();
+
+ const rg = /--before-close/i;
+ expect(rg.test(moverlay(modal).className)).toBeTruthy();
+ expect(rg.test(mcontent(modal).className)).toBeTruthy();
+
+ modal.portal.closeWithoutTimeout();
});
- it('check the state of the modal after close with time out and reopen it', function() {
+ it('check the state of the modal after close with time out and reopen it', () => {
var modal = renderModal({
isOpen: true,
closeTimeoutMS: 2000,
@@ -288,163 +244,18 @@ describe('Modal', () => {
expect(!modal.portal.state.isOpen).toBeTruthy();
});
- describe('should close on overlay click', () => {
- afterEach('Unmount modal', emptyDOM);
-
- 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);
- });
- });
-
- 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();
- });
-
- 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();
- });
-
- 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();
- });
- });
-
- it('verify event passing on overlay click', function() {
- var requestCloseCallback = sinon.spy();
- var modal = renderModal({
- 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'
- });
- expect(requestCloseCallback.called).toBeTruthy();
- // Check if event is passed to onRequestClose callback.
- var event = requestCloseCallback.getCall(0).args[0];
- expect(event).toBeTruthy();
- expect(event.constructor).toBeTruthy();
- expect(event.constructor.name).toEqual('SyntheticEvent');
- });
- });
-
- it('should close on Esc key event', function() {
- var requestCloseCallback = sinon.spy();
- var modal = renderModal({
- isOpen: true,
- shouldCloseOnOverlayClick: true,
- onRequestClose: requestCloseCallback
- });
- expect(modal.props.isOpen).toEqual(true);
- 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];
- expect(event).toBeTruthy();
- expect(event.constructor).toBeTruthy();
- expect(event.constructor.name).toEqual('SyntheticEvent');
+ it('verify default prop of shouldCloseOnOverlayClick', () => {
+ const modal = renderModal({ isOpen: true });
+ expect(modal.props.shouldCloseOnOverlayClick).toBeTruthy();
});
- it('adds --before-close for animations', () => {
- const closeTimeoutMS = 50;
- const modal = renderModal({
- isOpen: true,
- closeTimeoutMS
- });
- modal.portal.closeWithTimeout();
-
- const overlay = TestUtils.findRenderedDOMComponentWithClass(modal.portal, 'ReactModal__Overlay');
- const content = TestUtils.findRenderedDOMComponentWithClass(modal.portal, 'ReactModal__Content');
-
- expect(/ReactModal__Overlay--before-close/.test(overlay.className)).toBe(true);
- expect(/ReactModal__Content--before-close/.test(content.className)).toBe(true);
-
- modal.portal.closeWithoutTimeout();
+ it('verify prop of shouldCloseOnOverlayClick', () => {
+ var modalOpts = { isOpen: true, shouldCloseOnOverlayClick: false };
+ var modal = renderModal(modalOpts);
+ expect(!modal.props.shouldCloseOnOverlayClick).toBeTruthy();
});
- it('keeps the modal in the DOM until closeTimeoutMS elapses', (done) => {
+ it('keeps the modal in the DOM until closeTimeoutMS elapses', done => {
const closeTimeoutMS = 100;
const modal = renderModal({ isOpen: true, closeTimeoutMS });
diff --git a/specs/Modal.style.spec.js b/specs/Modal.style.spec.js
new file mode 100644
index 00000000..cb8a3b45
--- /dev/null
+++ b/specs/Modal.style.spec.js
@@ -0,0 +1,65 @@
+import sinon from 'sinon';
+import expect from 'expect';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import TestUtils from 'react-addons-test-utils';
+import Modal from '../lib/components/Modal';
+import * as ariaAppHider from '../lib/helpers/ariaAppHider';
+import {
+ isBodyWithReactModalOpenClass, findDOMWithClass,
+ contentAttribute, overlayAttribute,
+ mcontent, moverlay,
+ clickAt, mouseDownAt, mouseUpAt, escKeyDown, tabKeyDown,
+ renderModal, unmountModal, emptyDOM
+} from './helper';
+
+describe('Style', () => {
+ afterEach('Unmount modal', emptyDOM);
+
+ it('overrides the default styles when a custom classname is used', () => {
+ const modal = renderModal({ isOpen: true, className: 'myClass' });
+ expect(mcontent(modal).style.top).toEqual('');
+ });
+
+ it('overrides the default styles when a custom overlayClassName is used', () => {
+ const modal = renderModal({
+ isOpen: true,
+ overlayClassName: 'myOverlayClass'
+ });
+ expect(moverlay(modal).style.backgroundColor).toEqual('');
+ });
+
+ it('supports adding style to the modal contents', () => {
+ const style = { content: { width: '20px' } };
+ const modal = renderModal({ isOpen: true, style: style });
+ expect(mcontent(modal).style.width).toEqual('20px');
+ });
+
+ it('supports overriding style on the modal contents', () => {
+ const style = { content: { position: 'static' } };
+ const modal = renderModal({ isOpen: true, style: style });
+ expect(mcontent(modal).style.position).toEqual('static');
+ });
+
+ it('supports adding style on the modal overlay', () => {
+ const style = { overlay: { width: '75px' } };
+ const modal = renderModal({ isOpen: true, style: style });
+ expect(moverlay(modal).style.width).toEqual('75px');
+ });
+
+ it('supports overriding style on the modal overlay', () => {
+ const style = { overlay: { position: 'static' } };
+ const modal = renderModal({ isOpen: true, style: style });
+ expect(moverlay(modal).style.position).toEqual('static');
+ });
+
+ 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;
+ });
+});
diff --git a/specs/helper.js b/specs/helper.js
index f50fbab2..e34bf37a 100644
--- a/specs/helper.js
+++ b/specs/helper.js
@@ -1,10 +1,109 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Modal from '../lib/components/Modal';
-
+import TestUtils from 'react-addons-test-utils';
const divStack = [];
+/**
+ * Polyfill for String.includes on some node versions.
+ */
+if (!(String.prototype.hasOwnProperty('includes'))) {
+ String.prototype.includes = function(item) {
+ return this.length > 0 && this.split(" ").indexOf(item) !== -1;
+ };
+}
+
+/**
+ * Check if the document.body contains the react modal
+ * open class.
+ * @return {Boolean}
+ */
+export const isBodyWithReactModalOpenClass = () =>
+ document.body.className.includes('ReactModal__Body--open');
+
+/**
+ * Returns a rendered dom element by class.
+ * @param {React} element A react instance.
+ * @param {String} className A class to find.
+ * @return {DOMElement}
+ */
+export const findDOMWithClass = TestUtils.findRenderedDOMComponentWithClass;
+
+/**
+ * Returns an attribut of a rendered react tree.
+ * @param {React} component A react instance.
+ * @return {String}
+ */
+const getModalAttribute = component => (instance, attr) =>
+ modalComponent(component)(instance).getAttribute(attr);
+
+/**
+ * Return an element from a react component.
+ * @param {React} A react instance.
+ * @return {DOMElement}
+ */
+const modalComponent = component => instance =>
+ instance.portal.refs[component];
+
+/**
+ * Returns the modal content.
+ * @param {Modal} modal Modal instance.
+ * @return {DOMElement}
+ */
+export const mcontent = modalComponent('content');
+
+/**
+ * Returns the modal overlay.
+ * @param {Modal} modal Modal instance.
+ * @return {DOMElement}
+ */
+export const moverlay = modalComponent('overlay');
+
+/**
+ * Return an attribute of modal content.
+ * @param {Modal} modal Modal instance.
+ * @return {String}
+ */
+export const contentAttribute = getModalAttribute('content');
+
+/**
+ * Return an attribute of modal overlay.
+ * @param {Modal} modal Modal instance.
+ * @return {String}
+ */
+export const overlayAttribute = getModalAttribute('overlay');
+
+const Simulate = TestUtils.Simulate;
+
+const dispatchMockEvent = eventCtor => (key, code) => (element, opts) =>
+ eventCtor(element, Object.assign({}, {
+ key: key, keyCode: code, which: code
+ }, opts));
+
+const dispatchMockKeyDownEvent = dispatchMockEvent(Simulate.keyDown);
+
+/**
+ * Dispatch an 'esc' key down event from an element.
+ */
+export const escKeyDown = dispatchMockKeyDownEvent("ESC", 27);
+/**
+ * Dispatch a 'tab' key down event from an element.
+ */
+export const tabKeyDown = dispatchMockKeyDownEvent("TAB", 9);
+/**
+ * Dispatch a 'click' event at a node.
+ */
+export const clickAt = Simulate.click;
+/**
+ * Dispatch a 'mouse up' event at a node.
+ */
+export const mouseUpAt = Simulate.mouseUp;
+/**
+ * Dispatch a 'mouse down' event at a node.
+ */
+export const mouseDownAt = Simulate.mouseDown;
+
export const renderModal = function(props, children, callback) {
props.ariaHideApp = false;
const currentDiv = document.createElement('div');