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