diff --git a/.circleci/config.yml b/.circleci/config.yml index 510b10c7bbc370..e3e309d723f511 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -269,7 +269,10 @@ jobs: command: xvfb-run pnpm test:regressions - run: name: A11y results committed? - command: git add -A && git diff --exit-code --staged + # The react@17/18/next jobs pin React via `package-overrides`, which rewrites + # package.json/pnpm-lock.yaml. Exclude those so this check still verifies the a11y + # results on every job without tripping over the pinned dependencies. + command: git add -A -- . ':(exclude)package.json' ':(exclude)pnpm-lock.yaml' && git diff --exit-code --staged - run: name: Upload screenshots to Argos CI command: pnpm test:argos diff --git a/docs/pages/material-ui/api/list-item.json b/docs/pages/material-ui/api/list-item.json index 8c2aa1f1a31d2b..82302285146abc 100644 --- a/docs/pages/material-ui/api/list-item.json +++ b/docs/pages/material-ui/api/list-item.json @@ -15,7 +15,7 @@ "slotProps": { "type": { "name": "shape", - "description": "{ root?: object, secondaryAction?: func
| object }" + "description": "{ root?: func
| object, secondaryAction?: func
| object }" }, "default": "{}" }, diff --git a/packages/mui-material/src/ListItem/ListItem.d.ts b/packages/mui-material/src/ListItem/ListItem.d.ts index d58e8f12e9c668..740365ff2c7989 100644 --- a/packages/mui-material/src/ListItem/ListItem.d.ts +++ b/packages/mui-material/src/ListItem/ListItem.d.ts @@ -5,6 +5,8 @@ import { OverridableComponent, OverrideProps } from '../OverridableComponent'; import { ListItemClasses } from './listItemClasses'; import { SlotProps } from '../utils/types'; +export interface ListItemRootSlotPropsOverrides {} + export interface ListItemSecondaryActionSlotPropsOverrides {} /** @@ -66,7 +68,13 @@ export interface ListItemOwnProps extends ListItemBaseProps { */ slotProps?: | { - root?: React.HTMLAttributes | undefined; + root?: + | SlotProps< + React.ElementType>, + ListItemRootSlotPropsOverrides, + ListItemOwnerState + > + | undefined; secondaryAction?: | SlotProps< React.ElementType>, diff --git a/packages/mui-material/src/ListItem/ListItem.js b/packages/mui-material/src/ListItem/ListItem.js index bc9d4b0946a4c3..a9c11708d6fcee 100644 --- a/packages/mui-material/src/ListItem/ListItem.js +++ b/packages/mui-material/src/ListItem/ListItem.js @@ -271,7 +271,7 @@ ListItem.propTypes /* remove-proptypes */ = { * @default {} */ slotProps: PropTypes.shape({ - root: PropTypes.object, + root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), secondaryAction: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), }), /** diff --git a/packages/mui-material/src/Tab/Tab.test.js b/packages/mui-material/src/Tab/Tab.test.js index 46f5500ebfdc82..c103df7d24ff06 100644 --- a/packages/mui-material/src/Tab/Tab.test.js +++ b/packages/mui-material/src/Tab/Tab.test.js @@ -16,9 +16,17 @@ describe('', () => { classes, inheritComponent: ButtonBase, render: (node) => { - const value = node.props.value ?? 0; + // `Tab` must be a direct child of `Tabs`, which injects state into its children via + // `cloneElement`. Some conformance tests wrap the element(s) in a provider (e.g. + // `ThemeProvider`), so we slot `Tabs` *inside* the wrapper around the `Tab`s. Otherwise + // `Tabs` would clone the provider with `Tab`-internal props (`fullWidth`, `indicator`, …), + // tripping the provider's `exactProp` check under React 18. + // TODO: React 19 dropped runtime propType/`exactProp` validation, so once we stop testing + // React 18 this can revert to rendering `node` directly: `{node}`. + const isWrapped = node.type !== Tab; + const tabs = {isWrapped ? node.props.children : node}; const { container, ...other } = render( - {React.cloneElement(node, { value })}, + isWrapped ? React.cloneElement(node, undefined, tabs) : tabs, ); return { diff --git a/packages/mui-utils/src/useValueAsRef/useValueAsRef.test.tsx b/packages/mui-utils/src/useValueAsRef/useValueAsRef.test.tsx index 83bd6490ca693c..9b419b2151db26 100644 --- a/packages/mui-utils/src/useValueAsRef/useValueAsRef.test.tsx +++ b/packages/mui-utils/src/useValueAsRef/useValueAsRef.test.tsx @@ -33,25 +33,27 @@ describe('useValueAsRef', () => { }); it('returns the same ref object across renders', () => { - let firstRef: object | undefined; - let nextRef: object | undefined; + let capturedRef: object | undefined; function Test(props: { value: number }) { - const valueRef = useValueAsRef(props.value); - - if (firstRef === undefined) { - firstRef = valueRef; - } else { - nextRef = valueRef; - } - + capturedRef = useValueAsRef(props.value); return null; } + // Collect the committed ref after each render settles. Reading inside the render body + // instead would capture React 18 StrictMode's discarded initial-mount object (a distinct + // transient `ValueRef`), which is never the one the component ends up using. + const refs: Array = []; const { setProps } = render(); - + refs.push(capturedRef); setProps({ value: 2 }); - - expect(nextRef).to.equal(firstRef); + refs.push(capturedRef); + setProps({ value: 3 }); + refs.push(capturedRef); + + expect(refs.length).to.be.at.least(2); + refs.forEach((ref) => { + expect(ref).to.equal(refs[0]); + }); }); });