Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core] Set up eslint-plugin-testing-library #42909

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
15 changes: 15 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,21 @@ module.exports = {
'react/no-unused-prop-types': 'off',
},
},
{
files: [
// matching the pattern of the test runner
'*.test.mjs',
'*.test.js',
'*.test.ts',
'*.test.tsx',
],
excludedFiles: ['packages/markdown/**/*', 'test/e2e/**/*', 'test/regressions/**/*'],
extends: ['plugin:testing-library/react'],
rules: {
'testing-library/no-container': 'off',
'testing-library/prefer-screen-queries': 'off',
},
},
Comment on lines +279 to +293
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Configuring the ESLint plugin using overrides so it only runs on test files. See docs for more info https://github.com/testing-library/eslint-plugin-testing-library?tab=readme-ov-file#eslint-overrides

{
files: ['docs/src/modules/components/**/*.js'],
rules: {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
"eslint-plugin-react": "^7.34.4",
"eslint-plugin-react-compiler": "0.0.0-experimental-0998c1e-20240625",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-testing-library": "^6.2.2",
aarongarciah marked this conversation as resolved.
Show resolved Hide resolved
"fast-glob": "^3.3.2",
"fs-extra": "^11.2.0",
"globby": "^14.0.2",
Expand Down
1 change: 1 addition & 0 deletions packages-internal/test-utils/src/mochaHooks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ describe('mochaHooks', () => {
// not wrapped in act()
unsafeSetState(1);
// make sure effects are flushed
// eslint-disable-next-line testing-library/no-unnecessary-act
act(() => {});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be async as well?

Suggested change
act(() => {});
await act(async () => {});

Perhaps it could make sense to create a await flushEffects() helper?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I initially started to await all act but realized there were too many changes. I decided to await all act calls in another PR, but if you think it makes sense to make it all at once I'm ok with it, too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it would be good to do separate as far as I'm concerned.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it could make sense to create a await flushEffects() helper?

There is one: flushMicrotasks in internal-test-utils

});

Expand Down
17 changes: 7 additions & 10 deletions packages/mui-base/src/Button/Button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ describe('<Button />', () => {
const { getByRole } = render(<Button focusableWhenDisabled disabled />);

const button = getByRole('button');

act(() => {
button.focus();
});
Expand All @@ -83,14 +84,12 @@ describe('<Button />', () => {
const button = getByRole('button');
act(() => {
button.focus();
});

act(() => {
button.click();
fireEvent.keyDown(button, { key: 'Enter' });
fireEvent.keyUp(button, { key: ' ' });
});

fireEvent.keyDown(button, { key: 'Enter' });
fireEvent.keyUp(button, { key: ' ' });

expect(handleClick.callCount).to.equal(0);
});
});
Expand Down Expand Up @@ -129,14 +128,12 @@ describe('<Button />', () => {
const button = getByRole('button');
act(() => {
button.focus();
});

act(() => {
button.click();
fireEvent.keyDown(button, { key: 'Enter' });
fireEvent.keyUp(button, { key: ' ' });
});

fireEvent.keyDown(button, { key: 'Enter' });
fireEvent.keyUp(button, { key: ' ' });

expect(handleClick.callCount).to.equal(0);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ describe('<ClickAwayListener />', () => {
* React bug: https://github.com/facebook/react/issues/20074
*/
function render(...args) {
// eslint-disable-next-line testing-library/render-result-naming-convention
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The render-result-naming-convention rule seems to warn whenever it finds a function/method name that contains "render" 😅

Copy link
Member

@Janpot Janpot Jul 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh 🤦 . Maybe we should just disable this one rule?

const result = clientRender(...args);

This comment was marked as outdated.

clock.tick(0);
return result;
Expand Down
1 change: 1 addition & 0 deletions packages/mui-base/src/Dropdown/Dropdown.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('<Dropdown />', () => {
element: React.ReactElement<any, string | React.JSXElementConstructor<any>>,
options?: RenderOptions,
): Promise<MuiRenderResult> {
// eslint-disable-next-line testing-library/render-result-naming-convention
const rendered = internalRender(element, options);
await flushMicrotasks();
return rendered;
Expand Down
1 change: 1 addition & 0 deletions packages/mui-base/src/Menu/Menu.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe('<Menu />', () => {
element: React.ReactElement<any, string | React.JSXElementConstructor<any>>,
options?: RenderOptions,
): Promise<MuiRenderResult> {
// eslint-disable-next-line testing-library/render-result-naming-convention
const rendered = internalRender(element, options);
await flushMicrotasks();
return rendered;
Expand Down
2 changes: 1 addition & 1 deletion packages/mui-base/src/MenuButton/MenuButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ describe('<MenuButton />', () => {
button.focus();
});

expect(document.activeElement).to.equal(button);
expect(button).toHaveFocus();
});
});

Expand Down
24 changes: 13 additions & 11 deletions packages/mui-base/src/Select/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
MuiRenderResult,
RenderOptions,
flushMicrotasks,
within,
} from '@mui/internal-test-utils';
import userEvent from '@testing-library/user-event';
import { Select, SelectListboxSlotProps, selectClasses } from '@mui/base/Select';
Expand All @@ -28,6 +29,7 @@ describe('<Select />', () => {
element: React.ReactElement<any, string | React.JSXElementConstructor<any>>,
options?: RenderOptions,
): Promise<MuiRenderResult> {
// eslint-disable-next-line testing-library/render-result-naming-convention
const rendered = internalRender(element, options);
await flushMicrotasks();
return rendered;
Expand Down Expand Up @@ -918,15 +920,15 @@ describe('<Select />', () => {
});

it('renders a zero-width space when there is no selected value nor placeholder and renderValue is not provided', async () => {
const { getByRole } = await render(
await render(
<Select>
<Option value={1}>One</Option>
<Option value={2}>Two</Option>
</Select>,
);

const select = getByRole('combobox');
const zws = select.querySelector('.notranslate');
const select = screen.getByRole('combobox');
const zws = within(select).getByText('​'); // zero-width space

expect(zws).not.to.equal(null);
});
Expand Down Expand Up @@ -1160,7 +1162,7 @@ describe('<Select />', () => {
);

const input = getAllByRole('textbox')[0];
expect(document.activeElement).to.equal(input);
expect(input).toHaveFocus();
});

it('scrolls to initially highlighted option after opening', async function test() {
Expand Down Expand Up @@ -1219,7 +1221,7 @@ describe('<Select />', () => {
);

const select = getByRole('combobox');
expect(document.activeElement).to.equal(select);
expect(select).toHaveFocus();
});
});

Expand Down Expand Up @@ -1346,15 +1348,15 @@ describe('<Select />', () => {
describe('browser autofill', () => {
it('sets value and calls external onChange when browser autofills', async () => {
const onChangeHandler = spy();
const { container } = await render(
await render(
<Select onChange={onChangeHandler} defaultValue="germany" autoComplete="country">
<Option value="france">France</Option>
<Option value="germany">Germany</Option>
<Option value="china">China</Option>
</Select>,
);

const hiddenInput = container.querySelector('[autocomplete="country"]');
const hiddenInput = screen.getByRole('textbox', { hidden: true });

expect(hiddenInput).not.to.eq(null);
expect(hiddenInput).to.have.value('germany');
Expand All @@ -1372,15 +1374,15 @@ describe('<Select />', () => {

it('does not set value when browser autofills invalid value', async () => {
const onChangeHandler = spy();
const { container } = await render(
await render(
<Select onChange={onChangeHandler} defaultValue="germany" autoComplete="country">
<Option value="france">France</Option>
<Option value="germany">Germany</Option>
<Option value="china">China</Option>
</Select>,
);

const hiddenInput = container.querySelector('[autocomplete="country"]');
const hiddenInput = screen.getByRole('textbox', { hidden: true });

expect(hiddenInput).not.to.eq(null);
expect(hiddenInput).to.have.value('germany');
Expand All @@ -1397,15 +1399,15 @@ describe('<Select />', () => {

it('clears value and calls external onChange when browser clears autofill', async () => {
const onChangeHandler = spy();
const { container } = await render(
await render(
<Select onChange={onChangeHandler} defaultValue="germany" autoComplete="country">
<Option value="france">France</Option>
<Option value="germany">Germany</Option>
<Option value="china">China</Option>
</Select>,
);

const hiddenInput = container.querySelector('[autocomplete="country"]');
const hiddenInput = screen.getByRole('textbox', { hidden: true });

expect(hiddenInput).not.to.eq(null);
expect(hiddenInput).to.have.value('germany');
Expand Down
1 change: 1 addition & 0 deletions packages/mui-base/src/Snackbar/Snackbar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('<Snackbar />', () => {
* React bug: https://github.com/facebook/react/issues/20074
*/
function render(...args: [React.ReactElement<any>]) {
// eslint-disable-next-line testing-library/render-result-naming-convention
const result = clientRender(...args);
clock.tick(0);
return result;
Expand Down
18 changes: 8 additions & 10 deletions packages/mui-base/src/Unstable_NumberInput/NumberInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -357,21 +357,21 @@ describe('<NumberInput />', () => {
const incrementButton = getByTestId('increment-btn');
const decrementButton = getByTestId('decrement-btn');

expect(document.activeElement).to.equal(document.body);
expect(document.body).toHaveFocus();

await userEvent.click(incrementButton);

expect(document.activeElement).to.equal(input);
expect(input).toHaveFocus();

act(() => {
input.blur();
});

expect(document.activeElement).to.equal(document.body);
expect(document.body).toHaveFocus();

await userEvent.click(decrementButton);

expect(document.activeElement).to.equal(input);
expect(input).toHaveFocus();
});
});

Expand Down Expand Up @@ -554,13 +554,13 @@ describe('<NumberInput />', () => {
const { getByRole } = render(<NumberInput />);

const input = getByRole('textbox') as HTMLInputElement;
expect(document.activeElement).to.equal(document.body);
expect(document.body).toHaveFocus();

await userEvent.keyboard('[Tab]');
expect(document.activeElement).to.equal(input);
expect(input).toHaveFocus();

await userEvent.keyboard('[Tab]');
expect(document.activeElement).to.equal(document.body);
expect(document.body).toHaveFocus();
});
});

Expand Down Expand Up @@ -600,9 +600,7 @@ describe('<NumberInput />', () => {
const input = getByRole('textbox') as HTMLInputElement;
const button = getByTestId('button') as HTMLButtonElement;

act(() => {
fireEvent.click(button);
});
fireEvent.click(button);

await userEvent.click(input);
expect(input.value).to.equal('20');
Expand Down
1 change: 1 addition & 0 deletions packages/mui-base/src/Unstable_Popup/Popup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ describe('<Popup />', () => {
describeConformanceUnstyled(<Popup {...defaultProps} />, () => ({
inheritComponent: 'div',
render: async (...renderArgs) => {
// eslint-disable-next-line testing-library/render-result-naming-convention
const result = render(...renderArgs);
await waitForPosition();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ describe('useNumberInput', () => {
expect(handleChange.callCount).to.equal(0);

await userEvent.keyboard('[Tab]');
expect(document.activeElement).to.equal(document.body);
expect(document.body).toHaveFocus();

expect(handleChange.callCount).to.equal(1);
expect(handleChange.args[0][1]).to.equal(34);
Expand Down Expand Up @@ -180,7 +180,7 @@ describe('useNumberInput', () => {
await userEvent.keyboard('9');

await userEvent.keyboard('[Tab]');
expect(document.activeElement).to.equal(document.body);
expect(document.body).toHaveFocus();

expect(handleChange.args[0][1]).to.equal(5);
});
Expand All @@ -204,7 +204,7 @@ describe('useNumberInput', () => {
await userEvent.keyboard('-9');

await userEvent.keyboard('[Tab]');
expect(document.activeElement).to.equal(document.body);
expect(document.body).toHaveFocus();

expect(handleChange.args[0][1]).to.equal(5);
});
Expand All @@ -229,7 +229,7 @@ describe('useNumberInput', () => {
await userEvent.keyboard('4');

await userEvent.keyboard('[Tab]');
expect(document.activeElement).to.equal(document.body);
expect(document.body).toHaveFocus();

expect(handleChange.args[0][1]).to.equal(5);
});
Expand All @@ -254,7 +254,7 @@ describe('useNumberInput', () => {
expect(input.value).to.equal('');

await userEvent.keyboard('[Tab]');
expect(document.activeElement).to.equal(document.body);
expect(document.body).toHaveFocus();

expect(handleChange.callCount).to.equal(1);
expect(handleChange.args[0][1]).to.equal(null);
Expand All @@ -280,7 +280,7 @@ describe('useNumberInput', () => {
expect(input.value).to.equal('-');

await userEvent.keyboard('[Tab]');
expect(document.activeElement).to.equal(document.body);
expect(document.body).toHaveFocus();

expect(handleChange.callCount).to.equal(1);
expect(handleChange.args[0][1]).to.equal(null);
Expand Down
8 changes: 3 additions & 5 deletions packages/mui-base/src/useAutocomplete/useAutocomplete.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import { expect } from 'chai';
import { createRenderer, screen, ErrorBoundary, act, fireEvent } from '@mui/internal-test-utils';
import { createRenderer, screen, ErrorBoundary, fireEvent } from '@mui/internal-test-utils';
import { spy } from 'sinon';
import { useAutocomplete, createFilterOptions } from '@mui/base/useAutocomplete';

Expand Down Expand Up @@ -319,10 +319,8 @@ describe('useAutocomplete', () => {
render(<Test options={['foo', 'bar']} />);
const input = screen.getByRole('combobox');

act(() => {
fireEvent.change(input, { target: { value: 'free' } });
input.blur();
});
fireEvent.change(input, { target: { value: 'free' } });
fireEvent.blur(input);

expect(input.value).to.equal('free');
});
Expand Down
4 changes: 2 additions & 2 deletions packages/mui-joy/src/Accordion/Accordion.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,13 @@ describe('<Accordion />', () => {
});

it('should warn when switching from controlled to uncontrolled', () => {
const wrapper = render(
const { setProps } = render(
<Accordion expanded>
<AccordionSummary>Header</AccordionSummary>
</Accordion>,
);

expect(() => wrapper.setProps({ expanded: undefined })).to.toErrorDev(
expect(() => setProps({ expanded: undefined })).to.toErrorDev(
'MUI: A component is changing the controlled expanded state of Accordion to be uncontrolled.',
);
});
Expand Down
Loading