@activelylearn/material-ui
Version:
Material-UI's workspace package
261 lines (223 loc) • 9.47 kB
JavaScript
// @flow
import React from 'react';
import { spy } from 'sinon';
import { assert } from 'chai';
import JssProvider from 'react-jss/lib/JssProvider';
import { create, SheetsRegistry } from 'jss';
import consoleErrorMock from 'test/utils/consoleErrorMock';
import jssPreset from './jssPreset';
import withStyles from './withStyles';
import MuiThemeProvider from './MuiThemeProvider';
import createMuiTheme from './createMuiTheme';
import createGenerateClassName from './createGenerateClassName';
import { createShallow, createMount, getClasses } from '../test-utils';
// eslint-disable-next-line react/prefer-stateless-function
class Empty extends React.Component<{ classes: Object, theme?: Object }> {
render() {
return <div />;
}
}
describe('withStyles', () => {
let shallow;
let mount;
before(() => {
shallow = createShallow();
mount = createMount();
});
after(() => {
mount.cleanUp();
});
describe('props', () => {
let StyledComponent1;
let classes;
before(() => {
const styles = { root: { display: 'flex' } };
StyledComponent1 = withStyles(styles, { name: 'MuiTextField' })(Empty);
classes = getClasses(<StyledComponent1 />);
});
it('should provide a classes property', () => {
const wrapper = shallow(<StyledComponent1 />);
assert.deepEqual(wrapper.props().classes, classes, 'Should provide the classes property');
});
describe('prop: classes', () => {
before(() => {
consoleErrorMock.spy();
});
after(() => {
consoleErrorMock.reset();
});
it('should accept a classes property', () => {
const wrapper = shallow(<StyledComponent1 classes={{ root: 'h1' }} />);
assert.deepEqual(wrapper.props().classes, { root: `${classes.root} h1` });
assert.strictEqual(consoleErrorMock.callCount(), 0);
});
it('should ignore undefined property', () => {
const wrapper = shallow(<StyledComponent1 classes={{ root: undefined }} />);
assert.deepEqual(wrapper.props().classes, { root: classes.root });
assert.strictEqual(consoleErrorMock.callCount(), 0);
});
it('should warn if providing a unknown key', () => {
const wrapper = shallow(<StyledComponent1 classes={{ bar: 'foo' }} />);
assert.deepEqual(wrapper.props().classes, { root: classes.root, bar: 'undefined foo' });
assert.strictEqual(consoleErrorMock.callCount(), 1);
assert.match(
consoleErrorMock.args()[0][0],
/Material-UI: the key `bar` provided to the classes property is not implemented/,
);
});
it('should warn if providing a non string', () => {
const wrapper = shallow(<StyledComponent1 classes={{ root: {} }} />);
assert.deepEqual(wrapper.props().classes, { root: `${classes.root} [object Object]` });
assert.strictEqual(consoleErrorMock.callCount(), 2);
assert.match(
consoleErrorMock.args()[1][0],
/Material-UI: the key `root` provided to the classes property is not valid/,
);
});
});
describe('prop: innerRef', () => {
it('should provide a ref on the inner component', () => {
const handleRef = spy();
mount(<StyledComponent1 innerRef={handleRef} />);
assert.strictEqual(handleRef.callCount, 1);
});
});
describe('cache', () => {
it('should recycle with no classes property', () => {
const wrapper = mount(<StyledComponent1 />);
const classes1 = wrapper.find(Empty).props().classes;
wrapper.update();
const classes2 = wrapper.find(Empty).props().classes;
assert.strictEqual(classes1, classes2);
});
it('should recycle even when a classes property is provided', () => {
const inputClasses = { root: 'foo' };
const wrapper = mount(<StyledComponent1 classes={inputClasses} />);
const classes1 = wrapper.find(Empty).props().classes;
wrapper.setProps({
classes: inputClasses,
});
const classes2 = wrapper.find(Empty).props().classes;
assert.strictEqual(classes1, classes2);
});
it('should invalidate the cache', () => {
const wrapper = mount(<StyledComponent1 classes={{ root: 'foo' }} />);
const classes1 = wrapper.find(Empty).props().classes;
assert.deepEqual(classes1, {
root: `${classes.root} foo`,
});
wrapper.setProps({
classes: { root: 'bar' },
});
const classes2 = wrapper.find(Empty).props().classes;
assert.notStrictEqual(classes1, classes2);
assert.deepEqual(classes2, {
root: `${classes.root} bar`,
});
});
});
});
describe('mount', () => {
let sheetsRegistry;
let jss;
let generateClassName;
beforeEach(() => {
jss = create(jssPreset());
generateClassName = createGenerateClassName();
sheetsRegistry = new SheetsRegistry();
});
it('should run lifecycles with no theme', () => {
const styles = { root: { display: 'flex' } };
const StyledComponent = withStyles(styles)(Empty);
const wrapper = mount(
<MuiThemeProvider theme={createMuiTheme()}>
<JssProvider registry={sheetsRegistry} jss={jss} generateClassName={generateClassName}>
<StyledComponent />
</JssProvider>
</MuiThemeProvider>,
);
assert.strictEqual(sheetsRegistry.registry.length, 1);
assert.deepEqual(sheetsRegistry.registry[0].classes, { root: 'Empty-root-1' });
wrapper.update();
assert.strictEqual(sheetsRegistry.registry.length, 1, 'should only attach once');
assert.deepEqual(sheetsRegistry.registry[0].classes, { root: 'Empty-root-1' });
wrapper.setProps({ theme: createMuiTheme() });
assert.strictEqual(sheetsRegistry.registry.length, 1, 'should only attach once');
assert.deepEqual(sheetsRegistry.registry[0].classes, { root: 'Empty-root-1' });
wrapper.unmount();
assert.strictEqual(sheetsRegistry.registry.length, 0);
});
it('should work when depending on a theme', () => {
const styles = theme => ({ root: { padding: theme.spacing.unit } });
const StyledComponent = withStyles(styles, { name: 'MuiTextField' })(Empty);
const wrapper = mount(
<MuiThemeProvider theme={createMuiTheme()}>
<JssProvider registry={sheetsRegistry} jss={jss} generateClassName={generateClassName}>
<StyledComponent />
</JssProvider>
</MuiThemeProvider>,
);
assert.strictEqual(sheetsRegistry.registry.length, 1, 'should only attach once');
assert.deepEqual(sheetsRegistry.registry[0].classes, { root: 'MuiTextField-root-1' });
wrapper.setProps({ theme: createMuiTheme() });
assert.strictEqual(sheetsRegistry.registry.length, 1, 'should only attach once');
assert.deepEqual(sheetsRegistry.registry[0].classes, { root: 'MuiTextField-root-2' });
});
it('should support the overrides key', () => {
const styles = { root: { padding: 8 } };
const StyledComponent = withStyles(styles, { name: 'MuiTextField' })(Empty);
mount(
<MuiThemeProvider
theme={createMuiTheme({
overrides: {
MuiTextField: {
root: {
padding: 9,
},
},
},
})}
>
<JssProvider registry={sheetsRegistry} jss={jss} generateClassName={generateClassName}>
<StyledComponent />
</JssProvider>
</MuiThemeProvider>,
);
assert.strictEqual(sheetsRegistry.registry.length, 1, 'should only attach once');
assert.deepEqual(sheetsRegistry.registry[0].rules.raw, { root: { padding: 9 } });
});
describe('options: disableStylesGeneration', () => {
it('should not generate the styles', () => {
const styles = { root: { display: 'flex' } };
const StyledComponent = withStyles(styles)(Empty);
const wrapper = mount(
<MuiThemeProvider theme={createMuiTheme()} disableStylesGeneration>
<JssProvider registry={sheetsRegistry} jss={jss} generateClassName={generateClassName}>
<StyledComponent />
</JssProvider>
</MuiThemeProvider>,
);
assert.strictEqual(sheetsRegistry.registry.length, 0);
assert.deepEqual(wrapper.find(Empty).props().classes, {});
wrapper.unmount();
assert.strictEqual(sheetsRegistry.registry.length, 0);
});
});
});
describe('HMR with same state', () => {
it('should take the new stylesCreator into account', () => {
const styles1 = { root: { padding: 1 } };
const StyledComponent1 = withStyles(styles1, { name: 'MuiTextField' })(Empty);
const wrapper = mount(<StyledComponent1 />);
const styles2 = { root: { padding: 2 } };
const StyledComponent2 = withStyles(styles2, { name: 'MuiTextField' })(Empty);
// Simulate react-hot-loader behavior
wrapper.instance().componentDidUpdate = StyledComponent2.prototype.componentDidUpdate;
const classes1 = wrapper.childAt(0).props().classes.root;
wrapper.setProps({});
wrapper.update();
const classes2 = wrapper.childAt(0).props().classes.root;
assert.notStrictEqual(classes1, classes2, 'should generate new classes');
});
});
});