Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[test] Add toWarnDev() and toErrorDev() matcher #21581

Merged
merged 15 commits into from
Jul 2, 2020
Merged
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"test:regressions": "yarn test:regressions:build && rimraf test/regressions/screenshots/chrome/* && vrtest run --config test/vrtest.config.js --record",
"test:regressions:build": "webpack --config test/regressions/webpack.config.js",
"test:umd": "node packages/material-ui/test/umd/run.js",
"test:unit": "cross-env NODE_ENV=test mocha 'packages/**/*.test.js' 'docs/**/*.test.js' 'scripts/**/*.test.js' --exclude '**/node_modules/**'",
"test:unit": "cross-env NODE_ENV=test mocha 'packages/**/*.test.js' 'docs/**/*.test.js' 'scripts/**/*.test.js' 'test/utils/**/*.test.js' --exclude '**/node_modules/**'",
"test:watch": "yarn test:unit --watch",
"typescript": "lerna run typescript --parallel"
},
Expand Down
92 changes: 45 additions & 47 deletions packages/material-ui-lab/src/Autocomplete/Autocomplete.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { expect } from 'chai';
import { getClasses } from '@material-ui/core/test-utils';
import createMount from 'test/utils/createMount';
import describeConformance from '@material-ui/core/test-utils/describeConformance';
import consoleErrorMock, { consoleWarnMock } from 'test/utils/consoleErrorMock';
import { spy } from 'sinon';
import { act, createClientRender, fireEvent, screen } from 'test/utils/createClientRender';
import { createFilterOptions } from '../useAutocomplete/useAutocomplete';
Expand Down Expand Up @@ -1021,16 +1020,6 @@ describe('<Autocomplete />', () => {
});

describe('warnings', () => {
beforeEach(() => {
consoleErrorMock.spy();
consoleWarnMock.spy();
});

afterEach(() => {
consoleErrorMock.reset();
consoleWarnMock.reset();
});

it('warn if getOptionLabel do not return a string', () => {
const handleChange = spy();
render(
Expand All @@ -1045,14 +1034,18 @@ describe('<Autocomplete />', () => {
);
const textbox = screen.getByRole('textbox');

fireEvent.change(textbox, { target: { value: 'a' } });
fireEvent.keyDown(textbox, { key: 'Enter' });
expect(() => {
fireEvent.change(textbox, { target: { value: 'a' } });
fireEvent.keyDown(textbox, { key: 'Enter' });
}).toErrorDev([
'Material-UI: The `getOptionLabel` method of Autocomplete returned undefined instead of a string',
// strict mode renders twice
'Material-UI: The `getOptionLabel` method of Autocomplete returned undefined instead of a string',
'Material-UI: The `getOptionLabel` method of Autocomplete returned undefined instead of a string',
'Material-UI: The `getOptionLabel` method of Autocomplete returned undefined instead of a string',
]);
eps1lon marked this conversation as resolved.
Show resolved Hide resolved
expect(handleChange.callCount).to.equal(1);
expect(handleChange.args[0][1]).to.equal('a');
expect(consoleErrorMock.callCount()).to.equal(4); // strict mode renders twice
expect(consoleErrorMock.messages()[0]).to.include(
'Material-UI: The `getOptionLabel` method of Autocomplete returned undefined instead of a string',
);
});

it('warn if getOptionSelected match multiple values for a given option', () => {
Expand All @@ -1079,11 +1072,10 @@ describe('<Autocomplete />', () => {
);
const textbox = screen.getByRole('textbox');

fireEvent.keyDown(textbox, { key: 'ArrowDown' });
fireEvent.keyDown(textbox, { key: 'Enter' });

expect(consoleErrorMock.callCount()).to.equal(1);
expect(consoleErrorMock.messages()[0]).to.include(
expect(() => {
fireEvent.keyDown(textbox, { key: 'ArrowDown' });
fireEvent.keyDown(textbox, { key: 'Enter' });
}).toErrorDev(
'The component expects a single value to match a given option but found 2 matches.',
);
});
Expand All @@ -1092,19 +1084,22 @@ describe('<Autocomplete />', () => {
const value = 'not a good value';
const options = ['first option', 'second option'];

render(
<Autocomplete
{...defaultProps}
value={value}
options={options}
renderInput={(params) => <TextField {...params} />}
/>,
);

expect(consoleWarnMock.callCount()).to.equal(4); // strict mode renders twice
expect(consoleWarnMock.messages()[0]).to.include(
expect(() => {
render(
<Autocomplete
{...defaultProps}
value={value}
options={options}
renderInput={(params) => <TextField {...params} />}
/>,
);
}).toWarnDev([
'None of the options match with `"not a good value"`',
);
// strict mode renders twice
'None of the options match with `"not a good value"`',
'None of the options match with `"not a good value"`',
'None of the options match with `"not a good value"`',
]);
});

it('warn if groups options are not sorted', () => {
Expand All @@ -1117,21 +1112,24 @@ describe('<Autocomplete />', () => {
{ group: 2, value: 'F' },
{ group: 1, value: 'C' },
];
const { getAllByRole } = render(
<Autocomplete
{...defaultProps}
options={data}
getOptionLabel={(option) => option.value}
renderInput={(params) => <TextField {...params} autoFocus />}
groupBy={(option) => option.group}
/>,
);

const options = getAllByRole('option').map((el) => el.textContent);
expect(() => {
render(
<Autocomplete
{...defaultProps}
options={data}
getOptionLabel={(option) => option.value}
renderInput={(params) => <TextField {...params} autoFocus />}
groupBy={(option) => option.group}
/>,
);
}).toWarnDev([
// strict mode renders twice
'returns duplicated headers',
'returns duplicated headers',
]);
const options = screen.getAllByRole('option').map((el) => el.textContent);
expect(options).to.have.length(7);
expect(options).to.deep.equal(['A', 'D', 'E', 'B', 'G', 'F', 'C']);
expect(consoleWarnMock.callCount()).to.equal(2); // strict mode renders twice
expect(consoleWarnMock.messages()[0]).to.include('returns duplicated headers');
});
});

Expand Down
47 changes: 13 additions & 34 deletions packages/material-ui-lab/src/TreeView/TreeView.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import * as React from 'react';
import { expect } from 'chai';
import { spy } from 'sinon';
import { createClientRender, fireEvent, screen } from 'test/utils/createClientRender';
import { ErrorBoundary } from 'test/utils/components';
import describeConformance from '@material-ui/core/test-utils/describeConformance';
import { getClasses } from '@material-ui/core/test-utils';
import createMount from 'test/utils/createMount';
import consoleErrorMock from 'test/utils/consoleErrorMock';
import TreeView from './TreeView';
import TreeItem from '../TreeItem';

Expand All @@ -28,23 +28,16 @@ describe('<TreeView />', () => {
}));

describe('warnings', () => {
beforeEach(() => {
consoleErrorMock.spy();
});

afterEach(() => {
consoleErrorMock.reset();
});

it('should warn when switching from controlled to uncontrolled of the expanded prop', () => {
const { setProps } = render(
<TreeView expanded={[]}>
<TreeItem nodeId="1" label="one" />
</TreeView>,
);

setProps({ expanded: undefined });
expect(consoleErrorMock.messages()[0]).to.include(
expect(() => {
setProps({ expanded: undefined });
}).toErrorDev(
'Material-UI: A component is changing the controlled expanded state of TreeView to be uncontrolled.',
);
});
Expand All @@ -56,35 +49,16 @@ describe('<TreeView />', () => {
</TreeView>,
);

setProps({ selected: undefined });
expect(consoleErrorMock.messages()[0]).to.include(
expect(() => {
setProps({ selected: undefined });
}).toErrorDev(
'Material-UI: A component is changing the controlled selected state of TreeView to be uncontrolled.',
);
});

// should not throw eventually or with a better error message
// FIXME: https://github.com/mui-org/material-ui/issues/20832
it('crashes when unmounting with duplicate ids', () => {
class ErrorBoundary extends React.Component {
state = { error: null };

errors = [];

static getDerivedStateFromError(error) {
return { error };
}

componentDidCatch(error) {
this.errors.push(error);
}

render() {
if (this.state.error) {
return null;
}
return this.props.children;
}
}
const CustomTreeItem = () => {
return <TreeItem nodeId="iojerogj" />;
};
Expand Down Expand Up @@ -113,7 +87,12 @@ describe('<TreeView />', () => {
</ErrorBoundary>,
);

screen.getByRole('button').click();
expect(() => {
screen.getByRole('button').click();
}).toErrorDev([
'RangeError: Maximum call stack size exceeded',
'The above error occurred in the <ForwardRef(TreeItem)> component',
]);

const {
current: { errors },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import createMount from 'test/utils/createMount';
import StylesProvider, { StylesContext } from './StylesProvider';
import makeStyles from '../makeStyles';
import createGenerateClassName from '../createGenerateClassName';
import consoleErrorMock from 'test/utils/consoleErrorMock';

function Test() {
const options = React.useContext(StylesContext);
Expand Down Expand Up @@ -136,25 +135,16 @@ describe('StylesProvider', () => {
});

describe('warnings', () => {
beforeEach(() => {
consoleErrorMock.spy();
});

afterEach(() => {
consoleErrorMock.reset();
});

it('should support invalid input', () => {
const jss = create();
mount(
<StylesProvider injectFirst jss={jss}>
<Test />
</StylesProvider>,
);
expect(consoleErrorMock.callCount()).to.equal(1);
expect(consoleErrorMock.messages()[0]).to.include(
'Material-UI: You cannot use the jss and injectFirst props at the same time',
);

expect(() => {
mount(
<StylesProvider injectFirst jss={jss}>
<Test />
</StylesProvider>,
);
}).toErrorDev('Material-UI: You cannot use the jss and injectFirst props at the same time');
});
});
});
51 changes: 24 additions & 27 deletions packages/material-ui-styles/src/ThemeProvider/ThemeProvider.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import { expect } from 'chai';
import consoleErrorMock from 'test/utils/consoleErrorMock';
import { createClientRender } from 'test/utils/createClientRender';
import makeStyles from '../makeStyles';
import useTheme from '../useTheme';
Expand Down Expand Up @@ -121,37 +120,35 @@ describe('ThemeProvider', () => {
});

describe('warnings', () => {
beforeEach(() => {
consoleErrorMock.spy();
});

afterEach(() => {
consoleErrorMock.reset();
});

it('should warn about missing provider', () => {
render(
<ThemeProvider theme={(theme) => theme}>
<div />
</ThemeProvider>,
);
expect(consoleErrorMock.callCount()).to.equal(2); // strict mode renders twice
expect(consoleErrorMock.messages()[0]).to.include('However, no outer theme is present.');
expect(() => {
render(
<ThemeProvider theme={(theme) => theme}>
<div />
</ThemeProvider>,
);
}).toErrorDev([
'However, no outer theme is present.',
// strict mode renders twice
'However, no outer theme is present.',
]);
});

it('should warn about wrong theme function', () => {
render(
<ThemeProvider theme={{ bar: 'bar' }}>
<ThemeProvider theme={() => {}}>
<div />
</ThemeProvider>
,
</ThemeProvider>,
);
expect(consoleErrorMock.callCount()).to.equal(2); // strict mode renders twice
expect(consoleErrorMock.messages()[0]).to.include(
expect(() => {
render(
<ThemeProvider theme={{ bar: 'bar' }}>
<ThemeProvider theme={() => {}}>
<div />
</ThemeProvider>
,
</ThemeProvider>,
);
}).toErrorDev([
'Material-UI: You should return an object from your theme function',
);
// strict mode renders twice
'Material-UI: You should return an object from your theme function',
]);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { expect } from 'chai';
import consoleErrorMock from 'test/utils/consoleErrorMock';
import createGenerateClassName from './createGenerateClassName';
import nested from '../ThemeProvider/nested';

Expand Down Expand Up @@ -125,12 +124,10 @@ describe('createGenerateClassName', () => {
before(() => {
nodeEnv = env.NODE_ENV;
env.NODE_ENV = 'production';
consoleErrorMock.spy();
});

after(() => {
env.NODE_ENV = nodeEnv;
consoleErrorMock.reset();
});

it('should output a short representation', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { expect } from 'chai';
import consoleErrorMock from 'test/utils/consoleErrorMock';
import createGenerateClassNameHash from './createGenerateClassNameHash';

describe('createGenerateClassNameHash', () => {
Expand Down Expand Up @@ -232,12 +231,10 @@ describe('createGenerateClassNameHash', () => {
before(() => {
nodeEnv = env.NODE_ENV;
env.NODE_ENV = 'production';
consoleErrorMock.spy();
});

after(() => {
env.NODE_ENV = nodeEnv;
consoleErrorMock.reset();
});

it('should output a short representation', () => {
Expand Down
Loading