materialuiupgraded
Version:
Material-UI's workspace package
640 lines (546 loc) • 21.4 kB
JavaScript
import React from 'react';
import ReactDOM from 'react-dom';
import keycode from 'keycode';
import { assert } from 'chai';
import PropTypes from 'prop-types';
import { spy, useFakeTimers } from 'sinon';
import rerender from 'test/utils/rerender';
import { createShallow, createMount, getClasses, unwrap } from '../test-utils';
import TouchRipple from './TouchRipple';
import ButtonBase from './ButtonBase';
const ButtonBaseNaked = unwrap(ButtonBase);
describe('<ButtonBase />', () => {
let mount;
let shallow;
let classes;
before(() => {
shallow = createShallow({ dive: true, disableLifecycleMethods: true });
mount = createMount();
classes = getClasses(<ButtonBase />);
});
after(() => {
mount.cleanUp();
});
describe('root node', () => {
it('should render a button with type="button" by default', () => {
const wrapper = shallow(<ButtonBase>Hello</ButtonBase>);
assert.strictEqual(wrapper.name(), 'button');
assert.strictEqual(wrapper.childAt(0).equals('Hello'), true);
assert.strictEqual(wrapper.props().type, 'button');
});
it('should change the button type', () => {
const wrapper = shallow(<ButtonBase type="submit">Hello</ButtonBase>);
assert.strictEqual(wrapper.name(), 'button');
assert.strictEqual(wrapper.props().type, 'submit');
});
it('should change the button component and add accessibility requirements', () => {
const wrapper = shallow(<ButtonBase component="span" role="checkbox" aria-checked={false} />);
assert.strictEqual(wrapper.name(), 'span');
assert.strictEqual(wrapper.props().role, 'checkbox');
assert.strictEqual(wrapper.props().tabIndex, '0');
});
it('should spread props on button', () => {
const wrapper = shallow(<ButtonBase data-test="hello">Hello</ButtonBase>);
assert.strictEqual(wrapper.props()['data-test'], 'hello');
});
it('should render the custom className and the root class', () => {
const wrapper = shallow(<ButtonBase className="test-class-name" />);
assert.strictEqual(
wrapper.hasClass('test-class-name'),
true,
'should pass the test className',
);
assert.strictEqual(wrapper.hasClass(classes.root), true);
});
it('should not apply role="button" if type="button"', () => {
const wrapper = shallow(<ButtonBase>Hello</ButtonBase>);
assert.strictEqual(wrapper.name(), 'button');
assert.strictEqual(wrapper.props().type, 'button');
assert.strictEqual(wrapper.props().role, undefined);
});
it('should change the button type to span and set role="button"', () => {
const wrapper = shallow(<ButtonBase component="span">Hello</ButtonBase>);
assert.strictEqual(wrapper.name(), 'span');
assert.strictEqual(wrapper.props().type, undefined);
assert.strictEqual(wrapper.props().role, 'button');
});
it('should automatically change the button to an a element when href is provided', () => {
const wrapper = shallow(<ButtonBase href="http://google.com">Hello</ButtonBase>);
assert.strictEqual(wrapper.name(), 'a');
assert.strictEqual(wrapper.props().href, 'http://google.com');
});
it('should change the button type to a and set role="button"', () => {
const wrapper = shallow(<ButtonBase component="a">Hello</ButtonBase>);
assert.strictEqual(wrapper.name(), 'a');
assert.strictEqual(wrapper.props().type, undefined);
assert.strictEqual(wrapper.props().role, 'button');
});
it('should not change the button to an a element', () => {
const wrapper = shallow(
<ButtonBase component="span" href="http://google.com">
Hello
</ButtonBase>,
);
assert.strictEqual(wrapper.name(), 'span');
assert.strictEqual(wrapper.props().href, 'http://google.com');
});
});
describe('event callbacks', () => {
it('should fire event callbacks', () => {
const events = [
'onClick',
'onFocus',
'onBlur',
'onKeyUp',
'onKeyDown',
'onMouseDown',
'onMouseLeave',
'onMouseUp',
'onTouchEnd',
'onTouchStart',
];
const handlers = events.reduce((result, n) => {
result[n] = spy();
return result;
}, {});
const wrapper = shallow(<ButtonBase {...handlers} />);
events.forEach(n => {
const event = n.charAt(2).toLowerCase() + n.slice(3);
wrapper.simulate(event, { persist: () => {} });
assert.strictEqual(handlers[n].callCount, 1, `should have called the ${n} handler`);
});
});
});
describe('ripple', () => {
let wrapper;
before(() => {
wrapper = shallow(<ButtonBase>Hello</ButtonBase>);
});
it('should be enabled by default', () => {
const ripple = wrapper.find(TouchRipple);
assert.strictEqual(ripple.length, 1);
});
it('should not have a focus ripple by default', () => {
wrapper.instance().ripple = { pulsate: spy() };
wrapper.setState({ focusVisible: true });
assert.strictEqual(
wrapper.instance().ripple.pulsate.callCount,
0,
'should not call pulsate on the ripple',
);
});
it('should start the ripple when the mouse is pressed', () => {
wrapper.instance().ripple = { start: spy() };
wrapper.simulate('mouseDown', {});
assert.strictEqual(wrapper.instance().ripple.start.callCount, 1);
});
it('should stop the ripple when the mouse is released', () => {
wrapper.instance().ripple = { stop: spy() };
wrapper.simulate('mouseUp', {});
assert.strictEqual(wrapper.instance().ripple.stop.callCount, 1);
});
it('should start the ripple when the mouse is pressed', () => {
wrapper.instance().ripple = { start: spy() };
wrapper.simulate('mouseDown', {});
assert.strictEqual(wrapper.instance().ripple.start.callCount, 1);
});
it('should stop the ripple when the button blurs', () => {
wrapper.instance().ripple = { stop: spy() };
wrapper.simulate('blur', {});
assert.strictEqual(wrapper.instance().ripple.stop.callCount, 1);
});
it('should start the ripple when the mouse is pressed', () => {
wrapper.instance().ripple = { start: spy() };
wrapper.simulate('mouseDown', {});
assert.strictEqual(wrapper.instance().ripple.start.callCount, 1);
});
it('should stop the ripple when the mouse leaves', () => {
wrapper.instance().ripple = { stop: spy() };
wrapper.simulate('mouseLeave', {});
assert.strictEqual(wrapper.instance().ripple.stop.callCount, 1);
});
it('should center the ripple', () => {
assert.strictEqual(wrapper.find(TouchRipple).props().center, false);
wrapper.setProps({ centerRipple: true });
assert.strictEqual(wrapper.find(TouchRipple).props().center, true);
});
});
describe('focusRipple', () => {
let wrapper;
before(() => {
wrapper = mount(
<ButtonBaseNaked theme={{}} classes={{}} focusRipple>
Hello
</ButtonBaseNaked>,
);
});
it('should be enabled by default', () => {
const ripple = wrapper.find(TouchRipple);
assert.strictEqual(ripple.length, 1);
});
it('should pulsate the ripple when focusVisible', () => {
wrapper.instance().ripple = { pulsate: spy() };
wrapper.setState({ focusVisible: true });
assert.strictEqual(wrapper.instance().ripple.pulsate.callCount, 1);
});
it('should not stop the ripple when the mouse leaves', () => {
wrapper.instance().ripple = { stop: spy() };
wrapper.simulate('mouseLeave', {
defaultPrevented: false,
preventDefault() {
this.defaultPrevented = true;
},
});
assert.strictEqual(wrapper.instance().ripple.stop.callCount, 0);
});
it('should stop pulsate and start a ripple when the space button is pressed', () => {
wrapper.instance().ripple = { stop: spy((event, cb) => cb()), start: spy() };
wrapper.simulate('keyDown', { which: 32, keyCode: 32, key: ' ', persist: () => {} });
assert.strictEqual(wrapper.instance().ripple.stop.callCount, 1);
assert.strictEqual(wrapper.instance().ripple.start.callCount, 1);
});
it('should stop and re-pulsate when space bar is released', () => {
wrapper.instance().ripple = { stop: spy((event, cb) => cb()), pulsate: spy() };
wrapper.simulate('keyUp', { which: 32, keyCode: 32, key: ' ', persist: () => {} });
assert.strictEqual(wrapper.instance().ripple.stop.callCount, 1);
assert.strictEqual(wrapper.instance().ripple.pulsate.callCount, 1);
});
it('should stop on blur and set focusVisible to false', () => {
wrapper.instance().ripple = { stop: spy() };
wrapper.simulate('blur', {});
assert.strictEqual(wrapper.instance().ripple.stop.callCount, 1);
assert.strictEqual(wrapper.state().focusVisible, false);
});
});
describe('mounted tab press listener', () => {
let wrapper;
let instance;
let button;
let clock;
beforeEach(() => {
clock = useFakeTimers();
wrapper = mount(
<ButtonBaseNaked theme={{}} classes={{}} id="test-button">
Hello
</ButtonBaseNaked>,
);
instance = wrapper.instance();
button = document.getElementById('test-button');
if (!button) {
throw new Error('missing button');
}
button.focus();
const event = new window.Event('keyup');
event.which = keycode('tab');
window.dispatchEvent(event);
});
afterEach(() => {
clock.restore();
});
it('should detect the keyboard', () => {
assert.strictEqual(wrapper.state().focusVisible, false);
clock.tick(instance.focusVisibleCheckTime * instance.focusVisibleMaxCheckTimes);
assert.strictEqual(wrapper.state().focusVisible, true);
});
it('should ignore the keyboard after 1s', () => {
clock.tick(instance.focusVisibleCheckTime * instance.focusVisibleMaxCheckTimes);
assert.strictEqual(wrapper.state().focusVisible, true);
button.blur();
assert.strictEqual(wrapper.state().focusVisible, false);
button.focus();
clock.tick(instance.focusVisibleCheckTime * instance.focusVisibleMaxCheckTimes);
assert.strictEqual(wrapper.state().focusVisible, true);
clock.tick(1e3);
button.blur();
assert.strictEqual(wrapper.state().focusVisible, false);
button.focus();
clock.tick(instance.focusVisibleCheckTime * instance.focusVisibleMaxCheckTimes);
assert.strictEqual(wrapper.state().focusVisible, false);
});
});
describe('prop: disabled', () => {
it('should not receive the focus', () => {
const wrapper = shallow(<ButtonBase disabled>Hello</ButtonBase>);
assert.strictEqual(wrapper.props().tabIndex, '-1');
});
it('should also apply it when using component', () => {
const wrapper = shallow(
<ButtonBase disabled component="button">
Hello
</ButtonBase>,
);
assert.strictEqual(wrapper.find('button').props().disabled, true);
});
it('should reset the focused state', () => {
const wrapper = shallow(<ButtonBase>Hello</ButtonBase>);
// We simulate a focusVisible button that is getting disabled.
wrapper.setState({
focusVisible: true,
});
wrapper.setProps({
disabled: true,
});
assert.strictEqual(wrapper.state().focusVisible, false);
});
it('should not apply disabled on a span', () => {
const wrapper = shallow(
<ButtonBase component="span" disabled>
Hello
</ButtonBase>,
);
assert.strictEqual(wrapper.props().disabled, undefined);
});
});
describe('prop: component', () => {
it('should allow to use a link component', () => {
const Link = props => <div {...props} />;
const wrapper = shallow(<ButtonBase component={Link}>Hello</ButtonBase>);
assert.strictEqual(wrapper.is(Link), true);
});
});
describe('handleFocus()', () => {
let clock;
before(() => {
clock = useFakeTimers();
});
after(() => {
clock.restore();
});
it('when disabled should not persist event', () => {
const wrapper = mount(
<ButtonBaseNaked theme={{}} classes={{}} disabled>
Hello
</ButtonBaseNaked>,
);
const instance = wrapper.instance();
const eventMock = { persist: spy() };
instance.handleFocus(eventMock);
assert.strictEqual(eventMock.persist.callCount, 0);
});
it('onFocusVisibleHandler() should propogate call to onFocusVisible prop', () => {
const eventMock = 'woofButtonBase';
const onFocusVisibleSpy = spy();
const wrapper = mount(
<ButtonBaseNaked
theme={{}}
classes={{}}
component="span"
onFocusVisible={onFocusVisibleSpy}
>
Hello
</ButtonBaseNaked>,
);
const instance = wrapper.instance();
instance.onFocusVisibleHandler(eventMock);
assert.strictEqual(onFocusVisibleSpy.callCount, 1);
assert.strictEqual(onFocusVisibleSpy.calledWith(eventMock), true);
});
it('should work with a functionnal component', () => {
const MyLink = props => (
<a href="/foo" {...props}>
bar
</a>
);
const wrapper = mount(
<ButtonBaseNaked theme={{}} classes={{}} component={MyLink}>
Hello
</ButtonBaseNaked>,
);
const instance = wrapper.instance();
wrapper.simulate('focus');
clock.tick(instance.focusVisibleCheckTime);
});
});
describe('handleKeyDown()', () => {
let wrapper;
let instance;
let event;
describe('avoids multiple keydown presses', () => {
it('should work', () => {
wrapper = mount(
<ButtonBaseNaked theme={{}} classes={{}} focusRipple>
Hello
</ButtonBaseNaked>,
);
wrapper.setState({ focusVisible: true });
const eventPersistSpy = spy();
event = { persist: eventPersistSpy, keyCode: keycode('space') };
instance = wrapper.instance();
instance.keyDown = false;
instance.ripple = { stop: spy() };
instance.handleKeyDown(event);
assert.strictEqual(instance.keyDown, true);
assert.strictEqual(event.persist.callCount, 1);
assert.strictEqual(instance.ripple.stop.callCount, 1);
assert.strictEqual(instance.ripple.stop.calledWith(event), true);
});
});
describe('prop: onKeyDown', () => {
it('should work', () => {
const onKeyDownSpy = spy();
wrapper = mount(
<ButtonBaseNaked theme={{}} classes={{}} onKeyDown={onKeyDownSpy}>
Hello
</ButtonBaseNaked>,
);
const eventPersistSpy = spy();
event = { persist: eventPersistSpy, keyCode: undefined };
instance = wrapper.instance();
instance.keyDown = false;
instance.handleKeyDown(event);
assert.strictEqual(instance.keyDown, false);
assert.strictEqual(event.persist.callCount, 0);
assert.strictEqual(onKeyDownSpy.callCount, 1);
assert.strictEqual(onKeyDownSpy.calledWith(event), true);
});
});
describe('keyboard accessibility for non interactive elements', () => {
it('should work', () => {
const onClickSpy = spy();
wrapper = mount(
<ButtonBaseNaked theme={{}} classes={{}} onClick={onClickSpy} component="div">
Hello
</ButtonBaseNaked>,
);
event = {
preventDefault: spy(),
keyCode: keycode('space'),
target: 'target',
currentTarget: 'target',
};
instance = wrapper.instance();
instance.keyDown = false;
instance.handleKeyDown(event);
assert.strictEqual(instance.keyDown, false);
assert.strictEqual(event.preventDefault.callCount, 1);
assert.strictEqual(onClickSpy.callCount, 1);
assert.strictEqual(onClickSpy.calledWith(event), true);
});
it('should hanlde the link with no href', () => {
const onClickSpy = spy();
wrapper = mount(
<ButtonBaseNaked theme={{}} classes={{}} component="a" onClick={onClickSpy}>
Hello
</ButtonBaseNaked>,
);
event = {
preventDefault: spy(),
keyCode: keycode('enter'),
target: 'target',
currentTarget: 'target',
};
instance = wrapper.instance();
instance.handleKeyDown(event);
assert.strictEqual(event.preventDefault.callCount, 1);
assert.strictEqual(onClickSpy.callCount, 1);
});
it('should ignore the link with href', () => {
const onClickSpy = spy();
wrapper = mount(
<ButtonBaseNaked theme={{}} classes={{}} component="a" href="href" onClick={onClickSpy}>
Hello
</ButtonBaseNaked>,
);
event = {
preventDefault: spy(),
keyCode: keycode('enter'),
target: 'target',
currentTarget: 'target',
};
instance = wrapper.instance();
instance.handleKeyDown(event);
assert.strictEqual(event.preventDefault.callCount, 0);
assert.strictEqual(onClickSpy.callCount, 0);
});
});
describe('prop: disableTouchRipple', () => {
it('should work', () => {
wrapper = mount(
<ButtonBaseNaked theme={{}} classes={{}} disableTouchRipple>
Hello
</ButtonBaseNaked>,
);
assert.strictEqual(wrapper.find(TouchRipple).length, 1);
wrapper.instance().ripple = { start: spy(), stop: spy() };
wrapper.simulate('mouseDown', {});
assert.strictEqual(wrapper.instance().ripple.start.callCount, 0);
wrapper.simulate('mouseUp', {});
assert.strictEqual(wrapper.instance().ripple.stop.callCount, 0);
});
});
describe('prop: disableRipple', () => {
it('should work', () => {
wrapper = mount(
<ButtonBaseNaked theme={{}} classes={{}}>
Hello
</ButtonBaseNaked>,
);
assert.strictEqual(wrapper.find(TouchRipple).length, 1);
const onKeyDownSpy = spy();
wrapper.setProps({ onKeyDown: onKeyDownSpy, disableRipple: true, focusRipple: true });
wrapper.setState({ focusVisible: true });
assert.strictEqual(wrapper.find(TouchRipple).length, 0);
const eventPersistSpy = spy();
event = { persist: eventPersistSpy, keyCode: keycode('space') };
instance = wrapper.instance();
instance.keyDown = false;
instance.handleKeyDown(event);
assert.strictEqual(instance.keyDown, false);
assert.strictEqual(event.persist.callCount, 0);
assert.strictEqual(onKeyDownSpy.callCount, 1);
assert.strictEqual(onKeyDownSpy.calledWith(event), true);
});
});
});
describe('prop: ref', () => {
it('should be able to get a ref of the root element', () => {
function ButtonBaseRef(props) {
return <ButtonBase ref={props.rootRef} />;
}
ButtonBaseRef.propTypes = {
rootRef: PropTypes.func.isRequired,
};
const ref = spy();
mount(<ButtonBaseRef rootRef={ref}>Hello</ButtonBaseRef>);
assert.strictEqual(ref.callCount, 1);
assert.strictEqual(ReactDOM.findDOMNode(ref.args[0][0]).type, 'button');
});
});
describe('prop: action', () => {
it('should be able to focus visible the button', () => {
let buttonActions = {};
const wrapper = mount(
<ButtonBaseNaked
theme={{}}
classes={{}}
action={actions => {
buttonActions = actions;
}}
focusVisibleClassName="focusVisible"
>
Hello
</ButtonBaseNaked>,
);
assert.strictEqual(typeof buttonActions.focusVisible, 'function');
buttonActions.focusVisible();
wrapper.update();
assert.strictEqual(wrapper.instance().button, document.activeElement);
assert.strictEqual(wrapper.find('.focusVisible').length, 1);
});
});
describe('rerender', () => {
beforeEach(() => {
rerender.spy();
});
afterEach(() => {
rerender.reset();
});
it('should not rerender the TouchRipple', () => {
const wrapper = mount(<ButtonBase>foo</ButtonBase>);
wrapper.setProps({
children: 'bar',
});
assert.strictEqual(rerender.updates.length, 1);
});
});
});