- // -> 0:
- return wrapper.find(Step).childAt(0).childAt(1).childAt(0);
- },
muiName: 'MuiStepContent',
refInstanceof: window.HTMLDivElement,
render: (node) => {
diff --git a/packages/mui-material/src/SwipeableDrawer/SwipeableDrawer.test.js b/packages/mui-material/src/SwipeableDrawer/SwipeableDrawer.test.js
index d11b9f01c4a29a..1f197b487bce9e 100644
--- a/packages/mui-material/src/SwipeableDrawer/SwipeableDrawer.test.js
+++ b/packages/mui-material/src/SwipeableDrawer/SwipeableDrawer.test.js
@@ -63,6 +63,7 @@ describe('
', () => {
const { render } = createRenderer({ clock: 'fake' });
describeConformance(
{}} onClose={() => {}} open />, () => ({
+ render,
classes: {},
inheritComponent: Drawer,
refInstanceof: window.HTMLDivElement,
diff --git a/packages/mui-material/src/Switch/Switch.test.js b/packages/mui-material/src/Switch/Switch.test.js
index 99f454cdb3006b..774e6450b7ad38 100644
--- a/packages/mui-material/src/Switch/Switch.test.js
+++ b/packages/mui-material/src/Switch/Switch.test.js
@@ -17,7 +17,16 @@ describe('', () => {
{ slotName: 'input', slotClassName: classes.input },
],
refInstanceof: window.HTMLSpanElement,
- skip: ['componentProp', 'componentsProp', 'propsSpread', 'themeDefaultProps', 'themeVariants'],
+ skip: [
+ 'componentProp',
+ 'componentsProp',
+ 'themeDefaultProps',
+ 'themeVariants',
+ // Props are spread to the root's child but className is added to the root
+ // We cannot use the standard mergeClassName test which relies on data-testid on the root
+ // We should fix this when refactoring with Base UI
+ 'mergeClassName',
+ ],
}));
describe('styleSheet', () => {
@@ -142,4 +151,12 @@ describe('', () => {
});
});
});
+
+ describe('mergeClassName', () => {
+ it('should merge the className', () => {
+ const { container } = render();
+
+ expect(container.firstChild).to.have.class('test-class-name');
+ });
+ });
});
diff --git a/packages/mui-material/src/TableBody/TableBody.test.js b/packages/mui-material/src/TableBody/TableBody.test.js
index 8faaa00569c13c..708b0add1c5230 100644
--- a/packages/mui-material/src/TableBody/TableBody.test.js
+++ b/packages/mui-material/src/TableBody/TableBody.test.js
@@ -15,10 +15,6 @@ describe('', () => {
describeConformance(, () => ({
classes,
inheritComponent: 'tbody',
- wrapMount: (mount) => (node) => {
- const wrapper = mount();
- return wrapper.find('table').childAt(0);
- },
render: (node) => {
const { container, ...other } = render();
return { container: container.firstChild, ...other };
diff --git a/packages/mui-material/src/TableCell/TableCell.test.js b/packages/mui-material/src/TableCell/TableCell.test.js
index 427864cf0df7e7..3d767a3c3066d3 100644
--- a/packages/mui-material/src/TableCell/TableCell.test.js
+++ b/packages/mui-material/src/TableCell/TableCell.test.js
@@ -32,16 +32,6 @@ describe('', () => {
);
return { container: container.firstChild.firstChild.firstChild, ...other };
},
- wrapMount: (mount) => (node) => {
- const wrapper = mount(
- ,
- );
- return wrapper.find('tr').childAt(0);
- },
muiName: 'MuiTableCell',
testVariantProps: { variant: 'body' },
refInstanceof: window.HTMLTableCellElement,
diff --git a/packages/mui-material/src/TableFooter/TableFooter.test.js b/packages/mui-material/src/TableFooter/TableFooter.test.js
index b0ffcd15d9da50..8f06544194906e 100644
--- a/packages/mui-material/src/TableFooter/TableFooter.test.js
+++ b/packages/mui-material/src/TableFooter/TableFooter.test.js
@@ -19,10 +19,6 @@ describe('', () => {
const { container, ...other } = render();
return { container: container.firstChild, ...other };
},
- wrapMount: (mount) => (node) => {
- const wrapper = mount();
- return wrapper.find('table').childAt(0);
- },
muiName: 'MuiTableFooter',
testVariantProps: { variant: 'foo' },
refInstanceof: window.HTMLTableSectionElement,
diff --git a/packages/mui-material/src/TableHead/TableHead.test.js b/packages/mui-material/src/TableHead/TableHead.test.js
index 0d898aee753bc2..6b235c3974b0fc 100644
--- a/packages/mui-material/src/TableHead/TableHead.test.js
+++ b/packages/mui-material/src/TableHead/TableHead.test.js
@@ -14,10 +14,6 @@ describe('', () => {
describeConformance(, () => ({
classes,
inheritComponent: 'thead',
- wrapMount: (mount) => (node) => {
- const wrapper = mount();
- return wrapper.find('table').childAt(0);
- },
render: (node) => {
const { container, ...other } = render();
return { container: container.firstChild, ...other };
diff --git a/packages/mui-material/src/TablePagination/TablePagination.test.js b/packages/mui-material/src/TablePagination/TablePagination.test.js
index dd84205b454d2d..aae4498c9e6d5a 100644
--- a/packages/mui-material/src/TablePagination/TablePagination.test.js
+++ b/packages/mui-material/src/TablePagination/TablePagination.test.js
@@ -45,16 +45,6 @@ describe('', () => {
);
return { container: container.firstChild.firstChild.firstChild, ...other };
},
- wrapMount: (mount) => (node) => {
- const wrapper = mount(
- ,
- );
- return wrapper.find('tr').childAt(0);
- },
muiName: 'MuiTablePagination',
refInstanceof: window.HTMLTableCellElement,
testComponentPropWith: 'td',
diff --git a/packages/mui-material/src/TableRow/TableRow.test.js b/packages/mui-material/src/TableRow/TableRow.test.js
index 5dee6de9e0dc3e..1c16402755ecc3 100644
--- a/packages/mui-material/src/TableRow/TableRow.test.js
+++ b/packages/mui-material/src/TableRow/TableRow.test.js
@@ -26,14 +26,6 @@ describe('', () => {
);
return { container: container.firstChild.firstChild, ...other };
},
- wrapMount: (mount) => (node) => {
- const wrapper = mount(
- ,
- );
- return wrapper.find('tbody').childAt(0);
- },
muiName: 'MuiTableRow',
testVariantProps: { variant: 'foo' },
refInstanceof: window.HTMLTableRowElement,
diff --git a/packages/mui-material/src/Zoom/Zoom.test.js b/packages/mui-material/src/Zoom/Zoom.test.js
index c3d272f2dd0650..19d330e846e811 100644
--- a/packages/mui-material/src/Zoom/Zoom.test.js
+++ b/packages/mui-material/src/Zoom/Zoom.test.js
@@ -15,6 +15,7 @@ describe('', () => {
,
() => ({
+ render,
classes: {},
inheritComponent: Transition,
refInstanceof: window.HTMLDivElement,
diff --git a/packages/mui-private-theming/package.json b/packages/mui-private-theming/package.json
index 22c347fec35d18..63046e80b6ed49 100644
--- a/packages/mui-private-theming/package.json
+++ b/packages/mui-private-theming/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/private-theming",
- "version": "5.15.20",
+ "version": "5.16.0",
"private": false,
"author": "MUI Team",
"description": "Private - The React theme context to be shared between `@mui/styles` and `@mui/material`.",
diff --git a/packages/mui-styles/package.json b/packages/mui-styles/package.json
index 27ad18c1d783fd..8739503f978f00 100644
--- a/packages/mui-styles/package.json
+++ b/packages/mui-styles/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/styles",
- "version": "5.15.21",
+ "version": "5.16.0",
"private": false,
"author": "MUI Team",
"description": "MUI Styles - The legacy JSS-based styling solution of Material UI.",
diff --git a/packages/mui-styles/src/StylesProvider/StylesProvider.test.js b/packages/mui-styles/src/StylesProvider/StylesProvider.test.js
index 0c9945949a95ce..9b391dc283c9bd 100644
--- a/packages/mui-styles/src/StylesProvider/StylesProvider.test.js
+++ b/packages/mui-styles/src/StylesProvider/StylesProvider.test.js
@@ -1,56 +1,65 @@
import * as React from 'react';
import * as ReactDOMServer from 'react-dom/server';
import { expect } from 'chai';
+import { spy, match } from 'sinon';
import { create, SheetsRegistry } from 'jss';
-import { createMount, strictModeDoubleLoggingSuppressed } from '@mui-internal/test-utils';
+import { createRenderer, strictModeDoubleLoggingSuppressed } from '@mui-internal/test-utils';
import StylesProvider, { StylesContext } from './StylesProvider';
import makeStyles from '../makeStyles';
import createGenerateClassName from '../createGenerateClassName';
-function Test() {
- const options = React.useContext(StylesContext);
- return ;
-}
-
-function getOptions(wrapper) {
- return wrapper.find('span').props()['data-options'];
-}
-
describe('StylesProvider', () => {
- const mount = createMount();
+ const { render } = createRenderer();
let generateClassName;
+ let getContext;
+
+ function Test() {
+ const context = React.useContext(StylesContext);
+
+ React.useEffect(() => {
+ getContext(context);
+ }, [context]);
+
+ return null;
+ }
beforeEach(() => {
generateClassName = createGenerateClassName();
+ getContext = spy();
});
- it('should provide the options', () => {
- const wrapper = mount(
+ it('should provide the context', () => {
+ render(
,
);
- expect(getOptions(wrapper).disableGeneration).to.equal(true);
+
+ expect(getContext.alwaysCalledWith(match({ disableGeneration: true }))).to.equal(true);
});
it('should merge the themes', () => {
- const wrapper = mount(
+ render(
,
);
- expect(getOptions(wrapper).disableGeneration).to.equal(true);
+
+ expect(getContext.alwaysCalledWith(match({ disableGeneration: true }))).to.equal(true);
});
it('should handle injectFirst', () => {
- const wrapper = mount(
+ render(
,
);
- expect(getOptions(wrapper).jss.options.insertionPoint.nodeType).to.equal(8);
+
+ expect(
+ getContext.alwaysCalledWith(match({ jss: { options: { insertionPoint: { nodeType: 8 } } } })),
+ ).to.equal(true);
});
describe('server-side', () => {
@@ -128,20 +137,20 @@ describe('StylesProvider', () => {
it('should accept a custom JSS instance', () => {
const jss = create();
- const wrapper = mount(
+ render(
,
);
- expect(getOptions(wrapper).jss).to.equal(jss);
+
+ expect(getContext.alwaysCalledWith(match({ jss }))).to.equal(true);
});
describe('warnings', () => {
it('should support invalid input', () => {
const jss = create();
-
expect(() => {
- mount(
+ render(
,
diff --git a/packages/mui-styles/src/makeStyles/makeStyles.test.js b/packages/mui-styles/src/makeStyles/makeStyles.test.js
index 7cddc8825c46d3..e5721335d8c460 100644
--- a/packages/mui-styles/src/makeStyles/makeStyles.test.js
+++ b/packages/mui-styles/src/makeStyles/makeStyles.test.js
@@ -2,8 +2,7 @@ import { expect } from 'chai';
import * as React from 'react';
import PropTypes from 'prop-types';
import { SheetsRegistry } from 'jss';
-import { act } from 'react-dom/test-utils';
-import { createMount } from '@mui-internal/test-utils';
+import { createRenderer, screen, renderHook, fireEvent } from '@mui-internal/test-utils';
import { createTheme } from '@mui/material/styles';
import createGenerateClassName from '../createGenerateClassName';
import makeStyles from './makeStyles';
@@ -13,28 +12,7 @@ import ThemeProvider from '../ThemeProvider';
describe('makeStyles', () => {
// StrictModeViolation: uses `useSynchronousEffect`
- const mount = createMount({ strict: null });
-
- /**
- * returns a function that given the props for the styles object will return
- * the css classes
- * @param {object} styles argument for `makeStyles`
- */
- function createGetClasses(styles) {
- const useStyles = makeStyles(styles);
- const output = {};
-
- function TestComponent(props) {
- output.classes = useStyles(props);
- return ;
- }
-
- return function mountWithProps(props) {
- const wrapper = mount();
- output.wrapper = wrapper;
- return output;
- };
- }
+ const { render } = createRenderer({ strict: false });
let generateClassName;
@@ -44,72 +22,73 @@ describe('makeStyles', () => {
it('should accept a classes prop', () => {
const styles = { root: {} };
- const mountWithProps = createGetClasses(styles);
- const output = mountWithProps();
- const baseClasses = output.classes;
- output.wrapper.setProps({
- classes: { root: 'h1' },
- });
- const extendedClasses = output.classes;
+ const useStyles = makeStyles(styles);
+
+ const { result, rerender } = renderHook((props) => useStyles(props));
+ const baseClasses = result.current;
+ rerender({ classes: { root: 'h1' } });
+ const extendedClasses = result.current;
+
expect(extendedClasses.root).to.equal(`${baseClasses.root} h1`);
});
it('should ignore undefined prop', () => {
const styles = { root: {} };
- const mountWithProps = createGetClasses(styles);
- const output = mountWithProps();
- const baseClasses = output.classes;
- output.wrapper.setProps({
- classes: { root: undefined },
- });
- const extendedClasses = output.classes;
+ const useStyles = makeStyles(styles);
+
+ const { result, rerender } = renderHook((props) => useStyles(props));
+ const baseClasses = result.current;
+ rerender({ classes: { root: undefined } });
+ const extendedClasses = result.current;
+
expect(extendedClasses.root).to.equal(baseClasses.root);
});
describe('warnings', () => {
- const mountWithProps = createGetClasses({ root: {} });
+ const useStyles = makeStyles({ root: {} });
it('should warn if providing a unknown key', () => {
- const output = mountWithProps();
+ let baseClasses;
+ let extendedClasses;
expect(() => {
- output.wrapper.setProps({ classes: { bar: 'foo' } });
+ const { result } = renderHook(() => useStyles({ classes: { bar: 'foo' } }));
+ baseClasses = result.current;
+ extendedClasses = result.current;
}).toErrorDev('MUI: The key `bar` provided to the classes prop is not implemented');
-
- const baseClasses = output.classes;
- const extendedClasses = output.classes;
expect(extendedClasses).to.deep.equal({ root: baseClasses.root, bar: 'undefined foo' });
});
it('should warn if providing a string', () => {
- const output = mountWithProps();
-
expect(() => {
- output.wrapper.setProps({ classes: 'foo' });
+ renderHook(() => useStyles({ classes: 'foo' }));
}).toErrorDev(['You might want to use the className prop instead']);
});
it('should warn if providing a non string', () => {
- const output = mountWithProps();
- const baseClasses = output.classes;
+ const { result, rerender } = renderHook((props) => useStyles(props));
+ const baseClasses = result.current;
expect(() => {
- output.wrapper.setProps({ classes: { root: {} } });
+ rerender({ classes: { root: {} } });
}).toErrorDev('MUI: The key `root` provided to the classes prop is not valid');
- const extendedClasses = output.classes;
+ const extendedClasses = result.current;
+
expect(extendedClasses).to.deep.equal({ root: `${baseClasses.root} [object Object]` });
});
it('should warn if missing theme', () => {
- const styles = (theme) => ({ root: { padding: theme.spacing(2) } });
- const mountWithProps2 = createGetClasses(styles);
+ const useStyles2 = makeStyles((theme) => ({ root: { padding: theme.spacing(2) } }));
expect(() => {
expect(() => {
- mountWithProps2({});
+ renderHook(() => useStyles2({}));
}).to.throw('theme.spacing is not a function');
}).toErrorDev([
+ 'MUI: The `styles` argument provided is invalid.\nYou are providing a function without a theme in the context.',
+ 'MUI: The `styles` argument provided is invalid.\nYou are providing a function without a theme in the context.',
+ 'Uncaught [TypeError: theme.spacing is not a function',
'MUI: The `styles` argument provided is invalid.\nYou are providing a function without a theme in the context.',
'MUI: The `styles` argument provided is invalid.\nYou are providing a function without a theme in the context.',
'Uncaught [TypeError: theme.spacing is not a function',
@@ -118,16 +97,15 @@ describe('makeStyles', () => {
});
it('should warn but not throw if providing an invalid styles type', () => {
- let mountWithProps2;
+ let useStyles2;
expect(() => {
- mountWithProps2 = createGetClasses(undefined);
+ useStyles2 = makeStyles(undefined);
}).toErrorDev(
'MUI: The `styles` argument provided is invalid.\nYou need to provide a function generating the styles or a styles object.',
);
-
expect(() => {
- mountWithProps2({});
+ renderHook(() => useStyles2({}));
}).not.to.throw();
});
@@ -143,15 +121,14 @@ describe('makeStyles', () => {
},
},
};
-
- const useStyles = makeStyles({ root: { margin: 5, padding: 3 } }, { name: 'Test' });
+ const useStyles2 = makeStyles({ root: { margin: 5, padding: 3 } }, { name: 'Test' });
function Test() {
- const classes = useStyles();
+ const classes = useStyles2();
return ;
}
expect(() => {
- mount(
+ render(
,
@@ -164,45 +141,47 @@ describe('makeStyles', () => {
});
describe('classes memoization', () => {
- let mountWithProps;
+ let useStyles;
before(() => {
const styles = { root: {} };
- mountWithProps = createGetClasses(styles);
+ useStyles = makeStyles(styles);
});
it('should recycle with no classes prop', () => {
- const output = mountWithProps();
- const classes1 = output.classes;
- output.wrapper.update();
- const classes2 = output.classes;
+ const { result, rerender } = renderHook(() => useStyles());
+ const classes1 = result.current;
+ rerender();
+ const classes2 = result.current;
+
expect(classes1).to.equal(classes2);
});
it('should recycle even when a classes prop is provided', () => {
const inputClasses = { root: 'foo' };
- const output = mountWithProps({ classes: inputClasses });
- const classes1 = output.classes;
- output.wrapper.setProps({
- classes: inputClasses,
- });
- const classes2 = output.classes;
+ const { result, rerender } = renderHook(() => useStyles({ classes: inputClasses }));
+ const classes1 = result.current;
+ rerender();
+ const classes2 = result.current;
+
expect(classes1).to.equal(classes2);
});
it('should invalidate the cache', () => {
- const output = mountWithProps();
- const classes = output.classes;
- output.wrapper.setProps({ classes: { root: 'foo' } });
- const classes1 = output.classes;
+ const { result, rerender } = renderHook((props) => useStyles(props));
+ const classes = result.current;
+ rerender({ classes: { root: 'foo' } });
+ const classes1 = result.current;
+
expect(classes1).to.deep.equal({
root: `${classes.root} foo`,
});
- output.wrapper.setProps({
+ rerender({
classes: { root: 'bar' },
});
- const classes2 = output.classes;
+ const classes2 = result.current;
+
expect(classes1).not.to.equal(classes2);
expect(classes2).to.deep.equal({
root: `${classes.root} bar`,
@@ -219,13 +198,14 @@ describe('makeStyles', () => {
it('should run lifecycles with no theme', () => {
const useStyles = makeStyles({ root: { display: 'flex' } });
+ const initialTheme = createTheme();
function StyledComponent() {
useStyles();
return ;
}
- const wrapper = mount(
-
+ const { setProps, unmount } = render(
+
{
,
);
+
expect(sheetsRegistry.registry.length).to.equal(1);
expect(sheetsRegistry.registry[0].classes).to.deep.equal({ root: 'makeStyles-root-1' });
- wrapper.update();
+
+ setProps();
+
expect(sheetsRegistry.registry.length).to.equal(1);
expect(sheetsRegistry.registry[0].classes).to.deep.equal({ root: 'makeStyles-root-1' });
- wrapper.setProps({ theme: createTheme() });
+
+ setProps({ theme: createTheme() });
+
expect(sheetsRegistry.registry.length).to.equal(1);
expect(sheetsRegistry.registry[0].classes).to.deep.equal({ root: 'makeStyles-root-2' });
- wrapper.unmount();
+ unmount();
+
expect(sheetsRegistry.registry.length).to.equal(0);
});
@@ -257,7 +243,7 @@ describe('makeStyles', () => {
return ;
}
- const wrapper = mount(
+ const { setProps } = render(
{
,
);
+
expect(sheetsRegistry.registry.length).to.equal(1);
expect(sheetsRegistry.registry[0].classes).to.deep.equal({ root: 'MuiTextField-root' });
- wrapper.setProps({ theme: createTheme({ foo: 'bar' }) });
+ setProps({ theme: createTheme({ foo: 'bar' }) });
+
expect(sheetsRegistry.registry.length).to.equal(1);
expect(sheetsRegistry.registry[0].classes).to.deep.equal({ root: 'MuiTextField-root' });
});
@@ -294,7 +282,7 @@ describe('makeStyles', () => {
return ;
}
- mount(
+ render(
{
,
);
+
expect(sheetsRegistry.registry.length).to.equal(1);
expect(sheetsRegistry.registry[0].rules.raw).to.deep.equal({
root: { padding: 9, margin: [2, 2, 3] },
@@ -332,14 +321,13 @@ describe('makeStyles', () => {
},
},
};
-
const useStyles = makeStyles({ root: { margin: 5, padding: 3 } }, { name: 'Test' });
function Test() {
const classes = useStyles();
return ;
}
- mount(
+ render(
@@ -354,7 +342,7 @@ describe('makeStyles', () => {
});
});
- it('should handle dynamic props', () => {
+ it('should handle dynamic props', async () => {
const useStyles = makeStyles({
root: (props) => ({ margin: 8, padding: props.padding || 8 }),
});
@@ -362,7 +350,6 @@ describe('makeStyles', () => {
const classes = useStyles(props);
return ;
}
-
function Test(props) {
return (
{
);
}
- const wrapper = mount();
+ const { setProps } = render();
+
expect(sheetsRegistry.registry.length).to.equal(2);
expect(sheetsRegistry.registry[0].classes).to.deep.equal({ root: 'makeStyles-root-1' });
expect(sheetsRegistry.registry[1].classes).to.deep.equal({ root: 'makeStyles-root-2' });
@@ -384,7 +372,8 @@ describe('makeStyles', () => {
padding: '8px',
});
- wrapper.setProps({ padding: 4 });
+ setProps({ padding: 4 });
+
expect(sheetsRegistry.registry.length).to.equal(2);
expect(sheetsRegistry.registry[0].classes).to.deep.equal({ root: 'makeStyles-root-1' });
expect(sheetsRegistry.registry[1].classes).to.deep.equal({ root: 'makeStyles-root-2' });
@@ -398,23 +387,30 @@ describe('makeStyles', () => {
describe('options: disableGeneration', () => {
it('should not generate the styles', () => {
const sheetsRegistry = new SheetsRegistry();
- function Empty() {
+ const useStyles = makeStyles({ root: { padding: 8 } });
+ let classes;
+ function Empty(props) {
+ React.useEffect(() => {
+ classes = props.classes;
+ }, [props.classes]);
return ;
}
- const useStyles = makeStyles({ root: { padding: 8 } });
function StyledComponent() {
- const classes = useStyles();
- return ;
+ const classes2 = useStyles();
+ return ;
}
- const wrapper = mount(
+ const { unmount } = render(
,
);
+
expect(sheetsRegistry.registry.length).to.equal(0);
- expect(wrapper.find(Empty).props().classes).to.deep.equal({});
- wrapper.unmount();
+ expect(classes).to.deep.equal({});
+
+ unmount();
+
expect(sheetsRegistry.registry.length).to.equal(0);
});
});
@@ -439,12 +435,13 @@ describe('makeStyles', () => {
return ;
}
- mount(
+ render(
,
);
+
expect(sheetsRegistry.registry[0].options.classNamePrefix).to.equal('makeStyles');
expect(sheetsRegistry.registry[0].options.name).to.equal(undefined);
expect(sheetsRegistry.registry[1].options.classNamePrefix).to.equal('Fooo');
@@ -522,7 +519,7 @@ describe('makeStyles', () => {
it('should update like expected', () => {
const sheetsRegistry = new SheetsRegistry();
- const wrapper = mount(
+ render(
{
,
);
+
expect(sheetsRegistry.registry.length).to.equal(2);
expect(sheetsRegistry.toString()).to.equal(`.makeStyles-root-2 {
color: white;
background-color: black;
}`);
- act(() => {
- wrapper.find('#color').simulate('change', { target: { value: 'blue' } });
- });
+ fireEvent.change(screen.getByLabelText('color'), { target: { value: 'blue' } });
+
expect(sheetsRegistry.toString()).to.equal(
`.makeStyles-root-4 {
color: blue;
background-color: black;
}`,
);
- act(() => {
- wrapper.find('#background-color').simulate('change', { target: { value: 'green' } });
+
+ fireEvent.change(screen.getByLabelText('background-color'), {
+ target: { value: 'green' },
});
+
expect(sheetsRegistry.toString()).to.equal(
`.makeStyles-root-4 {
color: blue;
diff --git a/packages/mui-styles/src/withStyles/withStyles.test.js b/packages/mui-styles/src/withStyles/withStyles.test.js
index f739276ae56d0d..6b9170aa89c26a 100644
--- a/packages/mui-styles/src/withStyles/withStyles.test.js
+++ b/packages/mui-styles/src/withStyles/withStyles.test.js
@@ -135,7 +135,16 @@ describe('withStyles', () => {
const jssCallbackStub = stub().returns({});
const styles = { root: jssCallbackStub };
const StyledComponent = withStyles(styles)(MyComp);
- render();
+ const renderCb = () => render();
+
+ // React 18.3.0 started warning for deprecated defaultProps for function components
+ if (React.version.startsWith('18.3')) {
+ expect(renderCb).toErrorDev([
+ 'Warning: MyComp: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
+ ]);
+ } else {
+ renderCb();
+ }
expect(jssCallbackStub.callCount).to.equal(1);
expect(jssCallbackStub.args[0][0]).to.deep.equal({
@@ -178,24 +187,33 @@ describe('withStyles', () => {
const styles = { root: { display: 'flex' } };
const StyledComponent = withStyles(styles, { name: 'MuiFoo' })(MuiFoo);
-
- const { container } = render(
-
+ render(
+
-
- ,
- );
+ })}
+ >
+
+ ,
+ );
- expect(container).to.have.text('bar');
+ // React 18.3.0 started warning for deprecated defaultProps for function components
+ if (React.version.startsWith('18.3')) {
+ expect(renderCb).toErrorDev([
+ 'Warning: MuiFoo: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
+ ]);
+ } else {
+ renderCb();
+ }
+
+ expect(screen.getByText('bar')).not.to.equal(null);
});
it('should work when depending on a theme', () => {
diff --git a/packages/mui-system/package.json b/packages/mui-system/package.json
index 9d4f7b1b139da0..ad8b9a0fb66f0c 100644
--- a/packages/mui-system/package.json
+++ b/packages/mui-system/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/system",
- "version": "5.15.20",
+ "version": "5.16.0",
"private": false,
"author": "MUI Team",
"description": "MUI System is a set of CSS utilities to help you build custom designs more efficiently. It makes it possible to rapidly lay out custom designs.",
diff --git a/packages/mui-utils/package.json b/packages/mui-utils/package.json
index ac541b81e49025..d917656f35f9fc 100644
--- a/packages/mui-utils/package.json
+++ b/packages/mui-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@mui/utils",
- "version": "5.15.20",
+ "version": "5.16.0",
"private": false,
"author": "MUI Team",
"description": "Utility functions for React components.",
diff --git a/packages/test-utils/src/createMount.tsx b/packages/test-utils/src/createMount.tsx
deleted file mode 100644
index 03f0d273d08bff..00000000000000
--- a/packages/test-utils/src/createMount.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-/* eslint-env mocha */
-import * as React from 'react';
-import * as ReactDOM from 'react-dom';
-import * as ReactDOMTestUtils from 'react-dom/test-utils';
-import { Test, Suite } from 'mocha';
-import { mount as enzymeMount, MountRendererProps } from 'enzyme';
-
-interface ModeProps {
- /**
- * this is essentially children. However, we can't use children because then
- * using `wrapper.setProps({ children })` would work differently if this component
- * would be the root.
- */
- __element: React.ReactElement;
- __strict: boolean;
-}
-
-/**
- * Can't just mount {node}
- * because that swallows wrapper.setProps
- *
- * why class component:
- * https://github.com/airbnb/enzyme/issues/2043
- */
-// eslint-disable-next-line react/prefer-stateless-function
-class Mode extends React.Component {
- render() {
- // Excess props will come from e.g. enzyme setProps
- // eslint-disable-next-line @typescript-eslint/naming-convention
- const { __element, __strict, ...other } = this.props;
- const Component = __strict ? React.StrictMode : React.Fragment;
-
- return {React.cloneElement(__element, other)};
- }
-}
-
-interface CreateMountOptions extends MountRendererProps {
- mount?: typeof enzymeMount;
- strict?: boolean;
-}
-// Generate an enhanced mount function.
-export default function createMount(options: CreateMountOptions = {}) {
- const { mount = enzymeMount, strict: globalStrict = true, ...globalEnzymeOptions } = options;
-
- let container: HTMLElement | null = null;
-
- function computeTestName(test: Test | undefined) {
- let current: Test | Suite | undefined = test;
- const titles: string[] = [];
- while (current != null) {
- titles.push(current.title);
- current = current.parent;
- }
-
- return titles.filter(Boolean).reverse().join(' -> ');
- }
-
- // save stack to re-use in test-hooks
- const { stack: createMountStack } = new Error();
-
- /**
- * Flag whether `createMount` was called in a suite i.e. describe() block.
- * For legacy reasons `createMount` might accidentally be called in a before(Each) hook.
- */
- let wasCalledInSuite = false;
- before(() => {
- wasCalledInSuite = true;
- });
-
- beforeEach(() => {
- if (!wasCalledInSuite) {
- const error = new Error(
- 'Unable to run `before` hook for `createMount`. This usually indicates that `createMount` was called in a `before` hook instead of in a `describe()` block.',
- );
- error.stack = createMountStack;
- throw error;
- }
- });
-
- beforeEach(function beforeEachMountTest() {
- container = document.createElement('div');
- container.setAttribute('data-test', computeTestName(this.currentTest));
- document.body.insertBefore(container, document.body.firstChild);
- });
-
- afterEach(() => {
- ReactDOMTestUtils.act(() => {
- // eslint-disable-next-line react/no-deprecated
- ReactDOM.unmountComponentAtNode(container!);
- });
- container!.parentElement!.removeChild(container!);
- container = null;
- });
-
- const mountWithContext = function mountWithContext(
- node: React.ReactElement,
- localOptions: Omit = {},
- ) {
- const { strict = globalStrict, ...localEnzymeOptions } = localOptions;
-
- if (container === null) {
- throw new Error(
- `Tried to mount without setup. Mounting inside before() is not allowed. Try mounting in beforeEach or better: in each test`,
- );
- }
- ReactDOMTestUtils.act(() => {
- // eslint-disable-next-line react/no-deprecated
- ReactDOM.unmountComponentAtNode(container!);
- });
-
- // some tests require that no other components are in the tree
- // for example when doing .instance(), .state() etc.
- const wrapper = mount(
- strict == null ? node : ,
- {
- attachTo: container,
- ...globalEnzymeOptions,
- ...localEnzymeOptions,
- },
- );
- const originalUnmount = wrapper.unmount;
-
- wrapper.unmount = () => {
- // flush effect cleanup functions
- ReactDOMTestUtils.act(() => {
- originalUnmount.call(wrapper);
- });
-
- return wrapper;
- };
-
- return wrapper;
- };
-
- return mountWithContext;
-}
diff --git a/packages/test-utils/src/describeConformance.tsx b/packages/test-utils/src/describeConformance.tsx
index 3063749ac0d095..d1625eba4c5189 100644
--- a/packages/test-utils/src/describeConformance.tsx
+++ b/packages/test-utils/src/describeConformance.tsx
@@ -1,11 +1,8 @@
/* eslint-env mocha */
import * as React from 'react';
import { expect } from 'chai';
-import { ReactWrapper } from 'enzyme';
import ReactTestRenderer from 'react-test-renderer';
-import createMount from './createMount';
import createDescribe from './createDescribe';
-import findOutermostIntrinsic from './findOutermostIntrinsic';
import { MuiRenderResult } from './createRenderer';
function capitalize(string: string): string {
@@ -45,11 +42,19 @@ export interface ConformanceOptions {
refInstanceof: any;
after?: () => void;
inheritComponent?: React.ElementType;
- render: (node: React.ReactElement) => MuiRenderResult;
- mount?: (node: React.ReactElement) => ReactWrapper;
+ render: (node: React.ReactElement) => MuiRenderResult;
only?: Array;
skip?: Array;
testComponentsRootPropWith?: string;
+ /**
+ * A custom React component to test if the component prop is implemented correctly.
+ *
+ * It must either:
+ * - Be a string that is a valid HTML tag, or
+ * - A component that spread props to the underlying rendered element.
+ *
+ * If not provided, the default 'em' element is used.
+ */
testComponentPropWith?: string | React.ElementType;
testDeepOverrides?: SlotTestOverride | SlotTestOverride[];
testRootOverrides?: SlotTestOverride;
@@ -57,42 +62,11 @@ export interface ConformanceOptions {
testCustomVariant?: boolean;
testVariantProps?: object;
testLegacyComponentsProp?: boolean;
- wrapMount?: (
- mount: (node: React.ReactElement) => ReactWrapper,
- ) => (node: React.ReactElement) => ReactWrapper;
slots?: Record;
ThemeProvider?: React.ElementType;
createTheme?: (arg: any) => any;
}
-/**
- * @param {object} node
- * @returns
- */
-function assertDOMNode(node: unknown) {
- // duck typing a DOM node
- expect(typeof (node as HTMLElement).nodeName).to.equal('string');
-}
-
-/**
- * Utility method to make assertions about the ref on an element
- * The element should have a component wrapped in withStyles as the root
- */
-function testRef(
- element: React.ReactElement,
- mount: ConformanceOptions['mount'],
- onRef: (instance: unknown, wrapper: import('enzyme').ReactWrapper) => void = assertDOMNode,
-) {
- if (!mount) {
- throwMissingPropError('mount');
- }
-
- const ref = React.createRef();
-
- const wrapper = mount({React.cloneElement(element, { ref })});
- onRef(ref.current, wrapper);
-}
-
/**
* Glossary
* - root component:
@@ -102,18 +76,6 @@ function testRef(
* - has the type of `inheritComponent`
*/
-/**
- * Returns the component with the same constructor as `component` that renders
- * the outermost host
- */
-export function findRootComponent(wrapper: ReactWrapper, component: string | React.ElementType) {
- const outermostHostElement = findOutermostIntrinsic(wrapper).getElement();
-
- return wrapper.find(component as string).filterWhere((componentWrapper) => {
- return componentWrapper.contains(outermostHostElement);
- });
-}
-
export function randomStringValue() {
return `s${Math.random().toString(36).slice(2)}`;
}
@@ -131,16 +93,20 @@ function throwMissingPropError(field: string): never {
*/
export function testClassName(element: React.ReactElement, getOptions: () => ConformanceOptions) {
it('applies the className to the root component', () => {
- const { mount } = getOptions();
- if (!mount) {
- throwMissingPropError('mount');
+ const { render } = getOptions();
+
+ if (!render) {
+ throwMissingPropError('render');
}
const className = randomStringValue();
+ const testId = randomStringValue();
- const wrapper = mount(React.cloneElement(element, { className }));
+ const { getByTestId } = render(
+ React.cloneElement(element, { className, 'data-testid': testId }),
+ );
- expect(findOutermostIntrinsic(wrapper).instance()).to.have.class(className);
+ expect(getByTestId(testId)).to.have.class(className);
});
}
@@ -154,42 +120,54 @@ export function testComponentProp(
) {
describe('prop: component', () => {
it('can render another root component with the `component` prop', () => {
- const { mount, testComponentPropWith: component = 'em' } = getOptions();
- if (!mount) {
- throwMissingPropError('mount');
+ const { render, testComponentPropWith: component = 'em' } = getOptions();
+ if (!render) {
+ throwMissingPropError('render');
}
- const wrapper = mount(React.cloneElement(element, { component }));
+ const testId = randomStringValue();
- expect(findRootComponent(wrapper, component).exists()).to.equal(true);
+ if (typeof component === 'string') {
+ const { getByTestId } = render(
+ React.cloneElement(element, { component, 'data-testid': testId }),
+ );
+ expect(getByTestId(testId)).not.to.equal(null);
+ expect(getByTestId(testId).nodeName.toLowerCase()).to.eq(component);
+ } else {
+ const componentWithTestId = (props: {}) =>
+ React.createElement(component, { ...props, 'data-testid': testId });
+ const { getByTestId } = render(
+ React.cloneElement(element, {
+ component: componentWithTestId,
+ }),
+ );
+ expect(getByTestId(testId)).not.to.equal(null);
+ }
});
});
}
/**
- * MUI components can spread additional props to a documented component.
+ * MUI components spread additional props to its root.
*/
export function testPropsSpread(element: React.ReactElement, getOptions: () => ConformanceOptions) {
it(`spreads props to the root component`, () => {
// type def in ConformanceOptions
- const { inheritComponent, mount } = getOptions();
- if (!mount) {
- throwMissingPropError('mount');
- }
+ const { render } = getOptions();
- if (inheritComponent === undefined) {
- throw new TypeError(
- 'Unable to test props spread without `inheritComponent`. Either skip the test or pass a React element type.',
- );
+ if (!render) {
+ throwMissingPropError('render');
}
const testProp = 'data-test-props-spread';
const value = randomStringValue();
+ const testId = randomStringValue();
- const wrapper = mount(React.cloneElement(element, { [testProp]: value }));
- const root = findRootComponent(wrapper, inheritComponent);
+ const { getByTestId } = render(
+ React.cloneElement(element, { [testProp]: value, 'data-testid': testId }),
+ );
- expect(root.props()).to.have.property(testProp, value);
+ expect(getByTestId(testId)).to.have.attribute(testProp, value);
});
}
@@ -203,16 +181,17 @@ export function describeRef(element: React.ReactElement, getOptions: () => Confo
describe('ref', () => {
it(`attaches the ref`, () => {
// type def in ConformanceOptions
- const { inheritComponent, mount, refInstanceof } = getOptions();
+ const { render, refInstanceof } = getOptions();
+
+ if (!render) {
+ throwMissingPropError('render');
+ }
- testRef(element, mount, (instance, wrapper) => {
- expect(instance).to.be.instanceof(refInstanceof);
+ const ref = React.createRef();
- if (inheritComponent !== undefined && (instance as HTMLElement).nodeType === 1) {
- const rootHost = findOutermostIntrinsic(wrapper);
- expect(instance).to.equal(rootHost.instance());
- }
- });
+ render(React.cloneElement(element, { ref }));
+
+ expect(ref.current).to.be.instanceof(refInstanceof);
});
});
}
@@ -571,14 +550,18 @@ function testSlotPropsCallback(element: React.ReactElement, getOptions: () => Co
function testComponentsProp(element: React.ReactElement, getOptions: () => ConformanceOptions) {
describe('prop components:', () => {
it('can render another root component with the `components` prop', () => {
- const { mount, testComponentsRootPropWith: component = 'em' } = getOptions();
- if (!mount) {
- throwMissingPropError('mount');
+ const { render, testComponentsRootPropWith: component = 'em' } = getOptions();
+ if (!render) {
+ throwMissingPropError('render');
}
- const wrapper = mount(React.cloneElement(element, { components: { Root: component } }));
+ const testId = randomStringValue();
- expect(findRootComponent(wrapper, component).exists()).to.equal(true);
+ const { getByTestId } = render(
+ React.cloneElement(element, { components: { Root: component }, 'data-testid': testId }),
+ );
+ expect(getByTestId(testId)).not.to.equal(null);
+ expect(getByTestId(testId).nodeName.toLowerCase()).to.eq(component);
});
});
}
@@ -1056,7 +1039,6 @@ function describeConformance(
only = Object.keys(fullSuite),
slots,
skip = [],
- wrapMount,
} = getOptions();
let filteredTests = Object.keys(fullSuite).filter(
@@ -1071,21 +1053,11 @@ function describeConformance(
filteredTests = filteredTests.filter((testKey) => !slotBasedTests.includes(testKey));
}
- const baseMount = createMount();
- const mount = wrapMount !== undefined ? wrapMount(baseMount) : baseMount;
-
after(runAfterHook);
- function getTestOptions(): ConformanceOptions {
- return {
- ...getOptions(),
- mount,
- };
- }
-
filteredTests.forEach((testKey) => {
const test = fullSuite[testKey];
- test(minimalElement, getTestOptions);
+ test(minimalElement, getOptions);
});
}
diff --git a/packages/test-utils/src/findOutermostIntrinsic.test.js b/packages/test-utils/src/findOutermostIntrinsic.test.js
deleted file mode 100644
index f5d9b21e4c6cb6..00000000000000
--- a/packages/test-utils/src/findOutermostIntrinsic.test.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import * as React from 'react';
-import { expect } from 'chai';
-import createMount from './createMount';
-import findOutermostIntrinsic from './findOutermostIntrinsic';
-
-describe('findOutermostIntrinsic', () => {
- const mount = createMount({ strict: null });
- const expectIntrinsic = (node, expected) => {
- const wrapper = mount(node);
- const outermostIntrinsic = findOutermostIntrinsic(wrapper);
-
- if (expected === null) {
- expect(outermostIntrinsic.exists()).to.equal(false);
- } else {
- expect(outermostIntrinsic.type()).to.equal(expected);
- expect(outermostIntrinsic.type()).to.equal(
- outermostIntrinsic.getDOMNode().nodeName.toLowerCase(),
- );
- }
- };
- const Headless = ({ children }) => children;
-
- it('returns immediate DOM nodes', () => {
- expectIntrinsic(Hello, World!
, 'div');
- });
-
- it('only returns the outermost', () => {
- expectIntrinsic(
-
- Hello, World!
- ,
- 'span',
- );
- });
-
- it('ignores components', () => {
- expectIntrinsic(
-
- Hello, World!
- ,
- 'div',
- );
- expectIntrinsic(
-
-
- Hello, World!
-
- ,
- 'div',
- );
- expectIntrinsic(
-
-
-
-
- Hello, World!
-
-
-
- ,
- 'div',
- );
- });
-
- it('can handle that no DOM node is rendered', () => {
- expectIntrinsic({false && }, null);
- });
-});
diff --git a/packages/test-utils/src/findOutermostIntrinsic.ts b/packages/test-utils/src/findOutermostIntrinsic.ts
deleted file mode 100644
index ed1c1372d9bd39..00000000000000
--- a/packages/test-utils/src/findOutermostIntrinsic.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { ReactWrapper } from 'enzyme';
-
-/**
- * checks if a given react wrapper wraps an intrinsic element i.e. a DOM node
- * @param {import('enzyme').ReactWrapper} reactWrapper
- * @returns {boolean} true if the given reactWrapper wraps an intrinsic element
- */
-export function wrapsIntrinsicElement(reactWrapper: ReactWrapper): boolean {
- return typeof reactWrapper.type() === 'string';
-}
-
-/**
- * like ReactWrapper#getDOMNode() but returns a ReactWrapper
- * @param {import('enzyme').ReactWrapper} reactWrapper
- * @returns {import('enzyme').ReactWrapper} the wrapper for the outermost DOM node
- */
-export default function findOutermostIntrinsic(reactWrapper: ReactWrapper): ReactWrapper {
- return reactWrapper.findWhere((n) => n.exists() && wrapsIntrinsicElement(n)).first();
-}
diff --git a/packages/test-utils/src/index.ts b/packages/test-utils/src/index.ts
index 6f0e1527448056..e80fc539833170 100644
--- a/packages/test-utils/src/index.ts
+++ b/packages/test-utils/src/index.ts
@@ -5,8 +5,6 @@ export { default as describeConformance } from './describeConformance';
export * from './describeConformance';
export { default as createDescribe } from './createDescribe';
export * from './createRenderer';
-export { default as createMount } from './createMount';
-export { default as findOutermostIntrinsic, wrapsIntrinsicElement } from './findOutermostIntrinsic';
export {
default as focusVisible,
simulatePointerDevice,