materialuiupgraded
Version:
Material-UI's workspace package
626 lines (567 loc) • 18.2 kB
JavaScript
import React from 'react';
import { assert } from 'chai';
import { spy, stub, useFakeTimers } from 'sinon';
import { ShallowWrapper } from 'enzyme';
import consoleErrorMock from 'test/utils/consoleErrorMock';
import { createShallow, createMount, getClasses, unwrap } from '../test-utils';
import Tab from '../Tab';
import Tabs from './Tabs';
import TabScrollButton from './TabScrollButton';
import TabIndicator from './TabIndicator';
describe('<Tabs />', () => {
let mount;
let shallow;
let classes;
const TabsNaked = unwrap(Tabs);
const noop = () => {};
const fakeTabs = {
getBoundingClientRect: () => ({}),
children: [
{
children: [
{
getBoundingClientRect: () => ({}),
},
],
},
],
};
before(() => {
shallow = createShallow({ untilSelector: 'Tabs', disableLifecycleMethods: true });
classes = getClasses(<Tabs onChange={noop} value={0} />);
mount = createMount();
});
after(() => {
mount.cleanUp();
});
it('should render with the root class', () => {
const wrapper = shallow(
<Tabs width="md" onChange={noop} value={0}>
<Tab />
<Tab />
</Tabs>,
);
assert.strictEqual(wrapper.name(), 'div');
assert.strictEqual(wrapper.hasClass(classes.root), true);
});
describe('warning', () => {
before(() => {
consoleErrorMock.spy();
});
after(() => {
consoleErrorMock.reset();
});
it('should warn if the input is invalid', () => {
shallow(<Tabs onChange={noop} value={0} centered scrollable />);
assert.match(
consoleErrorMock.args()[0][0],
/Material-UI: you can not use the `centered={true}` and `scrollable={true}`/,
);
});
});
describe('prop: action', () => {
it('should be able to access updateIndicator function', () => {
let tabsActions = {};
mount(
<Tabs
width="md"
onChange={noop}
value={0}
className="woofTabs"
action={actions => {
tabsActions = actions;
}}
>
<Tab />
<Tab />
</Tabs>,
);
assert.strictEqual(
typeof tabsActions.updateIndicator === 'function',
true,
'Should be a function.',
);
tabsActions.updateIndicator();
});
});
describe('prop: className', () => {
it('should render with the user and root classes', () => {
const wrapper = shallow(
<Tabs width="md" onChange={noop} value={0} className="woofTabs">
<Tab />
<Tab />
</Tabs>,
);
assert.strictEqual(wrapper.hasClass('woofTabs'), true);
assert.strictEqual(wrapper.hasClass(classes.root), true);
});
});
describe('prop: centered', () => {
it('should render with the centered class', () => {
const wrapper = shallow(
<Tabs width="md" onChange={noop} value={0} centered>
<Tab />
<Tab />
</Tabs>,
);
const selector = `.${classes.flexContainer}.${classes.centered}`;
assert.strictEqual(wrapper.find(selector).name(), 'div');
assert.strictEqual(wrapper.find(selector).length, 1);
});
});
describe('prop: children', () => {
it('should accept an invalid child', () => {
const wrapper = shallow(
<Tabs width="md" onChange={noop} value={0}>
{null}
<Tab />
</Tabs>,
);
assert.strictEqual(wrapper.find(Tab).length, 1);
});
it('should support empty children', () => {
const wrapper = mount(<Tabs width="md" onChange={noop} value={1} />);
assert.strictEqual(wrapper.find('EventListener').length, 1);
});
});
describe('prop: value', () => {
let wrapper;
before(() => {
wrapper = shallow(
<Tabs width="md" onChange={noop} value={1}>
<Tab />
<Tab />
</Tabs>,
);
});
after(() => {
consoleErrorMock.reset();
});
it('should pass selected prop to children', () => {
assert.strictEqual(
wrapper
.find(Tab)
.at(0)
.props().selected,
false,
'should have selected to false',
);
assert.strictEqual(
wrapper
.find(Tab)
.at(1)
.props().selected,
true,
'should have selected',
);
});
it('should switch from the original value', () => {
wrapper.setProps({ value: 0 });
assert.strictEqual(
wrapper
.find(Tab)
.at(0)
.props().selected,
true,
'should have switched to true',
);
assert.strictEqual(
wrapper
.find(Tab)
.at(1)
.props().selected,
false,
'should have switched to false',
);
});
describe('indicator', () => {
it('should accept a false value', () => {
const wrapper2 = mount(
<Tabs width="md" onChange={noop} value={false}>
<Tab />
<Tab />
</Tabs>,
);
assert.strictEqual(wrapper2.find(TabIndicator).props().style.width, 0);
});
it('should work server-side', () => {
const wrapper2 = shallow(
<Tabs width="md" onChange={noop} value={1}>
<Tab />
<Tab />
</Tabs>,
{ disableLifecycleMethods: true },
);
const indicator = new ShallowWrapper(
wrapper2
.find(Tab)
.at(1)
.props().indicator,
wrapper2,
);
assert.deepEqual(indicator.props().style, {});
});
it('should let the selected <Tab /> render the indicator', () => {
const wrapper2 = shallow(
<Tabs width="md" onChange={noop} value={1}>
<Tab />
<Tab />
</Tabs>,
{ disableLifecycleMethods: true },
);
assert.strictEqual(
wrapper2
.find(Tab)
.at(0)
.props().indicator,
false,
);
assert.strictEqual(
wrapper2
.find(Tab)
.at(1)
.props().indicator.type,
TabIndicator,
);
});
it('should accept any value as selected tab value', () => {
const tab0 = {};
const tab1 = {};
assert.notStrictEqual(tab0, tab1);
const wrapper2 = shallow(
<Tabs width="md" onChange={noop} value={tab0}>
<Tab value={tab0} />
<Tab value={tab1} />
</Tabs>,
);
assert.strictEqual(wrapper2.instance().valueToIndex.size, 2);
});
it('should render the indicator', () => {
const wrapper2 = mount(
<Tabs width="md" onChange={noop} value={1}>
<Tab />
<Tab />
</Tabs>,
);
assert.strictEqual(
wrapper2
.find(Tab)
.at(0)
.props().indicator,
false,
);
assert.strictEqual(
wrapper2
.find(Tab)
.at(1)
.props().indicator,
false,
);
assert.strictEqual(wrapper2.find(TabIndicator).length, 1);
});
it('should update the indicator state no matter what', () => {
const wrapper2 = mount(
<TabsNaked width="md" onChange={noop} value={1} classes={{}} theme={{}}>
<Tab />
<Tab />
</TabsNaked>,
);
const instance = wrapper2.instance();
stub(instance, 'scrollSelectedIntoView');
wrapper2.setState({
indicatorStyle: {
left: 10,
width: 40,
},
});
wrapper2.setProps({
value: 0,
});
assert.strictEqual(
instance.scrollSelectedIntoView.callCount >= 2,
true,
'should have called scrollSelectedIntoView',
);
});
});
it('should warn when the value is invalid', () => {
consoleErrorMock.spy();
mount(
<Tabs width="md" onChange={noop} value={2}>
<Tab />
<Tab />
</Tabs>,
);
assert.strictEqual(consoleErrorMock.callCount(), 3);
assert.strictEqual(
consoleErrorMock.args()[0][0],
'Warning: Material-UI: the value provided `2` is invalid',
);
});
});
describe('prop: onChange', () => {
it('should call onChange when clicking', () => {
const handleChange = spy();
// use mount to ensure that click event on Tab can be fired
const wrapper = mount(
<Tabs width="md" value={0} onChange={handleChange}>
<Tab />
<Tab />
</Tabs>,
);
wrapper
.find(Tab)
.at(1)
.simulate('click');
wrapper.setProps({ value: 1 });
assert.strictEqual(handleChange.callCount, 1, 'should have been called once');
assert.strictEqual(handleChange.args[0][1], 1, 'should have been called with value 1');
wrapper.unmount();
});
});
describe('prop: scrollable', () => {
let clock;
let wrapper;
before(() => {
clock = useFakeTimers();
wrapper = shallow(
<Tabs width="md" onChange={noop} value={0} scrollable>
<Tab />
<Tab />
</Tabs>,
);
});
after(() => {
clock.restore();
});
it('should render with the scrollable class', () => {
const selector = `.${classes.scroller}.${classes.scrollable}`;
assert.strictEqual(wrapper.find(selector).name(), 'div');
assert.strictEqual(wrapper.find(selector).length, 1);
});
it('should response to scroll events', () => {
const instance = wrapper.instance();
instance.tabsRef = { scrollLeft: 100, ...fakeTabs };
spy(instance, 'updateScrollButtonState');
const selector = `.${classes.scroller}.${classes.scrollable}`;
wrapper.find(selector).simulate('scroll');
clock.tick(166);
assert.strictEqual(
instance.updateScrollButtonState.called,
true,
'should have called updateScrollButtonState',
);
});
it('should get a scrollbar size listener', () => {
// use mount to ensure that handleScrollbarSizeChange gets covered
const mountWrapper = mount(
<Tabs width="md" onChange={noop} value={0} scrollable>
<Tab />
<Tab />
</Tabs>,
);
assert.strictEqual(mountWrapper.find('ScrollbarSize').length, 1);
mountWrapper.unmount();
});
});
describe('prop: !scrollable', () => {
it('should not render with the scrollable class', () => {
const wrapper = shallow(
<Tabs width="md" onChange={noop} value={0}>
<Tab />
<Tab />
</Tabs>,
);
const baseSelector = `.${classes.scroller}`;
const selector = `.${classes.scroller}.${classes.scrollable}`;
assert.strictEqual(wrapper.find(baseSelector).length, 1);
assert.strictEqual(wrapper.find(selector).length, 0);
});
});
describe('prop: scrollButtons', () => {
let clock;
before(() => {
clock = useFakeTimers();
});
after(() => {
clock.restore();
});
it('should render scroll buttons', () => {
const wrapper = shallow(
<Tabs width="md" onChange={noop} value={0} scrollable scrollButtons="on">
<Tab />
<Tab />
</Tabs>,
);
assert.strictEqual(wrapper.find(TabScrollButton).length, 2, 'should be two');
});
it('should render scroll buttons automatically', () => {
const wrapper = shallow(
<Tabs width="md" onChange={noop} value={0} scrollable scrollButtons="auto">
<Tab />
<Tab />
</Tabs>,
);
assert.strictEqual(wrapper.find(TabScrollButton).length, 2, 'should be two');
});
it('should should not render scroll buttons automatically', () => {
const wrapper = shallow(
<Tabs width="sm" onChange={noop} value={0} scrollable scrollButtons="auto">
<Tab />
<Tab />
</Tabs>,
);
assert.strictEqual(wrapper.find(TabScrollButton).length, 2, 'should be zero');
assert.strictEqual(
wrapper.find(TabScrollButton).everyWhere(node => node.hasClass(classes.scrollButtonsAuto)),
true,
);
});
it('should handle window resize event', () => {
const wrapper = shallow(
<Tabs width="md" onChange={noop} value={0} scrollable scrollButtons="on">
<Tab />
<Tab />
</Tabs>,
);
const instance = wrapper.instance();
stub(instance, 'updateScrollButtonState');
stub(instance, 'updateIndicatorState');
wrapper
.find('EventListener')
.at(0)
.simulate('resize');
clock.tick(166);
assert.strictEqual(instance.updateScrollButtonState.called, true);
assert.strictEqual(instance.updateIndicatorState.called, true);
});
describe('scroll button visibility states', () => {
let wrapper;
let instance;
before(() => {
wrapper = shallow(
<Tabs width="md" onChange={noop} value={0} scrollable scrollButtons="on">
<Tab />
<Tab />
</Tabs>,
);
instance = wrapper.instance();
});
it('should set neither left nor right scroll button state', () => {
instance.tabsRef = { scrollLeft: 0, scrollWidth: 90, clientWidth: 100, ...fakeTabs };
instance.updateScrollButtonState();
assert.strictEqual(wrapper.state().showLeftScroll, false);
assert.strictEqual(wrapper.state().showRightScroll, false);
});
it('should set only left scroll button state', () => {
instance.tabsRef = { scrollLeft: 1, ...fakeTabs };
instance.updateScrollButtonState();
assert.strictEqual(wrapper.state().showLeftScroll, true);
assert.strictEqual(wrapper.state().showRightScroll, false);
});
it('should set only right scroll button state', () => {
instance.tabsRef = { scrollLeft: 0, scrollWidth: 110, clientWidth: 100, ...fakeTabs };
instance.updateScrollButtonState();
assert.strictEqual(wrapper.state().showLeftScroll, false);
assert.strictEqual(wrapper.state().showRightScroll, true);
});
it('should set both left and right scroll button state', () => {
instance.tabsRef = { scrollLeft: 1, scrollWidth: 110, clientWidth: 100, ...fakeTabs };
instance.updateScrollButtonState();
assert.strictEqual(wrapper.state().showLeftScroll, true);
assert.strictEqual(wrapper.state().showRightScroll, true);
});
});
});
describe('scroll button behavior', () => {
let instance;
let wrapper;
let scrollSpy;
const dimensions = { scrollLeft: 100, clientWidth: 200, scrollWidth: 1000 };
before(() => {
wrapper = shallow(
<Tabs width="md" onChange={noop} value={0} scrollable scrollButtons="on">
<Tab />
<Tab />
</Tabs>,
);
instance = wrapper.instance();
instance.tabsRef = dimensions;
scrollSpy = spy(instance, 'moveTabsScroll');
});
it('should call moveTabsScroll', () => {
wrapper
.find(TabScrollButton)
.at(0)
.simulate('click');
assert.strictEqual(
scrollSpy.args[0][0],
-dimensions.clientWidth,
`should be called with -${dimensions.clientWidth}`,
);
wrapper
.find(TabScrollButton)
.at(1)
.simulate('click');
assert.strictEqual(
scrollSpy.args[1][0],
dimensions.clientWidth,
`should be called with ${dimensions.clientWidth}`,
);
});
});
describe('scroll into view behavior', () => {
let scrollStub;
let instance;
let metaStub;
beforeEach(() => {
const wrapper = shallow(
<Tabs width="md" onChange={noop} value={0} scrollable>
<Tab />
<Tab />
</Tabs>,
);
instance = wrapper.instance();
scrollStub = stub(instance, 'scroll');
metaStub = stub(instance, 'getTabsMeta');
});
afterEach(() => {
instance.scroll.restore();
});
it('should scroll left tab into view', () => {
metaStub.returns({
tabsMeta: { left: 0, right: 100, scrollLeft: 10 },
tabMeta: { left: -10, right: 10 },
});
instance.scrollSelectedIntoView();
assert.strictEqual(scrollStub.args[0][0], 0);
});
it('should scroll right tab into view', () => {
metaStub.returns({
tabsMeta: { left: 0, right: 100, scrollLeft: 0 },
tabMeta: { left: 90, right: 110 },
});
instance.scrollSelectedIntoView();
assert.strictEqual(scrollStub.args[0][0], 10);
});
it('should support value=false', () => {
metaStub.returns({
tabsMeta: { left: 0, right: 100, scrollLeft: 0 },
tabMeta: undefined,
});
instance.scrollSelectedIntoView();
assert.strictEqual(scrollStub.callCount, 0);
});
});
describe('prop: TabIndicatorProps', () => {
it('should merge the style', () => {
const wrapper = shallow(
<Tabs onChange={noop} value={0} TabIndicatorProps={{ style: { backgroundColor: 'green' } }}>
<Tab />
</Tabs>,
);
wrapper.setState({ mounted: true });
assert.strictEqual(wrapper.find(TabIndicator).props().style.backgroundColor, 'green');
});
});
});