From 5d7e0d5735363c985b06d0817e42865ca20701b9 Mon Sep 17 00:00:00 2001 From: Harry Whorlow <79278353+harry-whorlow@users.noreply.github.com> Date: Mon, 24 Feb 2025 20:52:14 +0100 Subject: [PATCH] [material-ui][Rating] Deprecate *Props and complete `slots`, `slotProps` (#45295) --- .../migrating-from-deprecated-apis.md | 19 ++ docs/pages/material-ui/api/rating.json | 66 ++++--- docs/translations/api-docs/rating/rating.json | 19 +- packages/mui-codemod/README.md | 15 ++ .../src/deprecations/all/deprecations-all.js | 2 + .../src/deprecations/rating-props/index.js | 1 + .../deprecations/rating-props/rating-props.js | 21 ++ .../rating-props/rating-props.test.js | 16 ++ .../rating-props/test-cases/actual.js | 28 +++ .../rating-props/test-cases/expected.js | 43 ++++ .../rating-props/test-cases/theme.actual.js | 31 +++ .../rating-props/test-cases/theme.expected.js | 38 ++++ packages/mui-material/src/Rating/Rating.d.ts | 60 +++++- packages/mui-material/src/Rating/Rating.js | 185 ++++++++++++------ .../mui-material/src/Rating/Rating.test.js | 58 +++++- 15 files changed, 510 insertions(+), 92 deletions(-) create mode 100644 packages/mui-codemod/src/deprecations/rating-props/index.js create mode 100644 packages/mui-codemod/src/deprecations/rating-props/rating-props.js create mode 100644 packages/mui-codemod/src/deprecations/rating-props/rating-props.test.js create mode 100644 packages/mui-codemod/src/deprecations/rating-props/test-cases/actual.js create mode 100644 packages/mui-codemod/src/deprecations/rating-props/test-cases/expected.js create mode 100644 packages/mui-codemod/src/deprecations/rating-props/test-cases/theme.actual.js create mode 100644 packages/mui-codemod/src/deprecations/rating-props/test-cases/theme.expected.js diff --git a/docs/data/material/migration/migrating-from-deprecated-apis/migrating-from-deprecated-apis.md b/docs/data/material/migration/migrating-from-deprecated-apis/migrating-from-deprecated-apis.md index a15b375740c4e2..1a5be889798fae 100644 --- a/docs/data/material/migration/migrating-from-deprecated-apis/migrating-from-deprecated-apis.md +++ b/docs/data/material/migration/migrating-from-deprecated-apis/migrating-from-deprecated-apis.md @@ -1874,6 +1874,25 @@ The Popper's prop `componentsProps` was deprecated in favor of `slotProps`: /> ``` +## Rating + +Use the [codemod](https://github.com/mui/material-ui/tree/HEAD/packages/mui-codemod#rating-props) below to migrate the code as described in the following sections: + +```bash +npx @mui/codemod@latest deprecations/step-label-props +``` + +### IconContainerComponent + +The Rating's `IconContainerComponent` prop was deprecated in favor of `slotProps.icon.component`: + +```diff + +``` + ## Select Use the [codemod](https://github.com/mui/material-ui/tree/HEAD/packages/mui-codemod#select-classes) below to migrate the code as described in the following sections: diff --git a/docs/pages/material-ui/api/rating.json b/docs/pages/material-ui/api/rating.json index aabb90b519a7c9..0bddf19e85a3c5 100644 --- a/docs/pages/material-ui/api/rating.json +++ b/docs/pages/material-ui/api/rating.json @@ -15,7 +15,9 @@ "icon": { "type": { "name": "node" }, "default": "" }, "IconContainerComponent": { "type": { "name": "elementType" }, - "default": "function IconContainer(props) {\n const { value, ...other } = props;\n return ;\n}" + "default": "function IconContainer(props) {\n const { value, ...other } = props;\n return ;\n}", + "deprecated": true, + "deprecationInfo": "Use slotProps.icon.component instead. This prop will be removed in v7. See Migrating from deprecated APIs for more details." }, "max": { "type": { "name": "number" }, "default": "5" }, "name": { "type": { "name": "string" } }, @@ -42,6 +44,20 @@ }, "default": "'medium'" }, + "slotProps": { + "type": { + "name": "shape", + "description": "{ decimal?: func
| object, icon?: func
| object, label?: func
| object, root?: func
| object }" + }, + "default": "{}" + }, + "slots": { + "type": { + "name": "shape", + "description": "{ decimal?: elementType, icon?: elementType, label?: elementType, root?: elementType }" + }, + "default": "{}" + }, "sx": { "type": { "name": "union", @@ -56,13 +72,33 @@ "import Rating from '@mui/material/Rating';", "import { Rating } from '@mui/material';" ], - "classes": [ + "slots": [ { - "key": "decimal", - "className": "MuiRating-decimal", - "description": "Styles applied to the icon wrapping elements when decimals are necessary.", - "isGlobal": false + "name": "root", + "description": "The component used for the root slot.", + "default": "'span'", + "class": "MuiRating-root" + }, + { + "name": "label", + "description": "The component used for the label slot.", + "default": "'label'", + "class": "MuiRating-label" }, + { + "name": "icon", + "description": "The component used for the icon slot.", + "default": "'span'", + "class": "MuiRating-icon" + }, + { + "name": "decimal", + "description": "The component used fo r the decimal slot.", + "default": "'span'", + "class": "MuiRating-decimal" + } + ], + "classes": [ { "key": "disabled", "className": "Mui-disabled", @@ -75,12 +111,6 @@ "description": "State class applied to the root element if keyboard focused.", "isGlobal": true }, - { - "key": "icon", - "className": "MuiRating-icon", - "description": "Styles applied to the icon wrapping elements.", - "isGlobal": false - }, { "key": "iconActive", "className": "MuiRating-iconActive", @@ -111,12 +141,6 @@ "description": "Styles applied to the icon wrapping elements when hover.", "isGlobal": false }, - { - "key": "label", - "className": "MuiRating-label", - "description": "Styles applied to the label elements.", - "isGlobal": false - }, { "key": "labelEmptyValueActive", "className": "MuiRating-labelEmptyValueActive", @@ -129,12 +153,6 @@ "description": "Styles applied to the root element if `readOnly={true}`.", "isGlobal": true }, - { - "key": "root", - "className": "MuiRating-root", - "description": "Styles applied to the root element.", - "isGlobal": false - }, { "key": "sizeLarge", "className": "MuiRating-sizeLarge", diff --git a/docs/translations/api-docs/rating/rating.json b/docs/translations/api-docs/rating/rating.json index 44c6a0da5b2590..06dad8dcf4d9ac 100644 --- a/docs/translations/api-docs/rating/rating.json +++ b/docs/translations/api-docs/rating/rating.json @@ -41,17 +41,14 @@ "precision": { "description": "The minimum increment value change allowed." }, "readOnly": { "description": "Removes all hover effects and pointer events." }, "size": { "description": "The size of the component." }, + "slotProps": { "description": "The props used for each slot inside." }, + "slots": { "description": "The components used for each slot inside." }, "sx": { "description": "The system prop that allows defining system overrides as well as additional CSS styles." }, "value": { "description": "The rating value." } }, "classDescriptions": { - "decimal": { - "description": "Styles applied to {{nodeName}} when {{conditions}}.", - "nodeName": "the icon wrapping elements", - "conditions": "decimals are necessary" - }, "disabled": { "description": "State class applied to {{nodeName}} if {{conditions}}.", "nodeName": "the root element", @@ -62,10 +59,6 @@ "nodeName": "the root element", "conditions": "keyboard focused" }, - "icon": { - "description": "Styles applied to {{nodeName}}.", - "nodeName": "the icon wrapping elements" - }, "iconActive": { "description": "Styles applied to {{nodeName}} when {{conditions}}.", "nodeName": "the icon wrapping elements", @@ -91,7 +84,6 @@ "nodeName": "the icon wrapping elements", "conditions": "hover" }, - "label": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the label elements" }, "labelEmptyValueActive": { "description": "Styles applied to {{nodeName}} when {{conditions}}.", "nodeName": "the label of the "no value" input", @@ -102,7 +94,6 @@ "nodeName": "the root element", "conditions": "readOnly={true}" }, - "root": { "description": "Styles applied to the root element." }, "sizeLarge": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the root element", @@ -119,5 +110,11 @@ "conditions": "size=\"small\"" }, "visuallyHidden": { "description": "Visually hide an element." } + }, + "slotDescriptions": { + "decimal": "The component used fo r the decimal slot.", + "icon": "The component used for the icon slot.", + "label": "The component used for the label slot.", + "root": "The component used for the root slot." } } diff --git a/packages/mui-codemod/README.md b/packages/mui-codemod/README.md index 5d335091ca76e3..a96b975390a67f 100644 --- a/packages/mui-codemod/README.md +++ b/packages/mui-codemod/README.md @@ -1551,6 +1551,21 @@ npx @mui/codemod@latest deprecations/popper-props npx @mui/codemod@latest deprecations/outlined-input-props ``` +#### `rating-props` + +```diff + +``` + +```bash +npx @mui/codemod@next deprecations/snackbar-props +``` + #### `select-classes` JS transforms: diff --git a/packages/mui-codemod/src/deprecations/all/deprecations-all.js b/packages/mui-codemod/src/deprecations/all/deprecations-all.js index d6821261c2eac2..b5d5215f55c3f5 100644 --- a/packages/mui-codemod/src/deprecations/all/deprecations-all.js +++ b/packages/mui-codemod/src/deprecations/all/deprecations-all.js @@ -41,6 +41,7 @@ import transformerTabsProps from '../tabs-props'; import transformerTabsClasses from '../tabs-classes'; import transformDrawerProps from '../drawer-props'; import transformMenuProps from '../menu-props'; +import transformRatingProps from '../rating-props'; /** * @param {import('jscodeshift').FileInfo} file @@ -90,6 +91,7 @@ export default function deprecationsAll(file, api, options) { file.source = transformerTabsClasses(file, api, options); file.source = transformDrawerProps(file, api, options); file.source = transformMenuProps(file, api, options); + file.source = transformRatingProps(file, api, options); return file.source; } diff --git a/packages/mui-codemod/src/deprecations/rating-props/index.js b/packages/mui-codemod/src/deprecations/rating-props/index.js new file mode 100644 index 00000000000000..87798bb84bf21f --- /dev/null +++ b/packages/mui-codemod/src/deprecations/rating-props/index.js @@ -0,0 +1 @@ +export { default } from './rating-props'; diff --git a/packages/mui-codemod/src/deprecations/rating-props/rating-props.js b/packages/mui-codemod/src/deprecations/rating-props/rating-props.js new file mode 100644 index 00000000000000..5dfedb5718c256 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/rating-props/rating-props.js @@ -0,0 +1,21 @@ +import movePropIntoSlotProps from '../utils/movePropIntoSlotProps'; + +/** + * @param {import('jscodeshift').FileInfo} file + * @param {import('jscodeshift').API} api + */ +export default function transformer(file, api, options) { + const j = api.jscodeshift; + const root = j(file.source); + const printOptions = options.printOptions; + + movePropIntoSlotProps(j, { + root, + componentName: 'Rating', + propName: 'IconContainerComponent', + slotName: 'icon', + slotPropName: 'component', + }); + + return root.toSource(printOptions); +} diff --git a/packages/mui-codemod/src/deprecations/rating-props/rating-props.test.js b/packages/mui-codemod/src/deprecations/rating-props/rating-props.test.js new file mode 100644 index 00000000000000..701995ba74adf6 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/rating-props/rating-props.test.js @@ -0,0 +1,16 @@ +import { describeJscodeshiftTransform } from '../../../testUtils'; +import transform from './rating-props'; + +describe('@mui/codemod', () => { + describe('deprecations', () => { + describeJscodeshiftTransform({ + transform, + transformName: 'rating-props', + dirname: __dirname, + testCases: [ + { actual: '/test-cases/actual.js', expected: '/test-cases/expected.js' }, + { actual: '/test-cases/theme.actual.js', expected: '/test-cases/theme.expected.js' }, + ], + }); + }); +}); diff --git a/packages/mui-codemod/src/deprecations/rating-props/test-cases/actual.js b/packages/mui-codemod/src/deprecations/rating-props/test-cases/actual.js new file mode 100644 index 00000000000000..c9449d349fa8dc --- /dev/null +++ b/packages/mui-codemod/src/deprecations/rating-props/test-cases/actual.js @@ -0,0 +1,28 @@ +import Rating from '@mui/material/Rating'; +import { Rating as MyRating } from '@mui/material'; + +; +; +; +; +; + +// should skip non MUI components +; diff --git a/packages/mui-codemod/src/deprecations/rating-props/test-cases/expected.js b/packages/mui-codemod/src/deprecations/rating-props/test-cases/expected.js new file mode 100644 index 00000000000000..1616afdeea4f6c --- /dev/null +++ b/packages/mui-codemod/src/deprecations/rating-props/test-cases/expected.js @@ -0,0 +1,43 @@ +import Rating from '@mui/material/Rating'; +import { Rating as MyRating } from '@mui/material'; + +; +; +; +; +; + +// should skip non MUI components +; diff --git a/packages/mui-codemod/src/deprecations/rating-props/test-cases/theme.actual.js b/packages/mui-codemod/src/deprecations/rating-props/test-cases/theme.actual.js new file mode 100644 index 00000000000000..bc07ebec6c44f3 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/rating-props/test-cases/theme.actual.js @@ -0,0 +1,31 @@ +fn({ + MuiRating: { + defaultProps: { + IconContainerComponent: CustomContainer, + }, + }, +}); + +fn({ + MuiRating: { + defaultProps: { + IconContainerComponent: CustomContainer, + slotProps: { + root: {}, + }, + }, + }, +}); + +fn({ + MuiRating: { + defaultProps: { + IconContainerComponent: CustomContainer, + slotProps: { + icon: { + id: 'my-rating-icon', + }, + }, + }, + }, +}); diff --git a/packages/mui-codemod/src/deprecations/rating-props/test-cases/theme.expected.js b/packages/mui-codemod/src/deprecations/rating-props/test-cases/theme.expected.js new file mode 100644 index 00000000000000..31a66ed6255492 --- /dev/null +++ b/packages/mui-codemod/src/deprecations/rating-props/test-cases/theme.expected.js @@ -0,0 +1,38 @@ +fn({ + MuiRating: { + defaultProps: { + slotProps: { + icon: { + component: CustomContainer + } + }, + }, + }, +}); + +fn({ + MuiRating: { + defaultProps: { + slotProps: { + root: {}, + + icon: { + component: CustomContainer + } + } + }, + }, +}); + +fn({ + MuiRating: { + defaultProps: { + slotProps: { + icon: { + id: 'my-rating-icon', + component: CustomContainer + }, + } + }, + }, +}); diff --git a/packages/mui-material/src/Rating/Rating.d.ts b/packages/mui-material/src/Rating/Rating.d.ts index 7af3ee1e29e0fb..fa7daf1781d5ea 100644 --- a/packages/mui-material/src/Rating/Rating.d.ts +++ b/packages/mui-material/src/Rating/Rating.d.ts @@ -4,6 +4,7 @@ import { OverridableStringUnion } from '@mui/types'; import { Theme } from '..'; import { RatingClasses } from './ratingClasses'; import { OverridableComponent, OverrideProps } from '../OverridableComponent'; +import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types'; export interface IconContainerProps extends React.HTMLAttributes { value: number; @@ -11,7 +12,61 @@ export interface IconContainerProps extends React.HTMLAttributes; + /** + * Props forwarded to the label slot. + * By default, the avaible props are based on the label element. + */ + label: SlotProps<'label', RatingLabelSlotPropsOverrides, RatingOwnerState>; + /** + * Props forwarded to the icon slot. + * By default, the avaible props are based on the span element. + */ + icon: SlotProps<'span', RatingIconSlotPropsOverrides, RatingOwnerState>; + /** + * Props forwarded to the decimal slot. + * By default, the avaible props are based on the span element. + */ + decimal: SlotProps<'span', RatingDecimalSlotPropsOverrides, RatingOwnerState>; + } +>; + +export interface RatingOwnProps extends RatingSlotsAndSlotProps { /** * Override or extend the styles applied to the component. */ @@ -60,6 +115,7 @@ export interface RatingOwnProps { icon?: React.ReactNode; /** * The component containing the icon. + * @deprecated Use `slotProps.icon.component` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. * @default function IconContainer(props) { * const { value, ...other } = props; * return ; @@ -114,6 +170,8 @@ export interface RatingOwnProps { value?: number | null; } +export interface RatingOwnerState extends Omit {} + export type RatingTypeMap< AdditionalProps = {}, RootComponent extends React.ElementType = 'span', diff --git a/packages/mui-material/src/Rating/Rating.js b/packages/mui-material/src/Rating/Rating.js index d6b5f761603982..cab53be3e0a84d 100644 --- a/packages/mui-material/src/Rating/Rating.js +++ b/packages/mui-material/src/Rating/Rating.js @@ -16,6 +16,7 @@ import memoTheme from '../utils/memoTheme'; import { useDefaultProps } from '../DefaultPropsProvider'; import slotShouldForwardProp from '../styles/slotShouldForwardProp'; import ratingClasses, { getRatingUtilityClass } from './ratingClasses'; +import useSlot from '../utils/useSlot'; function getDecimalPrecision(num) { const decimalPart = num.toString().split('.')[1]; @@ -235,6 +236,8 @@ function RatingItem(props) { ownerState, ratingValue, ratingValueRounded, + slots = {}, + slotProps = {}, } = props; const isFilled = highlightSelectedOnly ? itemValue === ratingValue : itemValue <= ratingValue; @@ -248,28 +251,51 @@ function RatingItem(props) { // More details: https://github.com/mui/material-ui/issues/40997 const id = `${name}-${useId()}`; + const externalForwardedProps = { + slots, + slotProps, + }; + + const [IconSlot, iconSlotProps] = useSlot('icon', { + elementType: RatingIcon, + className: clsx(classes.icon, { + [classes.iconEmpty]: !isFilled, + [classes.iconFilled]: isFilled, + [classes.iconHover]: isHovered, + [classes.iconFocus]: isFocused, + [classes.iconActive]: isActive, + }), + externalForwardedProps, + ownerState: { + ...ownerState, + iconEmpty: !isFilled, + iconFilled: isFilled, + iconHover: isHovered, + iconFocus: isFocused, + iconActive: isActive, + }, + additionalProps: { + value: itemValue, + }, + internalForwardedProps: { + // TODO: remove this in v7 because `IconContainerComponent` is deprecated + // only forward if `slots.icon` is NOT provided + as: IconContainerComponent, + }, + }); + + const [LabelSlot, labelSlotProps] = useSlot('label', { + elementType: RatingLabel, + externalForwardedProps, + ownerState: { ...ownerState, emptyValueFocused: undefined }, + additionalProps: { + style: labelProps?.style, + htmlFor: id, + }, + }); + const container = ( - - {emptyIcon && !isFilled ? emptyIcon : icon} - + {emptyIcon && !isFilled ? emptyIcon : icon} ); if (readOnly) { @@ -278,14 +304,10 @@ function RatingItem(props) { return ( - + {container} {getLabelText(itemValue)} - + ; @@ -357,6 +381,8 @@ const Rating = React.forwardRef(function Rating(inProps, ref) { readOnly = false, size = 'medium', value: valueProp, + slots = {}, + slotProps = {}, ...other } = props; @@ -524,25 +550,54 @@ const Rating = React.forwardRef(function Rating(inProps, ref) { const classes = useUtilityClasses(ownerState); + const externalForwardedProps = { + slots, + slotProps, + }; + + const [RootSlot, rootSlotProps] = useSlot('root', { + ref: handleRef, + className: clsx(classes.root, className), + elementType: RatingRoot, + externalForwardedProps: { + ...externalForwardedProps, + ...other, + component, + }, + getSlotProps: (handlers) => ({ + ...handlers, + onMouseMove: (event) => { + handleMouseMove(event); + handlers.onMouseMove?.(event); + }, + onMouseLeave: (event) => { + handleMouseLeave(event); + handlers.onMouseLeave?.(event); + }, + }), + ownerState, + additionalProps: { + role: readOnly ? 'img' : null, + 'aria-label': readOnly ? getLabelText(value) : null, + }, + }); + + const [LabelSlot, labelSlotProps] = useSlot('label', { + className: clsx(classes.label, classes.labelEmptyValue), + elementType: RatingLabel, + externalForwardedProps, + ownerState, + }); + + const [DecimalSlot, decimalSlotProps] = useSlot('decimal', { + className: classes.decimal, + elementType: RatingDecimal, + externalForwardedProps, + ownerState, + }); + return ( - + {Array.from(new Array(max)).map((_, index) => { const itemValue = index + 1; @@ -565,16 +620,18 @@ const Rating = React.forwardRef(function Rating(inProps, ref) { ratingValueRounded: valueRounded, readOnly, ownerState, + slots, + slotProps, }; const isActive = itemValue === Math.ceil(value) && (hover !== -1 || focus !== -1); if (precision < 1) { const items = Array.from(new Array(1 / precision)); return ( - {items.map(($, indexDecimal) => { @@ -606,7 +663,7 @@ const Rating = React.forwardRef(function Rating(inProps, ref) { /> ); })} - + ); } @@ -620,10 +677,7 @@ const Rating = React.forwardRef(function Rating(inProps, ref) { ); })} {!readOnly && !disabled && ( - + {emptyLabelText} - + )} - + ); }); @@ -708,6 +762,7 @@ Rating.propTypes /* remove-proptypes */ = { icon: PropTypes.node, /** * The component containing the icon. + * @deprecated Use `slotProps.icon.component` instead. This prop will be removed in v7. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. * @default function IconContainer(props) { * const { value, ...other } = props; * return ; @@ -773,6 +828,26 @@ Rating.propTypes /* remove-proptypes */ = { PropTypes.oneOf(['small', 'medium', 'large']), PropTypes.string, ]), + /** + * The props used for each slot inside. + * @default {} + */ + slotProps: PropTypes.shape({ + decimal: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + icon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + label: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + }), + /** + * The components used for each slot inside. + * @default {} + */ + slots: PropTypes.shape({ + decimal: PropTypes.elementType, + icon: PropTypes.elementType, + label: PropTypes.elementType, + root: PropTypes.elementType, + }), /** * The system prop that allows defining system overrides as well as additional CSS styles. */ diff --git a/packages/mui-material/src/Rating/Rating.test.js b/packages/mui-material/src/Rating/Rating.test.js index 7c84b7e72ce4fb..3f9e315e63bc78 100644 --- a/packages/mui-material/src/Rating/Rating.test.js +++ b/packages/mui-material/src/Rating/Rating.test.js @@ -9,7 +9,7 @@ import describeConformance from '../../test/describeConformance'; describe('', () => { const { render } = createRenderer(); - describeConformance(, () => ({ + describeConformance(, () => ({ classes, inheritComponent: 'span', render, @@ -18,9 +18,55 @@ describe('', () => { testDeepOverrides: { slotName: 'label', slotClassName: classes.label }, testStateOverrides: { prop: 'size', value: 'small', styleKey: 'sizeSmall' }, refInstanceof: window.HTMLSpanElement, + slots: { + root: { + expectedClassName: classes.root, + }, + label: { + expectedClassName: classes.label, + }, + }, skip: ['componentsProp'], })); + describeConformance(, () => ({ + render, + refInstanceof: window.HTMLSpanElement, + slots: { + icon: { + expectedClassName: classes.icon, + }, + }, + only: [ + 'slotsProp', + 'slotPropsProp', + 'slotPropsCallback', + 'slotPropsCallbackWithPropsAsOwnerState', + ], + })); + + function CustomDecimal({ iconActive, ownerState, ...props }) { + return ; + } + + describeConformance(, () => ({ + render, + refInstanceof: window.HTMLSpanElement, + slots: { + decimal: { + expectedClassName: classes.decimal, + testWithComponent: CustomDecimal, + testWithElement: CustomDecimal, + }, + }, + only: [ + 'slotsProp', + 'slotPropsProp', + 'slotPropsCallback', + 'slotPropsCallbackWithPropsAsOwnerState', + ], + })); + it('should render', () => { const { container } = render(); @@ -222,6 +268,16 @@ describe('', () => { } }); + it('should be able to replace the icon', () => { + function Icon(props) { + return ; + } + render(); + + expect(screen.getByTestId('custom')).to.have.property('tagName', 'I'); + expect(screen.getByTestId('custom')).to.have.class(classes.icon); + }); + describe('prop: readOnly', () => { it('renders a role="img"', () => { render();