Skip to content

Commit b902ae0

Browse files
committed
[test] Fix react@18/next nightly CI matrix failures
- test_regressions: skip the 'A11y results committed?' git-clean check on the react@17/18/next jobs, which pin React via package-overrides and so dirty package.json/pnpm-lock.yaml. - ListItem: type slotProps.root as SlotProps so it accepts a callback (matches the conformance slotProps-callback test); regenerated propTypes + API docs. - Tab: the conformance theme tests wrap the element in a provider; slot Tabs inside that wrapper so Tabs clones the Tab(s), not the provider. Cloning the provider with Tab-internal props (fullWidth, indicator, …) tripped its exactProp check under React 18. - useValueAsRef: compare the ref captured after each render settles so React 18 StrictMode's discarded initial-mount object doesn't skew the identity check.
1 parent aaaf3c1 commit b902ae0

6 files changed

Lines changed: 45 additions & 19 deletions

File tree

.circleci/config.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,15 @@ jobs:
269269
command: xvfb-run pnpm test:regressions
270270
- run:
271271
name: A11y results committed?
272-
command: git add -A && git diff --exit-code --staged
272+
# The a11y results are React-version independent, so we only verify them on the
273+
# default React job. The react@17/18/next jobs pin React via `package-overrides`,
274+
# which rewrites package.json/pnpm-lock.yaml and would otherwise trip this check.
275+
command: |
276+
if [ "<< parameters.react-version >>" != "stable" ]; then
277+
echo "Skipping a11y results check for react@<< parameters.react-version >>."
278+
exit 0
279+
fi
280+
git add -A && git diff --exit-code --staged
273281
- run:
274282
name: Upload screenshots to Argos CI
275283
command: pnpm test:argos

docs/pages/material-ui/api/list-item.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"slotProps": {
1616
"type": {
1717
"name": "shape",
18-
"description": "{ root?: object, secondaryAction?: func<br>&#124;&nbsp;object }"
18+
"description": "{ root?: func<br>&#124;&nbsp;object, secondaryAction?: func<br>&#124;&nbsp;object }"
1919
},
2020
"default": "{}"
2121
},

packages/mui-material/src/ListItem/ListItem.d.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { OverridableComponent, OverrideProps } from '../OverridableComponent';
55
import { ListItemClasses } from './listItemClasses';
66
import { SlotProps } from '../utils/types';
77

8+
export interface ListItemRootSlotPropsOverrides {}
9+
810
export interface ListItemSecondaryActionSlotPropsOverrides {}
911

1012
/**
@@ -66,7 +68,13 @@ export interface ListItemOwnProps extends ListItemBaseProps {
6668
*/
6769
slotProps?:
6870
| {
69-
root?: React.HTMLAttributes<HTMLDivElement> | undefined;
71+
root?:
72+
| SlotProps<
73+
React.ElementType<React.HTMLAttributes<HTMLDivElement>>,
74+
ListItemRootSlotPropsOverrides,
75+
ListItemOwnerState
76+
>
77+
| undefined;
7078
secondaryAction?:
7179
| SlotProps<
7280
React.ElementType<React.HTMLAttributes<HTMLDivElement>>,

packages/mui-material/src/ListItem/ListItem.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ ListItem.propTypes /* remove-proptypes */ = {
271271
* @default {}
272272
*/
273273
slotProps: PropTypes.shape({
274-
root: PropTypes.object,
274+
root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
275275
secondaryAction: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
276276
}),
277277
/**

packages/mui-material/src/Tab/Tab.test.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,17 @@ describe('<Tab />', () => {
1616
classes,
1717
inheritComponent: ButtonBase,
1818
render: (node) => {
19-
const value = node.props.value ?? 0;
19+
// `Tab` must be a direct child of `Tabs`, which injects state into its children via
20+
// `cloneElement`. Some conformance tests wrap the element(s) in a provider (e.g.
21+
// `ThemeProvider`), so we slot `Tabs` *inside* the wrapper around the `Tab`s. Otherwise
22+
// `Tabs` would clone the provider with `Tab`-internal props (`fullWidth`, `indicator`, …),
23+
// tripping the provider's `exactProp` check under React 18.
24+
// TODO: React 19 dropped runtime propType/`exactProp` validation, so once we stop testing
25+
// React 18 this can revert to rendering `node` directly: `<Tabs value={0}>{node}</Tabs>`.
26+
const isWrapped = node.type !== Tab;
27+
const tabs = <Tabs value={0}>{isWrapped ? node.props.children : node}</Tabs>;
2028
const { container, ...other } = render(
21-
<Tabs value={value}>{React.cloneElement(node, { value })}</Tabs>,
29+
isWrapped ? React.cloneElement(node, undefined, tabs) : tabs,
2230
);
2331

2432
return {

packages/mui-utils/src/useValueAsRef/useValueAsRef.test.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,25 +33,27 @@ describe('useValueAsRef', () => {
3333
});
3434

3535
it('returns the same ref object across renders', () => {
36-
let firstRef: object | undefined;
37-
let nextRef: object | undefined;
36+
let capturedRef: object | undefined;
3837

3938
function Test(props: { value: number }) {
40-
const valueRef = useValueAsRef(props.value);
41-
42-
if (firstRef === undefined) {
43-
firstRef = valueRef;
44-
} else {
45-
nextRef = valueRef;
46-
}
47-
39+
capturedRef = useValueAsRef(props.value);
4840
return null;
4941
}
5042

43+
// Collect the committed ref after each render settles. Reading inside the render body
44+
// instead would capture React 18 StrictMode's discarded initial-mount object (a distinct
45+
// transient `ValueRef`), which is never the one the component ends up using.
46+
const refs: Array<object | undefined> = [];
5147
const { setProps } = render(<Test value={1} />);
52-
48+
refs.push(capturedRef);
5349
setProps({ value: 2 });
54-
55-
expect(nextRef).to.equal(firstRef);
50+
refs.push(capturedRef);
51+
setProps({ value: 3 });
52+
refs.push(capturedRef);
53+
54+
expect(refs.length).to.be.at.least(2);
55+
refs.forEach((ref) => {
56+
expect(ref).to.equal(refs[0]);
57+
});
5658
});
5759
});

0 commit comments

Comments
 (0)