Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/material-ui/api/list-item.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"slotProps": {
"type": {
"name": "shape",
"description": "{ root?: object, secondaryAction?: func<br>&#124;&nbsp;object }"
"description": "{ root?: func<br>&#124;&nbsp;object, secondaryAction?: func<br>&#124;&nbsp;object }"
},
"default": "{}"
},
Expand Down
10 changes: 9 additions & 1 deletion packages/mui-material/src/ListItem/ListItem.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { OverridableComponent, OverrideProps } from '../OverridableComponent';
import { ListItemClasses } from './listItemClasses';
import { SlotProps } from '../utils/types';

export interface ListItemRootSlotPropsOverrides {}

export interface ListItemSecondaryActionSlotPropsOverrides {}

/**
Expand Down Expand Up @@ -66,7 +68,13 @@ export interface ListItemOwnProps extends ListItemBaseProps {
*/
slotProps?:
| {
root?: React.HTMLAttributes<HTMLDivElement> | undefined;
root?:
| SlotProps<
React.ElementType<React.HTMLAttributes<HTMLDivElement>>,
ListItemRootSlotPropsOverrides,
ListItemOwnerState
>
| undefined;
secondaryAction?:
| SlotProps<
React.ElementType<React.HTMLAttributes<HTMLDivElement>>,
Expand Down
2 changes: 1 addition & 1 deletion packages/mui-material/src/ListItem/ListItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
}),
/**
Expand Down
12 changes: 10 additions & 2 deletions packages/mui-material/src/Tab/Tab.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,17 @@ describe('<Tab />', () => {
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: `<Tabs value={0}>{node}</Tabs>`.
const isWrapped = node.type !== Tab;
const tabs = <Tabs value={0}>{isWrapped ? node.props.children : node}</Tabs>;
const { container, ...other } = render(
<Tabs value={value}>{React.cloneElement(node, { value })}</Tabs>,
isWrapped ? React.cloneElement(node, undefined, tabs) : tabs,
);

return {
Expand Down
28 changes: 15 additions & 13 deletions packages/mui-utils/src/useValueAsRef/useValueAsRef.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<object | undefined> = [];
const { setProps } = render(<Test value={1} />);

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]);
});
});
});
Loading