diff --git a/package.json b/package.json index c837fd2a54a357..0788a8e265b1ae 100644 --- a/package.json +++ b/package.json @@ -185,6 +185,7 @@ "karma-firefox-launcher": "^2.1.3", "karma-mocha": "^2.0.1", "karma-sourcemap-loader": "^0.4.0", + "karma-spec-reporter": "^0.0.36", "karma-webpack": "^5.0.0", "lerna": "^8.2.2", "lodash": "^4.17.21", diff --git a/packages-internal/test-utils/src/focusVisible.ts b/packages-internal/test-utils/src/focusVisible.ts index 1c6e10b23d1c29..9ef8f4934b5737 100644 --- a/packages-internal/test-utils/src/focusVisible.ts +++ b/packages-internal/test-utils/src/focusVisible.ts @@ -1,7 +1,7 @@ import { act, fireEvent } from './createRenderer'; -export default function focusVisible(element: HTMLElement) { - act(() => { +export default async function focusVisible(element: HTMLElement) { + await act(async () => { element.blur(); fireEvent.keyDown(document.body, { key: 'Tab' }); element.focus(); diff --git a/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx b/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx index b8ff2c801884f9..adb6f340d46e03 100644 --- a/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx +++ b/packages/mui-joy/src/Autocomplete/Autocomplete.test.tsx @@ -97,18 +97,18 @@ describe('Joy ', () => { }); describe('combobox', () => { - it('should clear the input when blur', () => { + it('should clear the input when blur', async () => { const { getByRole } = render(); const input = getByRole('combobox') as HTMLInputElement; - act(() => { + await act(async () => { input.focus(); fireEvent.change(document.activeElement!, { target: { value: 'a' } }); }); expect(input.value).to.equal('a'); - act(() => { + await act(async () => { (document.activeElement as HTMLElement).blur(); }); expect(input.value).to.equal(''); @@ -237,7 +237,7 @@ describe('Joy ', () => { }); describe('prop: limitTags', () => { - it('show all items on focus', () => { + it('show all items on focus', async () => { const { container, getAllByRole, getByRole } = render( ', () => { // include hidden clear button because JSDOM thinks it's visible expect(getAllByRole('button', { hidden: true })).to.have.lengthOf(4); - act(() => { + await act(async () => { getByRole('combobox').focus(); }); expect(container.textContent).to.equal('onetwothree'); @@ -261,7 +261,7 @@ describe('Joy ', () => { } }); - it('show 0 item on close when set 0 to limitTags', () => { + it('show 0 item on close when set 0 to limitTags', async () => { const { container, getAllByRole, getByRole } = render( ', () => { // include hidden clear button because JSDOM thinks it's visible expect(getAllByRole('button', { hidden: true })).to.have.lengthOf(2); - act(() => { + await act(async () => { getByRole('combobox').focus(); }); expect(container.textContent).to.equal('onetwothree'); @@ -287,7 +287,7 @@ describe('Joy ', () => { }); describe('prop: filterSelectedOptions', () => { - it('when the last item is selected, highlights the new last item', () => { + it('when the last item is selected, highlights the new last item', async () => { const { getByRole } = render( ', () => { fireEvent.keyDown(textbox, { key: 'ArrowUp' }); checkHighlightIs(getByRole('listbox'), 'three'); fireEvent.keyDown(textbox, { key: 'Enter' }); // selects the last option (three) - act(() => { + await act(async () => { textbox.blur(); textbox.focus(); // opens the listbox again }); @@ -310,7 +310,7 @@ describe('Joy ', () => { }); describe('prop: autoSelect', () => { - it('should not clear on blur when value does not match any option', () => { + it('should not clear on blur when value does not match any option', async () => { const handleChange = spy(); const options = ['one', 'two']; render( @@ -321,7 +321,7 @@ describe('Joy ', () => { fireEvent.change(textbox, { target: { value: 'o' } }); fireEvent.keyDown(textbox, { key: 'ArrowDown' }); fireEvent.change(textbox, { target: { value: 'oo' } }); - act(() => { + await act(async () => { textbox.blur(); }); @@ -329,7 +329,7 @@ describe('Joy ', () => { expect(handleChange.args[0][1]).to.deep.equal('oo'); }); - it('should add new value when autoSelect & multiple on blur', () => { + it('should add new value when autoSelect & multiple on blur', async () => { const handleChange = spy(); const options = ['one', 'two']; render( @@ -345,7 +345,7 @@ describe('Joy ', () => { ); const textbox = screen.getByRole('combobox'); - act(() => { + await act(async () => { fireEvent.change(textbox, { target: { value: 't' } }); fireEvent.keyDown(textbox, { key: 'ArrowDown' }); textbox.blur(); @@ -355,7 +355,7 @@ describe('Joy ', () => { expect(handleChange.args[0][1]).to.deep.equal(options); }); - it('should add new value when autoSelect & multiple & freeSolo on blur', () => { + it('should add new value when autoSelect & multiple & freeSolo on blur', async () => { const handleChange = spy(); render( ', () => { ); fireEvent.change(document.activeElement!, { target: { value: 'a' } }); - act(() => { + await act(async () => { (document.activeElement as HTMLElement).blur(); }); @@ -379,11 +379,11 @@ describe('Joy ', () => { }); describe('prop: multiple', () => { - it('should not crash', () => { + it('should not crash', async () => { const { getByRole } = render(); const input = getByRole('combobox'); - act(() => { + await act(async () => { input.focus(); (document.activeElement as HTMLElement).blur(); input.focus(); @@ -831,29 +831,29 @@ describe('Joy ', () => { }); describe('prop: clearOnBlur', () => { - it('should clear on blur', () => { + it('should clear on blur', async () => { render(); const textbox = screen.getByRole('combobox') as HTMLInputElement; fireEvent.change(textbox, { target: { value: 'test' } }); expect((document.activeElement as HTMLInputElement).value).to.equal('test'); - act(() => { + await act(async () => { textbox.blur(); }); expect(textbox.value).to.equal(''); }); - it('should not clear on blur', () => { + it('should not clear on blur', async () => { render(); const textbox = screen.getByRole('combobox') as HTMLInputElement; fireEvent.change(textbox, { target: { value: 'test' } }); expect((document.activeElement as HTMLInputElement).value).to.equal('test'); - act(() => { + await act(async () => { textbox.blur(); }); expect(textbox.value).to.equal('test'); }); - it('should not clear on blur with `multiple` enabled', () => { + it('should not clear on blur with `multiple` enabled', async () => { render( ', () => { const textbox = screen.getByRole('combobox') as HTMLInputElement; fireEvent.change(textbox, { target: { value: 'test' } }); expect((document.activeElement as HTMLInputElement).value).to.equal('test'); - act(() => { + await act(async () => { textbox.blur(); }); expect(textbox.value).to.equal('test'); @@ -947,7 +947,7 @@ describe('Joy ', () => { }); describe('prop: openOnFocus', () => { - it('enables open on input focus', () => { + it('enables open on input focus', async () => { const { getByRole } = render( , ); @@ -960,7 +960,7 @@ describe('Joy ', () => { fireEvent.click(textbox); expect(textbox).to.have.attribute('aria-expanded', 'false'); - act(() => { + await act(async () => { (document.activeElement as HTMLElement).blur(); }); @@ -1104,10 +1104,10 @@ describe('Joy ', () => { expect(container.querySelector(`.${classes.root}`)).to.have.class(classes.hasPopupIcon); }); - it('should close the popup when disabled is true', () => { + it('should close the popup when disabled is true', async () => { const { setProps } = render(); const textbox = screen.getByRole('combobox'); - act(() => { + await act(async () => { textbox.focus(); }); fireEvent.keyDown(textbox, { key: 'ArrowDown' }); @@ -1116,12 +1116,12 @@ describe('Joy ', () => { expect(screen.queryByRole('listbox')).to.equal(null); }); - it('should not crash when autoSelect & freeSolo are set, text is focused & disabled gets truthy', () => { + it('should not crash when autoSelect & freeSolo are set, text is focused & disabled gets truthy', async () => { const { setProps } = render( , ); const textbox = screen.getByRole('combobox'); - act(() => { + await act(async () => { textbox.focus(); }); setProps({ disabled: true }); @@ -1180,6 +1180,7 @@ describe('Joy ', () => { { id: '10', text: 'One' }, { id: '20', text: 'Two' }, ]; + const options = [ { id: '10', text: 'One' }, { id: '20', text: 'Two' }, @@ -1235,6 +1236,7 @@ describe('Joy ', () => { { group: 2, value: 'F' }, { group: 1, value: 'C' }, ]; + expect(() => { render( ', () => { }); describe('prop: options', () => { - it('should scroll selected option into view when multiple elements with role as listbox available', function test() { + it('should scroll selected option into view when multiple elements with role as listbox available', async function test() { if (/jsdom/.test(window.navigator.userAgent)) { this.skip(); } @@ -1304,6 +1306,7 @@ describe('Joy ', () => { }} autoFocus /> + ', () => { expect(container.firstChild).to.have.class(classes.disabled); }); - it('should reset the focused state if getting disabled', () => { + it('should reset the focused state if getting disabled', async () => { const handleBlur = spy(); const handleFocus = spy(); const { getByRole, setProps } = render(); - act(() => { + await act(async () => { getByRole('textbox').focus(); }); expect(handleFocus.callCount).to.equal(1); @@ -107,14 +107,14 @@ describe('Joy ', () => { }); describe('slotProps: input', () => { - it('`onKeyDown` and `onKeyUp` should work', () => { + it('`onKeyDown` and `onKeyUp` should work', async () => { const handleKeyDown = spy(); const handleKeyUp = spy(); const { getByRole } = render( , ); - act(() => { + await act(async () => { getByRole('textbox').focus(); }); fireEvent.keyDown(getByRole('textbox')); @@ -124,31 +124,31 @@ describe('Joy ', () => { expect(handleKeyUp.callCount).to.equal(1); }); - it('should call focus and blur', () => { + it('should call focus and blur', async () => { const handleBlur = spy(); const handleFocus = spy(); const { getByRole } = render( , ); - act(() => { + await act(async () => { getByRole('textbox').focus(); }); expect(handleFocus.callCount).to.equal(1); - act(() => { + await act(async () => { getByRole('textbox').blur(); }); expect(handleFocus.callCount).to.equal(1); }); - it('should override outer handlers', () => { + it('should override outer handlers', async () => { const handleFocus = spy(); const handleSlotFocus = spy(); const { getByRole } = render( , ); - act(() => { + await act(async () => { getByRole('textbox').focus(); }); expect(handleFocus.callCount).to.equal(0); diff --git a/packages/mui-joy/src/Link/Link.test.tsx b/packages/mui-joy/src/Link/Link.test.tsx index 0da69c7a560910..58ce4e3651f2bc 100644 --- a/packages/mui-joy/src/Link/Link.test.tsx +++ b/packages/mui-joy/src/Link/Link.test.tsx @@ -8,8 +8,8 @@ import Typography from '@mui/joy/Typography'; import { ThemeProvider, TypographySystem } from '@mui/joy/styles'; import describeConformance from '../../test/describeConformance'; -function focusVisible(element: HTMLAnchorElement | null) { - act(() => { +async function focusVisible(element: HTMLAnchorElement | null) { + await act(async () => { element?.blur(); document.dispatchEvent(new window.Event('keydown')); element?.focus(); @@ -75,7 +75,7 @@ describe('', () => { }); describe('keyboard focus', () => { - it('should add the focusVisible class when focused', function test() { + it('should add the focusVisible class when focused', async function test() { if (/jsdom/.test(window.navigator.userAgent)) { // JSDOM doesn't support :focus-visible this.skip(); @@ -86,11 +86,11 @@ describe('', () => { expect(anchor).not.to.have.class(classes.focusVisible); - focusVisible(anchor); + await focusVisible(anchor); expect(anchor).to.have.class(classes.focusVisible); - act(() => { + await act(async () => { anchor?.blur(); }); diff --git a/packages/mui-joy/src/ListItemButton/ListItemButton.test.tsx b/packages/mui-joy/src/ListItemButton/ListItemButton.test.tsx index 08f3eb26a2c20d..213dd9f4d1254b 100644 --- a/packages/mui-joy/src/ListItemButton/ListItemButton.test.tsx +++ b/packages/mui-joy/src/ListItemButton/ListItemButton.test.tsx @@ -56,7 +56,7 @@ describe('Joy ', () => { }); describe('prop: focusVisibleClassName', () => { - it('should have focusVisible classes', function test() { + it('should have focusVisible classes', async function test() { if (/jsdom/.test(window.navigator.userAgent)) { // JSDOM doesn't support :focus-visible this.skip(); @@ -65,7 +65,7 @@ describe('Joy ', () => { const { getByRole } = render(); const button = getByRole('button'); - act(() => { + await act(async () => { fireEvent.keyDown(document.activeElement || document.body, { key: 'Tab' }); button.focus(); }); diff --git a/packages/mui-joy/src/Menu/Menu.test.tsx b/packages/mui-joy/src/Menu/Menu.test.tsx index 8a8e9f38ab4222..261036e2e5e4c0 100644 --- a/packages/mui-joy/src/Menu/Menu.test.tsx +++ b/packages/mui-joy/src/Menu/Menu.test.tsx @@ -21,7 +21,7 @@ const testContext: DropdownContextValue = { }; describe('Joy ', () => { - const { render } = createRenderer({ clock: 'fake' }); + const { render } = createRenderer(); describeConformance(, () => ({ classes, @@ -54,7 +54,7 @@ describe('Joy ', () => { expect(screen.getByTestId('popover')).to.have.tagName('ul'); }); - it('should pass onClose prop to Popover', () => { + it('should pass onClose prop to Popover', async () => { const handleClose = spy(); render( @@ -67,7 +67,7 @@ describe('Joy ', () => { const item = screen.getByRole('menuitem'); - act(() => { + await act(async () => { item.focus(); }); diff --git a/packages/mui-joy/src/Modal/Modal.test.tsx b/packages/mui-joy/src/Modal/Modal.test.tsx index 422cdde727952c..31724bf045b0d5 100644 --- a/packages/mui-joy/src/Modal/Modal.test.tsx +++ b/packages/mui-joy/src/Modal/Modal.test.tsx @@ -178,14 +178,14 @@ describe('', () => { }); describe('event: keydown', () => { - it('when mounted, TopModal and event not esc should not call given functions', () => { + it('when mounted, TopModal and event not esc should not call given functions', async () => { const onCloseSpy = spy(); const { getByTestId } = render(
, ); - act(() => { + await act(async () => { getByTestId('modal').focus(); }); @@ -196,7 +196,7 @@ describe('', () => { expect(onCloseSpy).to.have.property('callCount', 0); }); - it('should call onClose when Esc is pressed and stop event propagation', () => { + it('should call onClose when Esc is pressed and stop event propagation', async () => { const handleKeyDown = spy(); const onCloseSpy = spy(); const { getByTestId } = render( @@ -206,7 +206,7 @@ describe('', () => {
, ); - act(() => { + await act(async () => { getByTestId('modal').focus(); }); @@ -218,7 +218,7 @@ describe('', () => { expect(handleKeyDown).to.have.property('callCount', 0); }); - it('should not call onClose when `disableEscapeKeyDown={true}`', () => { + it('should not call onClose when `disableEscapeKeyDown={true}`', async () => { const handleKeyDown = spy(); const onCloseSpy = spy(); const { getByTestId } = render( @@ -228,7 +228,7 @@ describe('', () => { , ); - act(() => { + await act(async () => { getByTestId('modal').focus(); }); @@ -285,11 +285,11 @@ describe('', () => { describe('focus', () => { let initialFocus: null | HTMLButtonElement = null; - beforeEach(() => { + beforeEach(async () => { initialFocus = document.createElement('button'); initialFocus.tabIndex = 0; document.body.appendChild(initialFocus); - act(() => { + await act(async () => { initialFocus?.focus(); }); }); @@ -357,7 +357,7 @@ describe('', () => { describe('focus stealing', () => { clock.withFakeTimers(); - it('does not steal focus from other frames', function test() { + it('does not steal focus from other frames', async function test() { if (/jsdom/.test(window.navigator.userAgent)) { // TODO: Unclear why this fails. Not important // since a browser test gives us more confidence anyway @@ -412,7 +412,7 @@ describe('', () => { , ); - act(() => { + await act(async () => { getByTestId('foreign-input').focus(); }); // wait for the `contain` interval check to kick in. diff --git a/packages/mui-joy/src/Radio/Radio.test.tsx b/packages/mui-joy/src/Radio/Radio.test.tsx index fdb63ce9459b5c..7192555bbe6cfb 100644 --- a/packages/mui-joy/src/Radio/Radio.test.tsx +++ b/packages/mui-joy/src/Radio/Radio.test.tsx @@ -98,11 +98,11 @@ describe('', () => { expect(getByRole('radio')).to.have.property('disabled', true); }); - it('the Checked state changes after change events', () => { + it('the Checked state changes after change events', async () => { const { getByRole } = render(); // how a user would trigger it - act(() => { + await act(async () => { getByRole('radio').click(); fireEvent.change(getByRole('radio'), { target: { checked: '' } }); }); @@ -148,7 +148,7 @@ describe('', () => { expect(getByRole('radio', { checked: true })).to.have.property('value', '1'); }); - it('should be checked when changing the value', () => { + it('should be checked when changing the value', async () => { const { getByRole } = render( @@ -158,13 +158,13 @@ describe('', () => { expect(getByRole('radio', { checked: true })).to.have.property('value', '1'); - act(() => { + await act(async () => { getByRole('radio', { checked: false }).click(); }); expect(getByRole('radio', { checked: true })).to.have.property('value', '0'); - act(() => { + await act(async () => { getByRole('radio', { checked: false }).click(); }); diff --git a/packages/mui-joy/src/RadioGroup/RadioGroup.test.tsx b/packages/mui-joy/src/RadioGroup/RadioGroup.test.tsx index 8665f6bd97ad82..90b851a22ebfb4 100644 --- a/packages/mui-joy/src/RadioGroup/RadioGroup.test.tsx +++ b/packages/mui-joy/src/RadioGroup/RadioGroup.test.tsx @@ -48,12 +48,12 @@ describe('', () => { expect(handleBlur.callCount).to.equal(1); }); - it('should fire the onKeyDown callback', () => { + it('should fire the onKeyDown callback', async () => { const handleKeyDown = spy(); const { getByRole } = render(); const radiogroup = getByRole('radiogroup'); - act(() => { + await act(async () => { radiogroup.focus(); }); diff --git a/packages/mui-joy/src/Select/Select.test.tsx b/packages/mui-joy/src/Select/Select.test.tsx index bfe89587c6bdbf..3f7a1c6e25a60a 100644 --- a/packages/mui-joy/src/Select/Select.test.tsx +++ b/packages/mui-joy/src/Select/Select.test.tsx @@ -11,7 +11,7 @@ import ListDivider from '@mui/joy/ListDivider'; import describeConformance from '../../test/describeConformance'; describe('Joy , () => ({ render, @@ -86,7 +86,7 @@ describe('Joy ', () => { , ); const select = getByRole('combobox'); - act(() => { + await act(async () => { select.focus(); }); - act(() => { + await act(async () => { select.blur(); }); @@ -114,7 +114,7 @@ describe('Joy ', () => { , ); - act(() => { + await act(async () => { screen.getByRole('option', { selected: true }).click(); }); @@ -141,7 +141,7 @@ describe('Joy @@ -151,7 +151,7 @@ describe('Joy , ); fireEvent.click(getByRole('combobox')); - act(() => { + await act(async () => { getAllByRole('option')[1].click(); }); @@ -159,7 +159,7 @@ describe('Joy @@ -169,7 +169,7 @@ describe('Joy , ); fireEvent.click(getByRole('combobox')); - act(() => { + await act(async () => { getAllByRole('option')[1].click(); }); @@ -443,7 +443,7 @@ describe('Joy ', () => { ); const options = screen.getAllByRole('option'); - act(() => { + await act(async () => { options[0].click(); }); @@ -465,7 +465,7 @@ describe('Joy @@ -476,7 +476,7 @@ describe('Joy ', () => { }); describe('form submission', () => { - it('includes the Select value in the submitted form data when the `name` attribute is provided', function test() { + it('includes the Select value in the submitted form data when the `name` attribute is provided', async function test() { if (/jsdom/.test(window.navigator.userAgent)) { // FormData is not available in JSDOM this.skip(); @@ -522,14 +522,14 @@ describe('Joy ', () => { ); const button = getByText('Submit'); - act(() => { + await act(async () => { button.click(); }); expect(isEventHandled).to.equal(true); }); - it('formats the object values as JSON before posting', function test() { + it('formats the object values as JSON before posting', async function test() { if (/jsdom/.test(window.navigator.userAgent)) { // FormData is not available in JSDOM this.skip(); @@ -599,7 +599,7 @@ describe('Joy ', () => { }); }); - it('should show dropdown if the children of the select button is clicked', () => { + it('should show dropdown if the children of the select button is clicked', async () => { const { getByTestId, getByRole } = render( , ); // Fire Click of the avatar - act(() => { + await act(async () => { fireEvent.click(getByTestId('test-element')); }); expect(getByRole('combobox', { hidden: true })).to.have.attribute('aria-expanded', 'true'); // click again should close - act(() => { + await act(async () => { fireEvent.click(getByTestId('test-element')); }); expect(getByRole('combobox', { hidden: true })).to.have.attribute('aria-expanded', 'false'); diff --git a/packages/mui-joy/src/Snackbar/Snackbar.test.tsx b/packages/mui-joy/src/Snackbar/Snackbar.test.tsx index af55c7aa4e5d98..94006819994117 100644 --- a/packages/mui-joy/src/Snackbar/Snackbar.test.tsx +++ b/packages/mui-joy/src/Snackbar/Snackbar.test.tsx @@ -1,13 +1,23 @@ import * as React from 'react'; import { expect } from 'chai'; -import { spy } from 'sinon'; +import { SinonFakeTimers, spy, useFakeTimers } from 'sinon'; import { createRenderer, fireEvent, act } from '@mui/internal-test-utils'; import Snackbar, { snackbarClasses as classes } from '@mui/joy/Snackbar'; import { ThemeProvider } from '@mui/joy/styles'; import describeConformance from '../../test/describeConformance'; describe('Joy ', () => { - const { render: clientRender, clock } = createRenderer({ clock: 'fake' }); + let timer: SinonFakeTimers | null = null; + + beforeEach(() => { + timer = useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'Date'] }); + }); + + afterEach(() => { + timer?.restore(); + }); + + const { render: clientRender } = createRenderer(); /** * @type {typeof plainRender extends (...args: infer T) => any ? T : never} args @@ -19,9 +29,11 @@ describe('Joy ', () => { * We have to defer the effect manually like `useEffect` would so we have to flush the effect manually instead of relying on `act()`. * React bug: https://github.com/facebook/react/issues/20074 */ - function render(...args: [React.ReactElement]) { + async function render(...args: [React.ReactElement]) { const result = clientRender(...args); - clock.tick(0); + await act(async () => { + await timer?.tickAsync(0); + }); return result; } @@ -30,7 +42,7 @@ describe('Joy ', () => { Hello World! , () => ({ - render, + render: clientRender, classes, ThemeProvider, muiName: 'JoySnackbar', @@ -46,24 +58,26 @@ describe('Joy ', () => { ); describe('prop: onClose', () => { - it('should be called when clicking away', () => { + it('should be called when clicking away', async () => { const handleClose = spy(); - render( + await render( Message , ); const event = new window.Event('click', { bubbles: true, cancelable: true }); - document.body.dispatchEvent(event); + await act(async () => { + document.body.dispatchEvent(event); + }); expect(handleClose.callCount).to.equal(1); expect(handleClose.args[0]).to.deep.equal([event, 'clickaway']); }); - it('should be called when pressing Escape', () => { + it('should be called when pressing Escape', async () => { const handleClose = spy(); - render( + await render( Message , @@ -74,10 +88,10 @@ describe('Joy ', () => { expect(handleClose.args[0][1]).to.equal('escapeKeyDown'); }); - it('can limit which Snackbars are closed when pressing Escape', () => { + it('can limit which Snackbars are closed when pressing Escape', async () => { const handleCloseA = spy((event) => event.preventDefault()); const handleCloseB = spy(); - render( + await render( Message A @@ -96,10 +110,10 @@ describe('Joy ', () => { }); describe('prop: autoHideDuration', () => { - it('should call onClose when the timer is done', () => { + it('should call onClose when the timer is done', async () => { const handleClose = spy(); const autoHideDuration = 2e3; - const { setProps } = render( + const { setProps } = await render( Message , @@ -109,35 +123,41 @@ describe('Joy ', () => { expect(handleClose.callCount).to.equal(0); - clock.tick(autoHideDuration); + await act(async () => { + await timer?.tickAsync(autoHideDuration); + }); expect(handleClose.callCount).to.equal(1); expect(handleClose.args[0]).to.deep.equal([null, 'timeout']); }); - it('calls onClose at timeout even if the prop changes', () => { + it('calls onClose at timeout even if the prop changes', async () => { const handleClose1 = spy(); const handleClose2 = spy(); const autoHideDuration = 2e3; - const { setProps } = render( + const { setProps } = await render( Message , ); setProps({ open: true }); - clock.tick(autoHideDuration / 2); + await act(async () => { + await timer?.tickAsync(autoHideDuration / 2); + }); setProps({ open: true, onClose: handleClose2 }); - clock.tick(autoHideDuration / 2); + await act(async () => { + await timer?.tickAsync(autoHideDuration / 2); + }); expect(handleClose1.callCount).to.equal(0); expect(handleClose2.callCount).to.equal(1); }); - it('should not call onClose when the autoHideDuration is reset', () => { + it('should not call onClose when the autoHideDuration is reset', async () => { const handleClose = spy(); const autoHideDuration = 2e3; - const { setProps } = render( + const { setProps } = await render( Message , @@ -147,17 +167,21 @@ describe('Joy ', () => { expect(handleClose.callCount).to.equal(0); - clock.tick(autoHideDuration / 2); + await act(async () => { + await timer?.tickAsync(autoHideDuration / 2); + }); setProps({ autoHideDuration: undefined }); - clock.tick(autoHideDuration / 2); + await act(async () => { + await timer?.tickAsync(autoHideDuration / 2); + }); expect(handleClose.callCount).to.equal(0); }); - it('should not call onClose if autoHideDuration is undefined', () => { + it('should not call onClose if autoHideDuration is undefined', async () => { const handleClose = spy(); const autoHideDuration = 2e3; - render( + await render( Message , @@ -165,16 +189,18 @@ describe('Joy ', () => { expect(handleClose.callCount).to.equal(0); - clock.tick(autoHideDuration); + await act(async () => { + await timer?.tickAsync(autoHideDuration); + }); expect(handleClose.callCount).to.equal(0); }); - it('should not call onClose if autoHideDuration is null', () => { + it('should not call onClose if autoHideDuration is null', async () => { const handleClose = spy(); const autoHideDuration = 2e3; - render( + await render( Message , @@ -182,16 +208,18 @@ describe('Joy ', () => { expect(handleClose.callCount).to.equal(0); - clock.tick(autoHideDuration); + await act(async () => { + await timer?.tickAsync(autoHideDuration); + }); expect(handleClose.callCount).to.equal(0); }); - it('should not call onClose when closed', () => { + it('should not call onClose when closed', async () => { const handleClose = spy(); const autoHideDuration = 2e3; - const { setProps } = render( + const { setProps } = await render( Message , @@ -199,9 +227,13 @@ describe('Joy ', () => { expect(handleClose.callCount).to.equal(0); - clock.tick(autoHideDuration / 2); + await act(async () => { + await timer?.tickAsync(autoHideDuration / 2); + }); setProps({ open: false }); - clock.tick(autoHideDuration / 2); + await act(async () => { + await timer?.tickAsync(autoHideDuration / 2); + }); expect(handleClose.callCount).to.equal(0); }); @@ -215,12 +247,16 @@ describe('Joy ', () => { }, { type: 'keyboard', - enter: (container: HTMLElement) => act(() => container.querySelector('button')!.focus()), - leave: (container: HTMLElement) => act(() => container.querySelector('button')!.blur()), + enter: async (container: HTMLElement) => { + await act(async () => container.querySelector('button')!.focus()); + }, + leave: async (container: HTMLElement) => { + await act(async () => container.querySelector('button')!.blur()); + }, }, ].forEach((userInteraction) => { describe(`interacting with ${userInteraction.type}`, () => { - it('should be able to interrupt the timer', () => { + it('should be able to interrupt the timer', async () => { const handleMouseEnter = spy(); const handleMouseLeave = spy(); const handleBlur = spy(); @@ -228,7 +264,7 @@ describe('Joy ', () => { const handleClose = spy(); const autoHideDuration = 2e3; - const { container } = render( + const { container } = await render( undo} open @@ -245,8 +281,10 @@ describe('Joy ', () => { expect(handleClose.callCount).to.equal(0); - clock.tick(autoHideDuration / 2); - userInteraction.enter(container.querySelector('div')!); + await act(async () => { + await timer?.tickAsync(autoHideDuration / 2); + }); + await await userInteraction.enter(container.querySelector('div')!); if (userInteraction.type === 'keyboard') { expect(handleFocus.callCount).to.equal(1); @@ -254,8 +292,10 @@ describe('Joy ', () => { expect(handleMouseEnter.callCount).to.equal(1); } - clock.tick(autoHideDuration / 2); - userInteraction.leave(container.querySelector('div')!); + await act(async () => { + await timer?.tickAsync(autoHideDuration / 2); + }); + await await userInteraction.leave(container.querySelector('div')!); if (userInteraction.type === 'keyboard') { expect(handleBlur.callCount).to.equal(1); @@ -264,18 +304,20 @@ describe('Joy ', () => { } expect(handleClose.callCount).to.equal(0); - clock.tick(2e3); + await act(async () => { + await timer?.tickAsync(2e3); + }); expect(handleClose.callCount).to.equal(1); expect(handleClose.args[0]).to.deep.equal([null, 'timeout']); }); - it('should not call onClose with not timeout after user interaction', () => { + it('should not call onClose with not timeout after user interaction', async () => { const handleClose = spy(); const autoHideDuration = 2e3; const resumeHideDuration = 3e3; - const { container } = render( + const { container } = await render( undo} open @@ -289,24 +331,30 @@ describe('Joy ', () => { expect(handleClose.callCount).to.equal(0); - clock.tick(autoHideDuration / 2); - userInteraction.enter(container.querySelector('div')!); - clock.tick(autoHideDuration / 2); - userInteraction.leave(container.querySelector('div')!); + await act(async () => { + await timer?.tickAsync(autoHideDuration / 2); + }); + await userInteraction.enter(container.querySelector('div')!); + await act(async () => { + await timer?.tickAsync(autoHideDuration / 2); + }); + await userInteraction.leave(container.querySelector('div')!); expect(handleClose.callCount).to.equal(0); - clock.tick(2e3); + await act(async () => { + await timer?.tickAsync(2e3); + }); expect(handleClose.callCount).to.equal(0); }); - it('should call onClose when timer done after user interaction', () => { + it('should call onClose when timer done after user interaction', async () => { const handleClose = spy(); const autoHideDuration = 2e3; const resumeHideDuration = 3e3; - const { container } = render( + const { container } = await render( undo} open @@ -320,24 +368,30 @@ describe('Joy ', () => { expect(handleClose.callCount).to.equal(0); - clock.tick(autoHideDuration / 2); - userInteraction.enter(container.querySelector('div')!); - clock.tick(autoHideDuration / 2); - userInteraction.leave(container.querySelector('div')!); + await act(async () => { + await timer?.tickAsync(autoHideDuration / 2); + }); + await userInteraction.enter(container.querySelector('div')!); + await act(async () => { + await timer?.tickAsync(autoHideDuration / 2); + }); + await userInteraction.leave(container.querySelector('div')!); expect(handleClose.callCount).to.equal(0); - clock.tick(resumeHideDuration); + await act(async () => { + await timer?.tickAsync(resumeHideDuration); + }); expect(handleClose.callCount).to.equal(1); expect(handleClose.args[0]).to.deep.equal([null, 'timeout']); }); - it('should call onClose immediately after user interaction when 0', () => { + it('should call onClose immediately after user interaction when 0', async () => { const handleClose = spy(); const autoHideDuration = 6e3; const resumeHideDuration = 0; - const { setProps, container } = render( + const { setProps, container } = await render( undo} open @@ -353,10 +407,14 @@ describe('Joy ', () => { expect(handleClose.callCount).to.equal(0); - userInteraction.enter(container.querySelector('div')!); - clock.tick(100); - userInteraction.leave(container.querySelector('div')!); - clock.tick(resumeHideDuration); + await userInteraction.enter(container.querySelector('div')!); + await act(async () => { + await timer?.tickAsync(100); + }); + await userInteraction.leave(container.querySelector('div')!); + await act(async () => { + await timer?.tickAsync(resumeHideDuration); + }); expect(handleClose.callCount).to.equal(1); expect(handleClose.args[0]).to.deep.equal([null, 'timeout']); @@ -365,10 +423,10 @@ describe('Joy ', () => { }); describe('prop: disableWindowBlurListener', () => { - it('should pause auto hide when not disabled and window lost focus', () => { + it('should pause auto hide when not disabled and window lost focus', async () => { const handleClose = spy(); const autoHideDuration = 2e3; - render( + await render( ', () => { , ); - act(() => { + await act(async () => { const bEvent = new window.Event('blur', { bubbles: false, cancelable: false, @@ -389,11 +447,13 @@ describe('Joy ', () => { expect(handleClose.callCount).to.equal(0); - clock.tick(autoHideDuration); + await act(async () => { + await timer?.tickAsync(autoHideDuration); + }); expect(handleClose.callCount).to.equal(0); - act(() => { + await act(async () => { const fEvent = new window.Event('focus', { bubbles: false, cancelable: false, @@ -403,16 +463,18 @@ describe('Joy ', () => { expect(handleClose.callCount).to.equal(0); - clock.tick(autoHideDuration); + await act(async () => { + await timer?.tickAsync(autoHideDuration); + }); expect(handleClose.callCount).to.equal(1); expect(handleClose.args[0]).to.deep.equal([null, 'timeout']); }); - it('should not pause auto hide when disabled and window lost focus', () => { + it('should not pause auto hide when disabled and window lost focus', async () => { const handleClose = spy(); const autoHideDuration = 2e3; - render( + await render( ', () => { , ); - act(() => { + await act(async () => { const event = new window.Event('blur', { bubbles: false, cancelable: false }); window.dispatchEvent(event); }); expect(handleClose.callCount).to.equal(0); - clock.tick(autoHideDuration); + await act(async () => { + await timer?.tickAsync(autoHideDuration); + }); expect(handleClose.callCount).to.equal(1); expect(handleClose.args[0]).to.deep.equal([null, 'timeout']); @@ -438,13 +502,13 @@ describe('Joy ', () => { }); describe('prop: open', () => { - it('should not render anything when closed', () => { - const { container } = render(Hello World!); + it('should not render anything when closed', async () => { + const { container } = await render(Hello World!); expect(container).to.have.text(''); }); - it('should be able show it after mounted', () => { - const { container, setProps } = render(Hello World!); + it('should be able show it after mounted', async () => { + const { container, setProps } = await render(Hello World!); expect(container).to.have.text(''); setProps({ open: true }); expect(container).to.have.text('Hello World!'); diff --git a/packages/mui-joy/src/Switch/Switch.test.tsx b/packages/mui-joy/src/Switch/Switch.test.tsx index d14a52403e2db0..357a4a6dc7187a 100644 --- a/packages/mui-joy/src/Switch/Switch.test.tsx +++ b/packages/mui-joy/src/Switch/Switch.test.tsx @@ -19,6 +19,7 @@ describe('', () => { { slotName: 'input', slotClassName: classes.input }, { slotName: 'thumb', slotClassName: classes.thumb }, ], + testVariantProps: { variant: 'soft' }, testCustomVariant: true, refInstanceof: window.HTMLDivElement, @@ -91,11 +92,11 @@ describe('', () => { expect(getByRole('switch')).to.have.property('readOnly', true); }); - it('the Checked state changes after change events', () => { + it('the Checked state changes after change events', async () => { const { getByRole } = render(); // how a user would trigger it - act(() => { + await act(async () => { getByRole('switch').click(); fireEvent.change(getByRole('switch'), { target: { checked: '' } }); }); @@ -116,7 +117,7 @@ describe('', () => { expect(getByText('bar')).toBeVisible(); }); - it('can receive startDecorator as function', () => { + it('can receive startDecorator as function', async () => { const { getByText, getByRole } = render( (checked ? 'On' : 'Off')} />, ); @@ -124,7 +125,7 @@ describe('', () => { expect(getByText('Off')).toBeVisible(); // how a user would trigger it - act(() => { + await act(async () => { getByRole('switch').click(); fireEvent.change(getByRole('switch'), { target: { checked: '' } }); }); @@ -132,7 +133,7 @@ describe('', () => { expect(getByText('On')).toBeVisible(); }); - it('can receive endDecorator as function', () => { + it('can receive endDecorator as function', async () => { const { getByText, getByRole } = render( (checked ? 'On' : 'Off')} />, ); @@ -140,7 +141,7 @@ describe('', () => { expect(getByText('Off')).toBeVisible(); // how a user would trigger it - act(() => { + await act(async () => { getByRole('switch').click(); fireEvent.change(getByRole('switch'), { target: { checked: '' } }); }); diff --git a/packages/mui-joy/src/Textarea/Textarea.test.tsx b/packages/mui-joy/src/Textarea/Textarea.test.tsx index 25f272d8b7939e..cae79a54ae9ad1 100644 --- a/packages/mui-joy/src/Textarea/Textarea.test.tsx +++ b/packages/mui-joy/src/Textarea/Textarea.test.tsx @@ -68,14 +68,14 @@ describe('Joy