twreporter-react
Version:
React-Redux site for The Reporter Foundation in Taiwan
455 lines (381 loc) • 15.5 kB
JavaScript
;
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
import { assert, expect } from 'chai';
import sinon from 'sinon';
import createShallowComponent from './utils/createShallowComponent';
import menuFactory from '../lib/menuFactory';
describe('menuFactory', () => {
let Menu, component;
const mockStyles = {
basic: {
menuWrap() { return {}; },
menu() { return {}; },
item() { return {}; },
overlay() { return {}; }
},
full: {
menuWrap() { return {}; },
menu() { return {}; },
item() { return {}; },
overlay() { return {}; },
pageWrap() { return {}; },
outerContainer() { return {}; }
},
withSvg: {
menuWrap() { return {}; },
menu() { return {}; },
item() { return {}; },
overlay() { return {}; },
morphShape() { return {}; },
svg: {}
}
};
const mockStylesProp = {
bmMenuWrap: {
transition: '0.2s'
},
bmMenu: {
background: 'blue'
},
bmMorphShape: {
fill: 'blue'
},
bmItemList: {
color: 'white'
},
bmOverlay: {
background: 'rgba(0, 0, 0, 0.5)'
}
};
function addWrapperElementsToDOM() {
let outerContainer = document.createElement('div');
outerContainer.setAttribute('id', 'outer-container');
let pageWrap = document.createElement('div');
pageWrap.setAttribute('id', 'page-wrap');
outerContainer.appendChild(pageWrap);
document.body.appendChild(outerContainer);
}
function removeWrapperElementsFromDOM() {
let outerContainer = document.getElementById('outer-container');
document.body.removeChild(outerContainer);
}
it('exists and is not undefined', () => {
assert.isDefined(menuFactory, 'menuFactory is defined');
});
it('throws if not passed any styles', () => {
Menu = menuFactory();
assert.throw(createShallowComponent.bind(null, <Menu />), Error, /No styles supplied/);
});
it('warns if external wrapper IDs are required but not passed', () => {
const warn = sinon.stub(console, 'warn');
Menu = menuFactory(mockStyles.full);
component = TestUtils.renderIntoDocument(<Menu />);
assert.ok(warn.calledWith('No pageWrapId supplied'));
assert.ok(warn.calledWith('No outerContainerId supplied'));
console.warn.restore();
});
describe('when rendered successfully', () => {
beforeEach(() => {
Menu = menuFactory(mockStyles.basic);
component = createShallowComponent(<Menu />);
});
it('sets global keydown event handler', () => {
component = TestUtils.renderIntoDocument(<Menu />);
assert.equal(window.onkeydown, component.listenForClose);
});
it('contains an overlay', () => {
const overlay = component.props.children[0];
expect(component.type).to.equal('div');
expect(overlay.props.className).to.contain('bm-overlay');
});
it('contains a menuWrap element with an optional ID', () => {
component = createShallowComponent(<Menu id={ 'menu-wrap' } />);
const menuWrap = component.props.children[1];
expect(component.type).to.equal('div');
expect(menuWrap.props.id).to.equal('menu-wrap');
});
it('contains a burger icon', () => {
component = TestUtils.renderIntoDocument(<Menu />);
const burgerIconBars = TestUtils.scryRenderedDOMComponentsWithClass(component, 'bm-burger-bars');
expect(burgerIconBars).to.have.length(3);
});
it('contains an SVG and morph shape if required', () => {
Menu = menuFactory(mockStyles.withSvg);
component = TestUtils.renderIntoDocument(<Menu />);
const shape = TestUtils.findRenderedDOMComponentWithClass(component, 'bm-morph-shape');
const svg = TestUtils.findRenderedDOMComponentWithTag(component, 'svg');
assert.ok(shape);
assert.ok(svg);
});
it('allows an arbitrary number of children', () => {
component = TestUtils.renderIntoDocument(<Menu />);
expect(React.Children.count(component.props.children)).to.equal(0);
component = TestUtils.renderIntoDocument(<Menu><div>A child</div></Menu>);
expect(React.Children.count(component.props.children)).to.equal(1);
component = TestUtils.renderIntoDocument(<Menu><div>A child</div><div>Another child</div></Menu>);
expect(React.Children.count(component.props.children)).to.equal(2);
});
it('is initially closed', () => {
expect(Menu.prototype.getInitialState().isOpen).to.be.false;
});
it('closes on Escape key press', () => {
component = TestUtils.renderIntoDocument(<Menu />);
component.setState({ isOpen: true });
window.onkeydown({ key: 'Escape' });
expect(component.state.isOpen).to.be.false;
});
it('closes on overlay click', () => {
component = TestUtils.renderIntoDocument(<Menu />);
component.setState({ isOpen: true });
const overlay = TestUtils.findRenderedDOMComponentWithClass(component, 'bm-overlay');
TestUtils.Simulate.click(overlay);
expect(component.state.isOpen).to.be.false;
});
});
describe('when unmounted', () => {
function unmountComponent() {
ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(component).parentNode);
}
beforeEach(() => {
component = TestUtils.renderIntoDocument(<Menu />);
});
it('clears global keydown event handler', () => {
assert.ok(window.onkeydown);
unmountComponent();
assert.notOk(window.onkeydown);
});
it('clears wrapper styles', () => {
const clearWrapperStyles = sinon.spy(component, 'clearWrapperStyles');
unmountComponent();
assert.ok(clearWrapperStyles.called);
component.clearWrapperStyles.restore();
});
});
describe('menuWrap element', () => {
beforeEach(() => {
Menu = menuFactory(mockStyles.basic);
component = TestUtils.renderIntoDocument(<Menu width={ 280 } styles={ mockStylesProp } />);
});
it('allows width to be set by props', () => {
const menuWrap = TestUtils.findRenderedDOMComponentWithClass(component, 'bm-menu-wrap');
expect(menuWrap.style.width).to.equal('280px');
});
it('can be styled with props', () => {
const menuWrap = TestUtils.findRenderedDOMComponentWithClass(component, 'bm-menu-wrap');
expect(menuWrap.style.transition).to.equal('0.2s');
});
it('has the correct number of children', () => {
expect(Object.keys(ReactDOM.findDOMNode(component).children)).to.have.length(3);
});
it('contains menu and item list elements with correct attributes', () => {
const menu = TestUtils.findRenderedDOMComponentWithClass(component, 'bm-menu');
const itemList = TestUtils.findRenderedDOMComponentWithClass(component, 'bm-item-list');
assert.ok(menu);
expect(menu.style.height).to.equal('100%');
assert.ok(itemList);
expect(menu.style.height).to.equal('100%');
});
it('contains a cross icon', () => {
const crossIconBars = TestUtils.scryRenderedDOMComponentsWithClass(component, 'bm-cross');
expect(crossIconBars).to.have.length(2);
});
});
describe('menu element', () => {
it('can be styled with props', () => {
component = TestUtils.renderIntoDocument(<Menu styles={ mockStylesProp } />);
const menu = TestUtils.findRenderedDOMComponentWithClass(component, 'bm-menu');
expect(menu.style.background).to.equal('blue');
});
});
describe('morphShape element', () => {
it('can be styled with props', () => {
Menu = menuFactory(mockStyles.withSvg);
component = TestUtils.renderIntoDocument(<Menu styles={ mockStylesProp } />);
const shape = TestUtils.findRenderedDOMComponentWithClass(component, 'bm-morph-shape');
expect(shape.style.fill).to.equal('blue');
});
});
describe('itemList element', () => {
it('can be styled with props', () => {
component = TestUtils.renderIntoDocument(<Menu styles={ mockStylesProp } />);
const itemList = TestUtils.findRenderedDOMComponentWithClass(component, 'bm-item-list');
expect(itemList.style.color).to.equal('white');
});
});
describe('overlay', () => {
it('can be styled with props', () => {
component = TestUtils.renderIntoDocument(<Menu styles={ mockStylesProp } />);
const overlay = TestUtils.findRenderedDOMComponentWithClass(component, 'bm-overlay');
expect(overlay.style.background).to.equal('rgba(0, 0, 0, 0.5)');
});
});
describe('toggleMenu method', () => {
beforeEach(() => {
Menu = menuFactory(mockStyles.basic);
component = TestUtils.renderIntoDocument(<Menu />);
});
it('updates the isOpen state', () => {
component.setState({ isOpen: false });
component.toggleMenu();
assert.ok(component.state.isOpen);
});
it('calls applyWrapperStyles', () => {
const applyWrapperStyles = sinon.spy(component, 'applyWrapperStyles');
component.toggleMenu();
assert.ok(applyWrapperStyles.called, 'applyWrapperStyles called');
component.applyWrapperStyles.restore();
});
it('calls onStateChange callback', () => {
const callback = sinon.spy();
component = TestUtils.renderIntoDocument(<Menu onStateChange={ callback } />);
component.toggleMenu();
assert.ok(callback.called, 'onStateChange callback called');
});
it('calls onStateChange callback with current menu state', () => {
const callback = sinon.spy();
component = TestUtils.renderIntoDocument(<Menu onStateChange={ callback } />);
component.setState({ isOpen: false });
component.toggleMenu();
assert.ok(callback.calledWith({ isOpen: true }));
});
});
describe('applyWrapperStyles method', () => {
beforeEach(() => {
Menu = menuFactory(mockStyles.full);
component = TestUtils.renderIntoDocument(<Menu pageWrapId={ 'page-wrap' } outerContainerId={ 'outer-container' } />);
addWrapperElementsToDOM();
});
afterEach(() => {
removeWrapperElementsFromDOM();
});
it('calls handleExternalWrapper with the correct params', () => {
const handleExternalWrapper = sinon.spy(component, 'handleExternalWrapper');
component.applyWrapperStyles();
assert.ok(handleExternalWrapper.calledWithExactly('page-wrap', mockStyles.full.pageWrap, true));
assert.ok(handleExternalWrapper.calledWithExactly('outer-container', mockStyles.full.outerContainer, true));
component.handleExternalWrapper.restore();
});
});
describe('clearWrapperStyles method', () => {
beforeEach(() => {
Menu = menuFactory(mockStyles.full);
component = TestUtils.renderIntoDocument(<Menu pageWrapId={ 'page-wrap' } outerContainerId={ 'outer-container' } />);
addWrapperElementsToDOM();
});
afterEach(() => {
removeWrapperElementsFromDOM();
});
it('calls handleExternalWrapper with the correct params', () => {
const handleExternalWrapper = sinon.spy(component, 'handleExternalWrapper');
component.clearWrapperStyles();
assert.ok(handleExternalWrapper.calledWithExactly('page-wrap', mockStyles.full.pageWrap, false));
assert.ok(handleExternalWrapper.calledWithExactly('outer-container', mockStyles.full.outerContainer, false));
component.handleExternalWrapper.restore();
});
});
describe('handleExternalWrapper method', () => {
const styles = () => {
return { color: 'red', position: 'relative' };
};
beforeEach(() => {
Menu = menuFactory(mockStyles.full);
component = TestUtils.renderIntoDocument(<Menu pageWrapId={ 'page-wrap' } outerContainerId={ 'outer-container' } />);
});
it('errors with the correct message if no wrapper element found', () => {
const error = sinon.stub(console, 'error');
component.handleExternalWrapper('page-wrap', mockStyles.full.pageWrap, true);
assert.ok(error.calledWith("Element with ID 'page-wrap' not found"));
console.error.restore();
});
it('sets styles on external wrapper elements', () => {
addWrapperElementsToDOM();
component.handleExternalWrapper('page-wrap', styles, true);
let wrapperElement = document.getElementById('page-wrap');
expect(wrapperElement.style.color).to.equal('red');
expect(wrapperElement.style.position).to.equal('relative');
removeWrapperElementsFromDOM();
});
it('clears styles from external wrapper elements', () => {
addWrapperElementsToDOM();
let wrapperElement = document.getElementById('page-wrap');
wrapperElement.style.color = 'red';
wrapperElement.style.position = 'relative';
component.handleExternalWrapper('page-wrap', styles, false);
expect(wrapperElement.style.color).to.be.empty;
expect(wrapperElement.style.position).to.be.empty;
removeWrapperElementsFromDOM();
});
it('sets styles on html and body elements', () => {
addWrapperElementsToDOM();
let html = document.querySelector('html');
let body = document.querySelector('body');
component.handleExternalWrapper('page-wrap', styles, true);
expect(html.style['overflow-x']).to.equal('hidden');
expect(body.style['overflow-x']).to.equal('hidden');
removeWrapperElementsFromDOM();
});
it('clears styles from html and body elements', () => {
addWrapperElementsToDOM();
let html = document.querySelector('html');
let body = document.querySelector('body');
component.handleExternalWrapper('page-wrap', styles, false);
expect(html.style['overflow-x']).to.be.empty;
expect(body.style['overflow-x']).to.be.empty;
removeWrapperElementsFromDOM();
});
});
describe('listenForClose method', () => {
beforeEach(() => {
addWrapperElementsToDOM();
component.setState({ isOpen: true });
});
afterEach(() => {
removeWrapperElementsFromDOM();
});
it('closes the menu when escape is pressed', () => {
component.listenForClose({ key: 'Escape', target: '' });
expect(component.state.isOpen).to.be.false;
});
});
describe('open state', () => {
beforeEach(() => {
Menu = menuFactory(mockStyles.basic);
});
it('can be set externally', () => {
component = TestUtils.renderIntoDocument(<Menu isOpen />);
expect(component.state.isOpen).to.be.true;
});
it('can be controlled externally', () => {
let container = document.createElement('div');
component = ReactDOM.render(<Menu />, container);
expect(component.state.isOpen).to.be.false;
ReactDOM.render(<Menu isOpen />, container);
expect(component.state.isOpen).to.be.true;
});
it('will not change when isOpen prop was not changed', () => {
let container = document.createElement('div');
let ParentComponent = React.createClass({
getInitialState() {
return { collapsed: true };
},
changeProps() {
this.setState({
collapsed: !this.state.collapsed
});
},
render() {
return (
<Menu ref="menu" />
);
}
});
component = ReactDOM.render(<ParentComponent />, container);
const menu = component.refs.menu;
menu.setState({isOpen: true});
component.changeProps();
expect(menu.state.isOpen).to.be.true;
});
});
});