From d85fff19138b109a8d81655d55de62dbca2c6065 Mon Sep 17 00:00:00 2001 From: Jose C Quintas Jr Date: Thu, 2 Jan 2025 12:42:58 +0100 Subject: [PATCH] [code-infra] Add `testSkipIf` and `describeSkipIf` (#16049) Signed-off-by: Jose C Quintas Jr Co-authored-by: Lukas Tyla --- .../src/BarChart/checkClickEvent.test.tsx | 123 +++-- .../src/ChartsSurface/ChartsSurface.test.tsx | 9 +- .../ChartsTooltip/contentDisplayed.test.tsx | 37 +- .../src/LineChart/checkClickEvent.test.tsx | 37 +- .../src/PieChart/checkClickEvent.test.tsx | 4 +- .../src/ScatterChart/ScatterChart.test.tsx | 16 +- .../src/ScatterChart/checkClickEvent.test.tsx | 27 +- .../useSkipAnimation.test.tsx | 11 +- .../useHighlighted.test.tsx | 11 +- .../x-charts/src/hooks/useSeries.test.tsx | 11 +- .../x-charts/src/hooks/useSvgRef.test.tsx | 11 +- .../cellSelection.DataGridPremium.test.tsx | 10 +- .../tests/clipboard.DataGridPremium.test.tsx | 11 +- .../tests/columns.DataGridPremium.test.tsx | 11 +- .../rowSpanning.DataGridPremium.test.tsx | 8 +- .../tests/accessibility.DataGridPro.test.tsx | 70 ++- .../tests/columnHeaders.DataGridPro.test.tsx | 9 +- .../tests/columnPinning.DataGridPro.test.tsx | 175 ++++--- .../tests/columnSpanning.DataGridPro.test.tsx | 58 ++- .../src/tests/columns.DataGridPro.test.tsx | 73 ++- .../src/tests/dataSource.DataGridPro.test.tsx | 12 +- .../dataSourceLazyLoader.DataGridPro.test.tsx | 13 +- .../dataSourceTreeData.DataGridPro.test.tsx | 12 +- .../tests/detailPanel.DataGridPro.test.tsx | 426 +++++++++--------- .../src/tests/events.DataGridPro.test.tsx | 40 +- .../src/tests/filtering.DataGridPro.test.tsx | 82 ++-- .../tests/infiniteLoader.DataGridPro.test.tsx | 363 +++++++-------- .../src/tests/layout.DataGridPro.test.tsx | 16 +- .../src/tests/lazyLoader.DataGridPro.test.tsx | 33 +- .../src/tests/rowPinning.DataGridPro.test.tsx | 65 +-- .../src/tests/rows.DataGridPro.test.tsx | 13 +- .../src/tests/sorting.DataGridPro.test.tsx | 78 ++-- .../utils/useGridApiEventHandler.test.tsx | 17 +- .../src/tests/cells.DataGrid.test.tsx | 122 +++-- .../tests/columnSpanning.DataGrid.test.tsx | 49 +- .../src/tests/columns.DataGrid.test.tsx | 10 +- .../src/tests/density.DataGrid.test.tsx | 13 +- .../src/tests/export.DataGrid.test.tsx | 14 +- .../src/tests/keyboard.DataGrid.test.tsx | 339 +++++++------- .../src/tests/layout.DataGrid.test.tsx | 346 +++++++------- .../src/tests/pagination.DataGrid.test.tsx | 33 +- .../tests/quickFiltering.DataGrid.test.tsx | 8 +- .../src/tests/rowSelection.DataGrid.test.tsx | 59 ++- .../src/tests/rowSpanning.DataGrid.test.tsx | 33 +- .../src/tests/rows.DataGrid.test.tsx | 234 +++++----- .../src/tests/slots.DataGrid.test.tsx | 12 +- .../DateRangeCalendar.test.tsx | 291 ++++++------ .../timezone.DateRangeCalendar.test.tsx | 53 ++- .../tests/DesktopDateRangePicker.test.tsx | 58 ++- .../DateCalendar/tests/DateCalendar.test.tsx | 9 +- .../tests/timezone.DateCalendar.test.tsx | 181 ++++---- .../tests/editing.DateField.test.tsx | 9 +- .../DateField/tests/format.DateField.test.tsx | 23 +- .../tests/timezone.DateTimeField.test.tsx | 179 ++++---- .../tests/DesktopDatePicker.test.tsx | 18 +- .../describes.DesktopDateTimePicker.test.tsx | 2 +- .../tests/timezone.DigitalClock.test.tsx | 238 +++++----- .../tests/MobileDateTimePicker.test.tsx | 7 +- .../tests/MobileTimePicker.test.tsx | 7 +- .../tests/timezone.MobileTimePicker.test.tsx | 49 +- .../tests/StaticDatePicker.test.tsx | 15 +- .../StaticTimePicker.test.tsx | 203 +++++---- .../src/TimeClock/tests/TimeClock.test.tsx | 107 +++-- .../tests/timezone.TimeClock.test.tsx | 165 +++---- .../useLicenseVerifier.test.tsx | 11 +- .../useTreeViewItemsReordering.test.tsx | 267 +++++------ .../useTreeViewExpansion.test.tsx | 2 +- .../useTreeViewItems.test.tsx | 382 ++++++++-------- .../useTreeViewKeyboardNavigation.test.tsx | 50 +- .../useTreeViewLabel.test.tsx | 80 +--- .../testCalculations.ts | 125 ++--- .../pickers/describePicker/describePicker.tsx | 257 +++++------ .../testDayViewRangeValidation.tsx | 283 ++++++------ .../testTextFieldKeyboardRangeValidation.tsx | 42 +- .../testTextFieldRangeValidation.tsx | 104 +---- .../testDayViewValidation.tsx | 37 +- .../testMinutesViewValidation.tsx | 24 +- .../testMonthViewValidation.tsx | 17 +- .../testTextFieldValidation.tsx | 403 ++++++++--------- .../testYearViewValidation.tsx | 17 +- .../testPickerOpenCloseLifeCycle.tsx | 153 +++---- test/utils/skipIf.ts | 27 ++ 82 files changed, 3200 insertions(+), 3859 deletions(-) create mode 100644 test/utils/skipIf.ts diff --git a/packages/x-charts/src/BarChart/checkClickEvent.test.tsx b/packages/x-charts/src/BarChart/checkClickEvent.test.tsx index b485c1e43062d..ddd7ee9b7eb7d 100644 --- a/packages/x-charts/src/BarChart/checkClickEvent.test.tsx +++ b/packages/x-charts/src/BarChart/checkClickEvent.test.tsx @@ -3,6 +3,7 @@ import { expect } from 'chai'; import { createRenderer, fireEvent } from '@mui/internal-test-utils'; import { spy } from 'sinon'; import { BarChart } from '@mui/x-charts/BarChart'; +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; import { firePointerEvent } from '../tests/firePointerEvent'; const config = { @@ -23,17 +24,12 @@ const config = { // | X X X X // ---A---B- -const isJSDOM = /jsdom/.test(window.navigator.userAgent); - describe('BarChart - click event', () => { const { render } = createRenderer(); describe('onAxisClick', () => { - it('should provide the right context as second argument', function test() { - if (isJSDOM) { - // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 - this.skip(); - } + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + testSkipIf(isJSDOM)('should provide the right context as second argument', () => { const onAxisClick = spy(); render(
{ }); }); - it('should provide the right context as second argument with layout="horizontal"', function test() { - if (isJSDOM) { - // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 - this.skip(); - } - const onAxisClick = spy(); - render( -
- -
, - ); - const svg = document.querySelector('svg')!; - - firePointerEvent(svg, 'pointermove', { - clientX: 60, - clientY: 198, - }); - fireEvent.click(svg); - - expect(onAxisClick.lastCall.args[1]).to.deep.equal({ - dataIndex: 0, - axisValue: 'A', - seriesValues: { s1: 4, s2: 2 }, - }); - - firePointerEvent(svg, 'pointermove', { - clientX: 60, - clientY: 201, - }); - fireEvent.click(svg); - - expect(onAxisClick.lastCall.args[1]).to.deep.equal({ - dataIndex: 1, - axisValue: 'B', - seriesValues: { s1: 1, s2: 1 }, - }); - }); + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + testSkipIf(isJSDOM)( + 'should provide the right context as second argument with layout="horizontal"', + () => { + const onAxisClick = spy(); + render( +
+ +
, + ); + const svg = document.querySelector('svg')!; + + firePointerEvent(svg, 'pointermove', { + clientX: 60, + clientY: 198, + }); + fireEvent.click(svg); + + expect(onAxisClick.lastCall.args[1]).to.deep.equal({ + dataIndex: 0, + axisValue: 'A', + seriesValues: { s1: 4, s2: 2 }, + }); + + firePointerEvent(svg, 'pointermove', { + clientX: 60, + clientY: 201, + }); + fireEvent.click(svg); + + expect(onAxisClick.lastCall.args[1]).to.deep.equal({ + dataIndex: 1, + axisValue: 'B', + seriesValues: { s1: 1, s2: 1 }, + }); + }, + ); }); describe('onItemClick', () => { - it('should add cursor="pointer" to bar elements', function test() { + it('should add cursor="pointer" to bar elements', () => { render( { ).to.deep.equal(['pointer', 'pointer', 'pointer', 'pointer']); }); - it('should provide the right context as second argument', function test() { - if (isJSDOM) { - // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 - this.skip(); - } + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + testSkipIf(isJSDOM)('should provide the right context as second argument', () => { const onItemClick = spy(); render(
', () => { - // JSDOM doesn't implement SVGElement - if (/jsdom/.test(window.navigator.userAgent)) { - return; - } - +// JSDOM doesn't implement SVGElement +describeSkipIf(isJSDOM)('', () => { const { render } = createRenderer(); it('should pass ref when it is added directly to component', () => { diff --git a/packages/x-charts/src/ChartsTooltip/contentDisplayed.test.tsx b/packages/x-charts/src/ChartsTooltip/contentDisplayed.test.tsx index 3677f29ee5461..2015a303c3dc6 100644 --- a/packages/x-charts/src/ChartsTooltip/contentDisplayed.test.tsx +++ b/packages/x-charts/src/ChartsTooltip/contentDisplayed.test.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { createRenderer, fireEvent } from '@mui/internal-test-utils'; import { BarChart } from '@mui/x-charts/BarChart'; +import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; const config = { dataset: [ @@ -21,18 +22,12 @@ const config = { // | X X X X // ---A---B- -const isJSDOM = /jsdom/.test(window.navigator.userAgent); - describe('ChartsTooltip', () => { const { render } = createRenderer(); - describe('axis trigger', () => { - it('should show right values with vertical layout', function test() { - if (isJSDOM) { - // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 - this.skip(); - } - + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + describeSkipIf(isJSDOM)('axis trigger', () => { + it('should show right values with vertical layout on axis', () => { render(
{ ]); }); - it('should show right values with horizontal layout', function test() { - if (isJSDOM) { - // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 - this.skip(); - } - + it('should show right values with horizontal layout on axis', () => { render(
{ }); }); - describe('item trigger', () => { - it('should show right values with vertical layout', function test() { - if (isJSDOM) { - // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 - this.skip(); - } - + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + describeSkipIf(isJSDOM)('item trigger', () => { + it('should show right values with vertical layout on item', () => { render(
{ expect([...cells].map((cell) => cell.textContent)).to.deep.equal(['', 'S2', '1']); }); - it('should show right values with horizontal layout', function test() { - if (isJSDOM) { - // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 - this.skip(); - } - + it('should show right values with horizontal layout on item', () => { render(
{ const { render } = createRenderer(); describe('onAxisClick', () => { - it('should provide the right context as second argument', function test() { - if (isJSDOM) { - // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 - this.skip(); - } + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + testSkipIf(isJSDOM)('should provide the right context as second argument', () => { const onAxisClick = spy(); render(
{ }); describe('onMarkClick', () => { - it('should add cursor="pointer" to bar elements', function test() { + it('should add cursor="pointer" to bar elements', () => { render( { ]); }); - it('should provide the right context as second argument', function test() { - if (isJSDOM) { - // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 - this.skip(); - } + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + testSkipIf(isJSDOM)('should provide the right context as second argument', () => { const onMarkClick = spy(); render(
{ }); describe('onAreaClick', () => { - it('should add cursor="pointer" to bar elements', function test() { + it('should add cursor="pointer" to bar elements', () => { render( { ]); }); - it('should provide the right context as second argument', function test() { - if (isJSDOM) { - // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 - this.skip(); - } + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + testSkipIf(isJSDOM)('should provide the right context as second argument', () => { const onAreaClick = spy(); render(
{ }); describe('onLineClick', () => { - it('should add cursor="pointer" to bar elements', function test() { + it('should add cursor="pointer" to bar elements', () => { render( { ]); }); - it('should provide the right context as second argument', function test() { - if (isJSDOM) { - // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 - this.skip(); - } + // can't do Pointer event with JSDom https://github.com/jsdom/jsdom/issues/2527 + testSkipIf(isJSDOM)('should provide the right context as second argument', () => { const onLineClick = spy(); render(
{ const { render } = createRenderer(); describe('onItemClick', () => { - it('should add cursor="pointer" to bar elements', function test() { + it('should add cursor="pointer" to bar elements', () => { render( { ]); }); - it('should provide the right context as second argument', function test() { + it('should provide the right context as second argument', () => { const onItemClick = spy(); render( ', () => { const { render } = createRenderer(); @@ -55,11 +54,8 @@ describe('', () => { height: 100, }; - it('should show the tooltip without errors in default config', function test() { - if (isJSDOM) { - // svg.createSVGPoint not supported by JSDom https://github.com/jsdom/jsdom/issues/300 - this.skip(); - } + // svg.createSVGPoint not supported by JSDom https://github.com/jsdom/jsdom/issues/300 + testSkipIf(isJSDOM)('should show the tooltip without errors in default config', () => { render(
', () => { expect([...cells].map((cell) => cell.textContent)).to.deep.equal(['', '', '(5, 5)']); }); - it('should support dataset with missing values', async function test() { - if (isJSDOM) { - this.skip(); - } - + testSkipIf(isJSDOM)('should support dataset with missing values', async () => { // x from 500 to 600 // y from 100 to 200 const dataset = [ diff --git a/packages/x-charts/src/ScatterChart/checkClickEvent.test.tsx b/packages/x-charts/src/ScatterChart/checkClickEvent.test.tsx index 08b8148eff60b..ecbfeb33f985d 100644 --- a/packages/x-charts/src/ScatterChart/checkClickEvent.test.tsx +++ b/packages/x-charts/src/ScatterChart/checkClickEvent.test.tsx @@ -3,6 +3,7 @@ import { expect } from 'chai'; import { createRenderer, fireEvent } from '@mui/internal-test-utils'; import { spy } from 'sinon'; import { ScatterChart } from '@mui/x-charts/ScatterChart'; +import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; const config = { dataset: [ @@ -25,17 +26,12 @@ const config = { // ..... // 4...3 -const isJSDOM = /jsdom/.test(window.navigator.userAgent); - describe('ScatterChart - click event', () => { const { render } = createRenderer(); - describe('onItemClick - using vornoid', () => { - it('should provide the right context as second argument when clicking svg', function test() { - if (isJSDOM) { - // svg.createSVGPoint not supported by JSDom https://github.com/jsdom/jsdom/issues/300 - this.skip(); - } + // svg.createSVGPoint not supported by JSDom https://github.com/jsdom/jsdom/issues/300 + describeSkipIf(isJSDOM)('onItemClick - using vornoid', () => { + it('should provide the right context as second argument when clicking svg', () => { const onItemClick = spy(); render(
{ expect(onItemClick.callCount).to.equal(2); }); - it('should provide the right context as second argument when clicking mark', function test() { - if (isJSDOM) { - this.skip(); - } + it('should provide the right context as second argument when clicking mark', () => { const onItemClick = spy(); render(
{ }); describe('onItemClick - disabling vornoid', () => { - it('should not call onItemClick when clicking the SVG', function test() { - if (isJSDOM) { - this.skip(); - } + it('should not call onItemClick when clicking the SVG', () => { const onItemClick = spy(); render(
{ expect(onItemClick.callCount).to.equal(0); }); - it('should provide the right context as second argument when clicking mark', function test() { - if (isJSDOM) { - this.skip(); - } + it('should provide the right context as second argument when clicking mark', () => { const onItemClick = spy(); render(
{ window.matchMedia = oldMatchMedia; }); - it('should throw an error when parent context not present', function test() { - if (!/jsdom/.test(window.navigator.userAgent)) { - // can't catch render errors in the browser for unknown reason - // tried try-catch + error boundary + window onError preventDefault - this.skip(); - } - + // can't catch render errors in the browser for unknown reason + // tried try-catch + error boundary + window onError preventDefault + testSkipIf(!isJSDOM)('should throw an error when parent context not present', function test() { const errorRef = React.createRef(); const errorMessage1 = 'MUI X: Could not find the animation ref context.'; diff --git a/packages/x-charts/src/context/HighlightedProvider/useHighlighted.test.tsx b/packages/x-charts/src/context/HighlightedProvider/useHighlighted.test.tsx index fb17106413fd3..92c35d7bcfaf4 100644 --- a/packages/x-charts/src/context/HighlightedProvider/useHighlighted.test.tsx +++ b/packages/x-charts/src/context/HighlightedProvider/useHighlighted.test.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { ErrorBoundary, createRenderer, screen, reactMajor } from '@mui/internal-test-utils'; +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; import { useHighlighted } from './useHighlighted'; import { HighlightedProvider } from './HighlightedProvider'; import { SeriesProvider } from '../SeriesProvider'; @@ -14,13 +15,9 @@ function UseHighlighted() { describe('useHighlighted', () => { const { render } = createRenderer(); - it('should throw an error when parent context not present', function test() { - if (!/jsdom/.test(window.navigator.userAgent)) { - // can't catch render errors in the browser for unknown reason - // tried try-catch + error boundary + window onError preventDefault - this.skip(); - } - + // can't catch render errors in the browser for unknown reason + // tried try-catch + error boundary + window onError preventDefault + testSkipIf(!isJSDOM)('should throw an error when parent context not present', () => { const errorRef = React.createRef(); const errorMessage1 = 'MUI X: Could not find the highlighted ref context.'; diff --git a/packages/x-charts/src/hooks/useSeries.test.tsx b/packages/x-charts/src/hooks/useSeries.test.tsx index 8bc52cb6c2e30..5da1f52bb8af2 100644 --- a/packages/x-charts/src/hooks/useSeries.test.tsx +++ b/packages/x-charts/src/hooks/useSeries.test.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { ErrorBoundary, createRenderer, reactMajor, screen } from '@mui/internal-test-utils'; +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; import { useSeries } from './useSeries'; import { SeriesProvider } from '../context/SeriesProvider'; import { PluginProvider } from '../internals'; @@ -13,13 +14,9 @@ function UseSeries() { describe('useSeries', () => { const { render } = createRenderer(); - it('should throw an error when parent context not present', function test() { - if (!/jsdom/.test(window.navigator.userAgent)) { - // can't catch render errors in the browser for unknown reason - // tried try-catch + error boundary + window onError preventDefault - this.skip(); - } - + // can't catch render errors in the browser for unknown reason + // tried try-catch + error boundary + window onError preventDefault + testSkipIf(!isJSDOM)('should throw an error when parent context not present', () => { const errorRef = React.createRef(); const errorMessage1 = 'MUI X: Could not find the series ref context.'; diff --git a/packages/x-charts/src/hooks/useSvgRef.test.tsx b/packages/x-charts/src/hooks/useSvgRef.test.tsx index 46348937f0dc9..f257395ca4926 100644 --- a/packages/x-charts/src/hooks/useSvgRef.test.tsx +++ b/packages/x-charts/src/hooks/useSvgRef.test.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { ErrorBoundary, createRenderer, reactMajor, screen } from '@mui/internal-test-utils'; +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; import { useSvgRef } from './useSvgRef'; import { ChartProvider } from '../context/ChartProvider'; @@ -16,13 +17,9 @@ function UseSvgRef() { describe('useSvgRef', () => { const { render } = createRenderer(); - it('should throw an error when parent context not present', function test() { - if (!/jsdom/.test(window.navigator.userAgent)) { - // can't catch render errors in the browser for unknown reason - // tried try-catch + error boundary + window onError preventDefault - this.skip(); - } - + // can't catch render errors in the browser for unknown reason + // tried try-catch + error boundary + window onError preventDefault + testSkipIf(!isJSDOM)('should throw an error when parent context not present', () => { const errorRef = React.createRef(); const errorMessages = [ diff --git a/packages/x-data-grid-premium/src/tests/cellSelection.DataGridPremium.test.tsx b/packages/x-data-grid-premium/src/tests/cellSelection.DataGridPremium.test.tsx index 4d8604f27d128..e96d4f09ff8af 100644 --- a/packages/x-data-grid-premium/src/tests/cellSelection.DataGridPremium.test.tsx +++ b/packages/x-data-grid-premium/src/tests/cellSelection.DataGridPremium.test.tsx @@ -12,6 +12,7 @@ import { } from '@mui/x-data-grid-premium'; import { getBasicGridData } from '@mui/x-data-grid-generator'; import { fireUserEvent } from 'test/utils/fireUserEvent'; +import { isJSDOM, describeSkipIf } from 'test/utils/skipIf'; describe(' - Cell selection', () => { const { render } = createRenderer(); @@ -364,14 +365,7 @@ describe(' - Cell selection', () => { }); }); - describe('Auto-scroll', () => { - before(function beforeHook() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - }); - + describeSkipIf(isJSDOM)('Auto-scroll', () => { it('should auto-scroll when the mouse approaches the bottom edge', () => { stub(window, 'requestAnimationFrame').callsFake(() => 0); diff --git a/packages/x-data-grid-premium/src/tests/clipboard.DataGridPremium.test.tsx b/packages/x-data-grid-premium/src/tests/clipboard.DataGridPremium.test.tsx index 3b4c67048c675..775b8155bb023 100644 --- a/packages/x-data-grid-premium/src/tests/clipboard.DataGridPremium.test.tsx +++ b/packages/x-data-grid-premium/src/tests/clipboard.DataGridPremium.test.tsx @@ -12,6 +12,7 @@ import { SinonSpy, spy, stub, SinonStub } from 'sinon'; import { getCell, getColumnValues, sleep } from 'test/utils/helperFn'; import { fireUserEvent } from 'test/utils/fireUserEvent'; import { getBasicGridData } from '@mui/x-data-grid-generator'; +import { isJSDOM, describeSkipIf } from 'test/utils/skipIf'; describe(' - Clipboard', () => { const { render } = createRenderer(); @@ -168,14 +169,8 @@ describe(' - Clipboard', () => { }); }); - describe('paste', () => { - before(function beforeHook() { - if (/jsdom/.test(window.navigator.userAgent)) { - // These test are flaky in JSDOM - this.skip(); - } - }); - + // These test are flaky in JSDOM + describeSkipIf(isJSDOM)('paste', () => { function paste(cell: HTMLElement, pasteText: string) { const pasteEvent = new Event('paste'); diff --git a/packages/x-data-grid-premium/src/tests/columns.DataGridPremium.test.tsx b/packages/x-data-grid-premium/src/tests/columns.DataGridPremium.test.tsx index 979136dc5e7e4..b380acbe3ce27 100644 --- a/packages/x-data-grid-premium/src/tests/columns.DataGridPremium.test.tsx +++ b/packages/x-data-grid-premium/src/tests/columns.DataGridPremium.test.tsx @@ -3,20 +3,15 @@ import { act, createRenderer, fireEvent } from '@mui/internal-test-utils'; import { expect } from 'chai'; import { DataGridPremium, gridClasses } from '@mui/x-data-grid-premium'; import { getCell, getColumnHeaderCell } from 'test/utils/helperFn'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Columns', () => { const { render } = createRenderer(); describe('resizing', () => { // https://github.com/mui/mui-x/issues/10078 - it('should properly resize aggregated column', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - + // Needs layout + testSkipIf(isJSDOM)('should properly resize aggregated column', () => { render(
- Row spanning', () => { const { render } = createRenderer(); @@ -109,10 +108,7 @@ describe(' - Row spanning', () => { } // See https://github.com/mui/mui-x/issues/14691 - it('should not throw when initializing an aggregation model', function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should not throw when initializing an aggregation model', () => { expect(() => render( - Accessibility', () => { - before(function beforeHook() { - if (!/chrome/i.test(window.navigator.userAgent)) { - // Only run accessibility tests in Chrome, since it should behave the same in all browsers - this.skip(); - } - }); - - const { render } = createRenderer(); - - it('pinned columns should pass `aria-required-parent` rule', async () => { - function TestCase() { - const data = useBasicDemoData(1, 3); - return ( -
- -
- ); - } - - render(); - - axe.configure({ - rules: [{ id: 'aria-required-parent', enabled: true }], - disableOtherRules: true, +// Only run accessibility tests in Chrome, since it should behave the same in all browsers +describeSkipIf(!/chrome/i.test(window.navigator.userAgent))( + ' - Accessibility', + () => { + const { render } = createRenderer(); + + it('pinned columns should pass `aria-required-parent` rule', async () => { + function TestCase() { + const data = useBasicDemoData(1, 3); + return ( +
+ +
+ ); + } + + render(); + + axe.configure({ + rules: [{ id: 'aria-required-parent', enabled: true }], + disableOtherRules: true, + }); + + const results = await axe.run(); + + logViolations(results.violations); + expect(results.violations.length).to.equal(0); }); - - const results = await axe.run(); - - logViolations(results.violations); - expect(results.violations.length).to.equal(0); - }); -}); + }, +); diff --git a/packages/x-data-grid-pro/src/tests/columnHeaders.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/columnHeaders.DataGridPro.test.tsx index 90830ac40578e..941db99c7893d 100644 --- a/packages/x-data-grid-pro/src/tests/columnHeaders.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/columnHeaders.DataGridPro.test.tsx @@ -3,8 +3,7 @@ import { createRenderer, fireEvent, screen } from '@mui/internal-test-utils'; import { expect } from 'chai'; import { gridClasses, DataGridPro, DataGridProProps } from '@mui/x-data-grid-pro'; import { getColumnHeaderCell, getColumnValues } from 'test/utils/helperFn'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Column headers', () => { const { render, clock } = createRenderer({ clock: 'fake' }); @@ -31,10 +30,8 @@ describe(' - Column headers', () => { ], }; - it('should not scroll the column headers when a column is focused', function test() { - if (isJSDOM) { - this.skip(); // JSDOM version of .focus() doesn't scroll - } + // JSDOM version of .focus() doesn't scroll + testSkipIf(isJSDOM)('should not scroll the column headers when a column is focused', () => { render(
- Column pinning', () => { const { render, clock } = createRenderer(); @@ -89,70 +88,70 @@ describe(' - Column pinning', () => { window.ResizeObserver = originalResizeObserver; }); - it('should scroll when the next cell to focus is covered by the left pinned columns', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - render(); - const virtualScroller = document.querySelector(`.${gridClasses.virtualScroller}`)!; - virtualScroller.scrollLeft = 100; - act(() => virtualScroller.dispatchEvent(new Event('scroll'))); - const cell = getCell(0, 2); - fireUserEvent.mousePress(cell); - fireEvent.keyDown(cell, { key: 'ArrowLeft' }); - expect(virtualScroller.scrollLeft).to.equal(0); - }); - - it('should scroll when the next cell to focus is covered by the right pinned columns', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - render(); - const virtualScroller = document.querySelector(`.${gridClasses.virtualScroller}`)!; - expect(virtualScroller.scrollLeft).to.equal(0); - const cell = getCell(0, 1); - fireUserEvent.mousePress(cell); - fireEvent.keyDown(cell, { key: 'ArrowRight' }); - expect(virtualScroller.scrollLeft).to.equal(100); - }); - - it('should increase the width of right pinned columns by resizing to the left', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - render(); - const columnHeader = getColumnHeaderCell(2); - expect(columnHeader).toHaveInlineStyle({ width: '100px' }); - - const separator = columnHeader.querySelector(`.${gridClasses['columnSeparator--resizable']}`)!; - fireEvent.mouseDown(separator, { clientX: 200 }); - fireEvent.mouseMove(separator, { clientX: 190, buttons: 1 }); - fireEvent.mouseUp(separator); - - expect(columnHeader).toHaveInlineStyle({ width: '110px' }); - expect(separator).to.have.class(gridClasses['columnSeparator--sideLeft']); - }); - - it('should reduce the width of right pinned columns by resizing to the right', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - render(); - const columnHeader = getColumnHeaderCell(2); - expect(columnHeader).toHaveInlineStyle({ width: '100px' }); - - const separator = columnHeader.querySelector(`.${gridClasses['columnSeparator--resizable']}`)!; - fireEvent.mouseDown(separator, { clientX: 200 }); - fireEvent.mouseMove(separator, { clientX: 210, buttons: 1 }); - fireEvent.mouseUp(separator); + testSkipIf(isJSDOM)( + 'should scroll when the next cell to focus is covered by the left pinned columns', + () => { + render(); + const virtualScroller = document.querySelector(`.${gridClasses.virtualScroller}`)!; + virtualScroller.scrollLeft = 100; + act(() => virtualScroller.dispatchEvent(new Event('scroll'))); + const cell = getCell(0, 2); + fireUserEvent.mousePress(cell); + fireEvent.keyDown(cell, { key: 'ArrowLeft' }); + expect(virtualScroller.scrollLeft).to.equal(0); + }, + ); + + testSkipIf(isJSDOM)( + 'should scroll when the next cell to focus is covered by the right pinned columns', + () => { + render(); + const virtualScroller = document.querySelector(`.${gridClasses.virtualScroller}`)!; + expect(virtualScroller.scrollLeft).to.equal(0); + const cell = getCell(0, 1); + fireUserEvent.mousePress(cell); + fireEvent.keyDown(cell, { key: 'ArrowRight' }); + expect(virtualScroller.scrollLeft).to.equal(100); + }, + ); + + testSkipIf(isJSDOM)( + 'should increase the width of right pinned columns by resizing to the left', + () => { + render(); + const columnHeader = getColumnHeaderCell(2); + expect(columnHeader).toHaveInlineStyle({ width: '100px' }); + + const separator = columnHeader.querySelector( + `.${gridClasses['columnSeparator--resizable']}`, + )!; + fireEvent.mouseDown(separator, { clientX: 200 }); + fireEvent.mouseMove(separator, { clientX: 190, buttons: 1 }); + fireEvent.mouseUp(separator); + + expect(columnHeader).toHaveInlineStyle({ width: '110px' }); + expect(separator).to.have.class(gridClasses['columnSeparator--sideLeft']); + }, + ); + + testSkipIf(isJSDOM)( + 'should reduce the width of right pinned columns by resizing to the right', + () => { + render(); + const columnHeader = getColumnHeaderCell(2); + expect(columnHeader).toHaveInlineStyle({ width: '100px' }); + + const separator = columnHeader.querySelector( + `.${gridClasses['columnSeparator--resizable']}`, + )!; + fireEvent.mouseDown(separator, { clientX: 200 }); + fireEvent.mouseMove(separator, { clientX: 210, buttons: 1 }); + fireEvent.mouseUp(separator); - expect(columnHeader).toHaveInlineStyle({ width: '90px' }); - expect(separator).to.have.class(gridClasses['columnSeparator--sideLeft']); - }); + expect(columnHeader).toHaveInlineStyle({ width: '90px' }); + expect(separator).to.have.class(gridClasses['columnSeparator--sideLeft']); + }, + ); it('should not allow to drag pinned columns', () => { render( @@ -204,35 +203,29 @@ describe(' - Column pinning', () => { expect(getColumnHeadersTextContent()).to.deep.equal(['id', '', 'Currency Pair']); }); - it('should add border to right pinned columns section when `showCellVerticalBorder={true}`', function test() { - if (isJSDOM) { - // Doesn't work with mocked window.getComputedStyle - this.skip(); - } - - render( -
- -
, - ); + // Doesn't work with mocked window.getComputedStyle + testSkipIf(isJSDOM)( + 'should add border to right pinned columns section when `showCellVerticalBorder={true}`', + () => { + render( +
+ +
, + ); - const computedStyle = window.getComputedStyle( - document.querySelector('.MuiDataGrid-cell--pinnedRight')!, - ); - const borderLeftColor = computedStyle.getPropertyValue('border-left-color'); - const borderLeftWidth = computedStyle.getPropertyValue('border-left-width'); - expect(borderLeftWidth).to.equal('1px'); - // should not be transparent - expect(borderLeftColor).not.to.equal('rgba(0, 0, 0, 0)'); - }); + const computedStyle = window.getComputedStyle( + document.querySelector('.MuiDataGrid-cell--pinnedRight')!, + ); + const borderLeftColor = computedStyle.getPropertyValue('border-left-color'); + const borderLeftWidth = computedStyle.getPropertyValue('border-left-width'); + expect(borderLeftWidth).to.equal('1px'); + // should not be transparent + expect(borderLeftColor).not.to.equal('rgba(0, 0, 0, 0)'); + }, + ); // https://github.com/mui/mui-x/issues/12431 - it('should not render unnecessary filler after the last row', function test() { - if (isJSDOM) { - // Needs layouting - this.skip(); - } - + testSkipIf(isJSDOM)('should not render unnecessary filler after the last row', () => { const rowHeight = 50; const columns: GridColDef[] = [ { field: 'id', headerName: 'ID', width: 120 }, diff --git a/packages/x-data-grid-pro/src/tests/columnSpanning.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/columnSpanning.DataGridPro.test.tsx index 4c13b9fb6cbf7..9ced5fb067465 100644 --- a/packages/x-data-grid-pro/src/tests/columnSpanning.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/columnSpanning.DataGridPro.test.tsx @@ -4,8 +4,7 @@ import { expect } from 'chai'; import { DataGridPro, GridApi, useGridApiRef, GridColDef, gridClasses } from '@mui/x-data-grid-pro'; import { getActiveCell, getCell, getColumnHeaderCell } from 'test/utils/helperFn'; import { fireUserEvent } from 'test/utils/fireUserEvent'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Column spanning', () => { const { render } = createRenderer({ clock: 'fake' }); @@ -36,31 +35,30 @@ describe(' - Column spanning', () => { ], }; - it('should not apply `colSpan` in pinned columns section if there is only one column there', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - - render( -
- -
, - ); + // Need layouting + testSkipIf(isJSDOM)( + 'should not apply `colSpan` in pinned columns section if there is only one column there', + () => { + render( +
+ +
, + ); - expect(getCell(0, 0).offsetWidth).to.equal(110); - expect(() => getCell(0, 0)).not.to.throw(); - expect(() => getCell(0, 1)).not.to.throw(); - expect(() => getCell(0, 2)).not.to.throw(); - }); + expect(getCell(0, 0).offsetWidth).to.equal(110); + expect(() => getCell(0, 0)).not.to.throw(); + expect(() => getCell(0, 1)).not.to.throw(); + expect(() => getCell(0, 2)).not.to.throw(); + }, + ); it('should apply `colSpan` inside pinned columns section', () => { render( @@ -155,12 +153,8 @@ describe(' - Column spanning', () => { expect(() => getCell(2, 3)).to.throw(/not found/); }); - it('should work with column resizing', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - + // Need layouting + testSkipIf(isJSDOM)('should work with column resizing', () => { const columns = [{ field: 'brand', colSpan: 2 }, { field: 'category' }, { field: 'price' }]; render( diff --git a/packages/x-data-grid-pro/src/tests/columns.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/columns.DataGridPro.test.tsx index a8f94fcc4132f..d3fad6f040384 100644 --- a/packages/x-data-grid-pro/src/tests/columns.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/columns.DataGridPro.test.tsx @@ -14,8 +14,7 @@ import { } from '@mui/x-data-grid-pro'; import { useGridPrivateApiContext } from '@mui/x-data-grid-pro/internals'; import { getColumnHeaderCell, getCell, microtasks, getRow } from 'test/utils/helperFn'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { describeSkipIf, testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Columns', () => { const { clock, render } = createRenderer({ clock: 'fake' }); @@ -73,14 +72,8 @@ describe(' - Columns', () => { }); }); - describe('resizing', () => { - before(function beforeHook() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - }); - + // Need layouting + describeSkipIf(isJSDOM)('resizing', () => { const columns = [{ field: 'brand', width: 100 }]; it('should allow to resize columns with the mouse', () => { @@ -93,26 +86,26 @@ describe(' - Columns', () => { expect(getCell(1, 0).getBoundingClientRect().width).to.equal(110); }); - it('should allow to resize columns with the touch', function test() { - // Only run in supported browsers - if (typeof Touch === 'undefined') { - this.skip(); - } - render(); - const separator = document.querySelector(`.${gridClasses['columnSeparator--resizable']}`)!; - const now = Date.now(); - fireEvent.touchStart(separator, { - changedTouches: [new Touch({ identifier: now, target: separator, clientX: 100 })], - }); - fireEvent.touchMove(separator, { - changedTouches: [new Touch({ identifier: now, target: separator, clientX: 110 })], - }); - fireEvent.touchEnd(separator, { - changedTouches: [new Touch({ identifier: now, target: separator, clientX: 110 })], - }); - expect(getColumnHeaderCell(0)).toHaveInlineStyle({ width: '110px' }); - expect(getCell(1, 0).getBoundingClientRect().width).to.equal(110); - }); + // Only run in supported browsers + testSkipIf(typeof Touch === 'undefined')( + 'should allow to resize columns with the touch', + () => { + render(); + const separator = document.querySelector(`.${gridClasses['columnSeparator--resizable']}`)!; + const now = Date.now(); + fireEvent.touchStart(separator, { + changedTouches: [new Touch({ identifier: now, target: separator, clientX: 100 })], + }); + fireEvent.touchMove(separator, { + changedTouches: [new Touch({ identifier: now, target: separator, clientX: 110 })], + }); + fireEvent.touchEnd(separator, { + changedTouches: [new Touch({ identifier: now, target: separator, clientX: 110 })], + }); + expect(getColumnHeaderCell(0)).toHaveInlineStyle({ width: '110px' }); + expect(getCell(1, 0).getBoundingClientRect().width).to.equal(110); + }, + ); it('should call onColumnResize during resizing', () => { const onColumnResize = spy(); @@ -301,14 +294,8 @@ describe(' - Columns', () => { expect(emptyCell.getBoundingClientRect().width).to.equal(rowWidth - 50); }); - describe('flex resizing', () => { - before(function beforeHook() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - }); - + // Need layouting + describeSkipIf(isJSDOM)('flex resizing', () => { it('should resize the flex width after resizing another column with api', () => { const twoColumns = [ { field: 'id', width: 100, flex: 1 }, @@ -498,14 +485,8 @@ describe(' - Columns', () => { }); }); - describe('autosizing', () => { - before(function beforeHook() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - }); - + // Need layouting + describeSkipIf(isJSDOM)('autosizing', () => { const rows = [ { id: 0, brand: 'Nike' }, { id: 1, brand: 'Adidas' }, diff --git a/packages/x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx index 8c10d9924034d..fbbc993501f36 100644 --- a/packages/x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx @@ -13,9 +13,9 @@ import { useGridApiRef, } from '@mui/x-data-grid-pro'; import { SinonSpy, spy } from 'sinon'; +import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; import { getKey } from '../hooks/features/dataSource/cache'; -const isJSDOM = /jsdom/.test(window.navigator.userAgent); const cache = new Map(); const testCache: GridDataSourceCache = { @@ -24,7 +24,8 @@ const testCache: GridDataSourceCache = { clear: () => cache.clear(), }; -describe(' - Data source', () => { +// Needs layout +describeSkipIf(isJSDOM)(' - Data source', () => { const { render } = createRenderer(); let apiRef: React.MutableRefObject; @@ -79,11 +80,8 @@ describe(' - Data source', () => { ); } - beforeEach(function beforeTest() { - if (isJSDOM) { - this.skip(); // Needs layout - } - + // eslint-disable-next-line mocha/no-top-level-hooks + beforeEach(() => { cache.clear(); }); diff --git a/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx index 9053706deeb6a..9cab47e58440a 100644 --- a/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/dataSourceLazyLoader.DataGridPro.test.tsx @@ -13,10 +13,10 @@ import { useGridApiRef, } from '@mui/x-data-grid-pro'; import { SinonSpy, spy } from 'sinon'; +import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; -const isJSDOM = /jsdom/.test(window.navigator.userAgent); - -describe(' - Data source lazy loader', () => { +// Needs layout +describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { const { render } = createRenderer(); const defaultTransformGetRowsResponse = (response: GridGetRowsResponse) => response; @@ -73,11 +73,8 @@ describe(' - Data source lazy loader', () => { ); } - beforeEach(function beforeTest() { - if (isJSDOM) { - this.skip(); // Needs layout - } - + // eslint-disable-next-line mocha/no-top-level-hooks + beforeEach(() => { transformGetRowsResponse = defaultTransformGetRowsResponse; }); diff --git a/packages/x-data-grid-pro/src/tests/dataSourceTreeData.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/dataSourceTreeData.DataGridPro.test.tsx index 8d71f25c41366..a0c883655b2ae 100644 --- a/packages/x-data-grid-pro/src/tests/dataSourceTreeData.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/dataSourceTreeData.DataGridPro.test.tsx @@ -14,8 +14,7 @@ import { } from '@mui/x-data-grid-pro'; import { SinonSpy, spy } from 'sinon'; import { getCell } from 'test/utils/helperFn'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; const dataSetOptions = { dataSet: 'Employee' as const, @@ -27,7 +26,8 @@ const pageSizeOptions = [5, 10, 50]; const serverOptions = { minDelay: 0, maxDelay: 0, verbose: false }; -describe(' - Data source tree data', () => { +// Needs layout +describeSkipIf(isJSDOM)(' - Data source tree data', () => { const { render } = createRenderer(); let apiRef: React.MutableRefObject; @@ -82,12 +82,6 @@ describe(' - Data source tree data', () => { ); } - beforeEach(function beforeTest() { - if (isJSDOM) { - this.skip(); // Needs layout - } - }); - it('should fetch the data on initial render', async () => { render(); await waitFor(() => { diff --git a/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx index 27f4105824d40..4d59cc70f476d 100644 --- a/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx @@ -21,8 +21,7 @@ import { } from '@mui/internal-test-utils'; import { $, $$, grid, getRow, getCell, getColumnValues, microtasks } from 'test/utils/helperFn'; import { fireUserEvent } from 'test/utils/fireUserEvent'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Detail panel', () => { const { render } = createRenderer(); @@ -39,128 +38,129 @@ describe(' - Detail panel', () => { ); } - it('should not allow to expand rows that do not specify a detail element', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - render( (Number(id) === 0 ? null :
)} />); - const cell = getCell(0, 0); - expect(cell.querySelector('[aria-label="Expand"]')).to.have.attribute('disabled'); - fireEvent.click(cell); - expect(getRow(0)).toHaveComputedStyle({ marginBottom: '0px' }); - }); - - it('should not consider the height of the detail panels when rendering new rows during scroll', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - const rowHeight = 50; - render( - (Number(id) % 2 === 0 ? 1 : 2) * rowHeight} // 50px for even rows, otherwise 100px - getDetailPanelContent={() =>
} - rowHeight={rowHeight} - rowBufferPx={0} - initialState={{ - detailPanel: { - expandedRowIds: new Set([0, 1]), - }, - }} - />, - ); - expect(getColumnValues(1)[0]).to.equal('0'); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - virtualScroller.scrollTop = 250; // 50 + 50 (detail panel) + 50 + 100 (detail panel * 2) - act(() => virtualScroller.dispatchEvent(new Event('scroll'))); - expect(getColumnValues(1)[0]).to.equal('2'); // If there was no expanded row, the first rendered would be 5 - }); - - it('should derive the height from the content if getDetailPanelHeight returns "auto"', async function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - const rowHeight = 50; - const detailPanelHeight = 100; - render( -
} - getDetailPanelHeight={() => 'auto'} - />, - ); - fireEvent.click(screen.getAllByRole('button', { name: 'Expand' })[0]); - await microtasks(); - - const virtualScrollerContent = $('.MuiDataGrid-virtualScrollerContent')!; - expect(virtualScrollerContent).toHaveComputedStyle({ - height: `${rowHeight + detailPanelHeight}px`, - }); - expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); + // Needs layout + testSkipIf(isJSDOM)( + 'should not allow to expand rows that do not specify a detail element', + () => { + render( (Number(id) === 0 ? null :
)} />); + const cell = getCell(0, 0); + expect(cell.querySelector('[aria-label="Expand"]')).to.have.attribute('disabled'); + fireEvent.click(cell); + expect(getRow(0)).toHaveComputedStyle({ marginBottom: '0px' }); + }, + ); + + // Needs layout + testSkipIf(isJSDOM)( + 'should not consider the height of the detail panels when rendering new rows during scroll', + () => { + const rowHeight = 50; + render( + (Number(id) % 2 === 0 ? 1 : 2) * rowHeight} // 50px for even rows, otherwise 100px + getDetailPanelContent={() =>
} + rowHeight={rowHeight} + rowBufferPx={0} + initialState={{ + detailPanel: { + expandedRowIds: new Set([0, 1]), + }, + }} + />, + ); + expect(getColumnValues(1)[0]).to.equal('0'); + const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; + virtualScroller.scrollTop = 250; // 50 + 50 (detail panel) + 50 + 100 (detail panel * 2) + act(() => virtualScroller.dispatchEvent(new Event('scroll'))); + expect(getColumnValues(1)[0]).to.equal('2'); // If there was no expanded row, the first rendered would be 5 + }, + ); + + // Needs layout + testSkipIf(isJSDOM)( + 'should derive the height from the content if getDetailPanelHeight returns "auto"', + async () => { + const rowHeight = 50; + const detailPanelHeight = 100; + render( +
} + getDetailPanelHeight={() => 'auto'} + />, + ); + fireEvent.click(screen.getAllByRole('button', { name: 'Expand' })[0]); + await microtasks(); - const detailPanels = $$('.MuiDataGrid-detailPanel'); - expect(detailPanels[0]).toHaveComputedStyle({ - height: `${detailPanelHeight}px`, - }); - }); + const virtualScrollerContent = $('.MuiDataGrid-virtualScrollerContent')!; + expect(virtualScrollerContent).toHaveComputedStyle({ + height: `${rowHeight + detailPanelHeight}px`, + }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); - it('should update the detail panel height if the content height changes when getDetailPanelHeight returns "auto"', async function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - function ExpandableCell() { - const [expanded, setExpanded] = React.useState(false); - return ( -
- -
+ const detailPanels = $$('.MuiDataGrid-detailPanel'); + expect(detailPanels[0]).toHaveComputedStyle({ + height: `${detailPanelHeight}px`, + }); + }, + ); + + // Needs layout + testSkipIf(isJSDOM)( + 'should update the detail panel height if the content height changes when getDetailPanelHeight returns "auto"', + async () => { + function ExpandableCell() { + const [expanded, setExpanded] = React.useState(false); + return ( +
+ +
+ ); + } + const rowHeight = 50; + render( + } + getDetailPanelHeight={() => 'auto'} + />, ); - } - const rowHeight = 50; - render( - } - getDetailPanelHeight={() => 'auto'} - />, - ); - const virtualScrollerContent = grid('virtualScrollerContent')!; - fireEvent.click(screen.getByRole('button', { name: 'Expand' })); - - await waitFor(() => { - expect(getRow(0).className).to.include(gridClasses['row--detailPanelExpanded']); - }); + const virtualScrollerContent = grid('virtualScrollerContent')!; + fireEvent.click(screen.getByRole('button', { name: 'Expand' })); - await waitFor(() => { - expect(virtualScrollerContent).toHaveComputedStyle({ height: `${rowHeight + 100}px` }); - }); - expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); + await waitFor(() => { + expect(getRow(0).className).to.include(gridClasses['row--detailPanelExpanded']); + }); - const detailPanels = $$('.MuiDataGrid-detailPanel'); - expect(detailPanels[0]).toHaveComputedStyle({ - height: `100px`, - }); + await waitFor(() => { + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${rowHeight + 100}px` }); + }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); - fireEvent.click(screen.getByRole('button', { name: 'Increase' })); + const detailPanels = $$('.MuiDataGrid-detailPanel'); + expect(detailPanels[0]).toHaveComputedStyle({ + height: `100px`, + }); - await waitFor(() => { - expect(virtualScrollerContent).toHaveComputedStyle({ height: `${rowHeight + 200}px` }); - }); - expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); + fireEvent.click(screen.getByRole('button', { name: 'Increase' })); - expect(detailPanels[0]).toHaveComputedStyle({ - height: `200px`, - }); - }); + await waitFor(() => { + expect(virtualScrollerContent).toHaveComputedStyle({ height: `${rowHeight + 200}px` }); + }); + expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); - it('should position correctly the detail panels', function test() { - if (isJSDOM) { - this.skip(); // Doesn't work with mocked window.getComputedStyle - } + expect(detailPanels[0]).toHaveComputedStyle({ + height: `200px`, + }); + }, + ); + // Doesn't work with mocked window.getComputedStyle + testSkipIf(isJSDOM)('should position correctly the detail panels', () => { const rowHeight = 50; const evenHeight = rowHeight; const oddHeight = 2 * rowHeight; @@ -204,38 +204,37 @@ describe(' - Detail panel', () => { expect(screen.queryByText('Row 0')).to.equal(null); }); - it('should consider the height of the detail panel when scrolling to a cell', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - const rowHeight = 50; - const columnHeaderHeight = 50; - render( - rowHeight} - getDetailPanelContent={() =>
} - rowHeight={rowHeight} - columnHeaderHeight={columnHeaderHeight} - initialState={{ - detailPanel: { - expandedRowIds: new Set([0]), - }, - }} - hideFooter - />, - ); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - fireUserEvent.mousePress(getCell(2, 1)); - fireEvent.keyDown(getCell(2, 1), { key: 'ArrowDown' }); - expect(virtualScroller.scrollTop).to.equal(0); - fireEvent.keyDown(getCell(3, 1), { key: 'ArrowDown' }); - expect(virtualScroller.scrollTop).to.equal(50); - }); - - it('should not scroll vertically when navigating expanded row cells', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } + // Needs layout + testSkipIf(isJSDOM)( + 'should consider the height of the detail panel when scrolling to a cell', + () => { + const rowHeight = 50; + const columnHeaderHeight = 50; + render( + rowHeight} + getDetailPanelContent={() =>
} + rowHeight={rowHeight} + columnHeaderHeight={columnHeaderHeight} + initialState={{ + detailPanel: { + expandedRowIds: new Set([0]), + }, + }} + hideFooter + />, + ); + const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; + fireUserEvent.mousePress(getCell(2, 1)); + fireEvent.keyDown(getCell(2, 1), { key: 'ArrowDown' }); + expect(virtualScroller.scrollTop).to.equal(0); + fireEvent.keyDown(getCell(3, 1), { key: 'ArrowDown' }); + expect(virtualScroller.scrollTop).to.equal(50); + }, + ); + + // Needs layout + testSkipIf(isJSDOM)('should not scroll vertically when navigating expanded row cells', () => { function Component() { const data = useBasicDemoData(10, 4); return ( @@ -381,40 +380,40 @@ describe(' - Detail panel', () => { expect(getDetailPanelHeight2.callCount).to.equal(2); }); - it('should update the panel height if getDetailPanelHeight is changed while the panel is open', function test() { - if (isJSDOM) { - this.skip(); // Doesn't work with mocked window.getComputedStyle - } - - const getDetailPanelHeight = spy(() => 100); - const { setProps } = render( -
Detail
} - getDetailPanelHeight={getDetailPanelHeight} - pagination - pageSizeOptions={[1]} - initialState={{ pagination: { paginationModel: { pageSize: 1 } } }} - autoHeight - />, - ); + // Doesn't work with mocked window.getComputedStyle + testSkipIf(isJSDOM)( + 'should update the panel height if getDetailPanelHeight is changed while the panel is open', + () => { + const getDetailPanelHeight = spy(() => 100); + const { setProps } = render( +
Detail
} + getDetailPanelHeight={getDetailPanelHeight} + pagination + pageSizeOptions={[1]} + initialState={{ pagination: { paginationModel: { pageSize: 1 } } }} + autoHeight + />, + ); - fireEvent.click(screen.getByRole('button', { name: 'Expand' })); - const detailPanel = $$('.MuiDataGrid-detailPanel')[0]; - expect(detailPanel).toHaveComputedStyle({ height: '100px' }); - const virtualScroller = grid('virtualScroller')!; - expect(virtualScroller.scrollHeight).to.equal(208); + fireEvent.click(screen.getByRole('button', { name: 'Expand' })); + const detailPanel = $$('.MuiDataGrid-detailPanel')[0]; + expect(detailPanel).toHaveComputedStyle({ height: '100px' }); + const virtualScroller = grid('virtualScroller')!; + expect(virtualScroller.scrollHeight).to.equal(208); - const getDetailPanelHeight2 = spy(() => 200); - setProps({ getDetailPanelHeight: getDetailPanelHeight2 }); + const getDetailPanelHeight2 = spy(() => 200); + setProps({ getDetailPanelHeight: getDetailPanelHeight2 }); - expect(detailPanel).toHaveComputedStyle({ height: '200px' }); - expect(virtualScroller.scrollHeight).to.equal(200 + 52 + 56); - }); + expect(detailPanel).toHaveComputedStyle({ height: '200px' }); + expect(virtualScroller.scrollHeight).to.equal(200 + 52 + 56); + }, + ); it('should only call getDetailPanelHeight on the rows that have detail content', () => { const getDetailPanelHeight = spy(({ row }) => row.id + 100); // Use `row` to allow to assert its args below @@ -458,10 +457,8 @@ describe(' - Detail panel', () => { }); // See https://github.com/mui/mui-x/issues/4607 - it('should make detail panel to take full width of the content', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } + // Needs layout + testSkipIf(isJSDOM)('should make detail panel to take full width of the content', () => { render(
Detail
} @@ -485,20 +482,20 @@ describe(' - Detail panel', () => { }); // See https://github.com/mui/mui-x/issues/6694 - it('should add a bottom margin to the expanded row when using `getRowSpacing`', function test() { - if (isJSDOM) { - this.skip(); // Doesn't work with mocked window.getComputedStyle - } - - render( - (id === 0 ?
: null)} - getRowSpacing={() => ({ top: 2, bottom: 2 })} - />, - ); - fireEvent.click(screen.getAllByRole('button', { name: 'Expand' })[0]); - expect(getRow(0)).toHaveComputedStyle({ marginBottom: '2px' }); - }); + // Doesn't work with mocked window.getComputedStyle + testSkipIf(isJSDOM)( + 'should add a bottom margin to the expanded row when using `getRowSpacing`', + () => { + render( + (id === 0 ?
: null)} + getRowSpacing={() => ({ top: 2, bottom: 2 })} + />, + ); + fireEvent.click(screen.getAllByRole('button', { name: 'Expand' })[0]); + expect(getRow(0)).toHaveComputedStyle({ marginBottom: '2px' }); + }, + ); it('should not reuse detail panel components', () => { let counter = 0; @@ -517,28 +514,29 @@ describe(' - Detail panel', () => { expect(screen.getByTestId(`detail-panel-content`).textContent).to.equal(`${counter}`); }); - it("should not render detail panel for the focused row if it's outside of the viewport", function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - render( - 50} - getDetailPanelContent={() =>
} - rowBufferPx={0} - nbRows={20} - />, - ); + // Needs layout + testSkipIf(isJSDOM)( + "should not render detail panel for the focused row if it's outside of the viewport", + () => { + render( + 50} + getDetailPanelContent={() =>
} + rowBufferPx={0} + nbRows={20} + />, + ); - fireUserEvent.mousePress(screen.getAllByRole('button', { name: 'Expand' })[0]); + fireUserEvent.mousePress(screen.getAllByRole('button', { name: 'Expand' })[0]); - const virtualScroller = document.querySelector(`.${gridClasses.virtualScroller}`)!; - virtualScroller.scrollTop = 500; - act(() => virtualScroller.dispatchEvent(new Event('scroll'))); + const virtualScroller = document.querySelector(`.${gridClasses.virtualScroller}`)!; + virtualScroller.scrollTop = 500; + act(() => virtualScroller.dispatchEvent(new Event('scroll'))); - const detailPanels = document.querySelectorAll(`.${gridClasses.detailPanel}`); - expect(detailPanels.length).to.equal(0); - }); + const detailPanels = document.querySelectorAll(`.${gridClasses.detailPanel}`); + expect(detailPanels.length).to.equal(0); + }, + ); describe('prop: onDetailPanelsExpandedRowIds', () => { it('should call when a row is expanded or closed', async () => { diff --git a/packages/x-data-grid-pro/src/tests/events.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/events.DataGridPro.test.tsx index 9a3625a655090..2ebe119234e60 100644 --- a/packages/x-data-grid-pro/src/tests/events.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/events.DataGridPro.test.tsx @@ -17,8 +17,7 @@ import { } from '@mui/x-data-grid-pro'; import { getCell, getColumnHeaderCell } from 'test/utils/helperFn'; import { spy } from 'sinon'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Events params', () => { const { render, clock } = createRenderer(); @@ -329,24 +328,25 @@ describe(' - Events params', () => { }); }); - it('publishing GRID_ROWS_SCROLL should call onFetchRows callback when rows lazy loading is enabled', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - const handleFetchRows = spy(); - render( - , - ); - act(() => apiRef.current.publishEvent('scrollPositionChange', { left: 0, top: 3 * 52 })); - expect(handleFetchRows.callCount).to.equal(1); - }); + // Needs layout + testSkipIf(isJSDOM)( + 'publishing GRID_ROWS_SCROLL should call onFetchRows callback when rows lazy loading is enabled', + () => { + const handleFetchRows = spy(); + render( + , + ); + act(() => apiRef.current.publishEvent('scrollPositionChange', { left: 0, top: 3 * 52 })); + expect(handleFetchRows.callCount).to.equal(1); + }, + ); it('should publish "unmount" event when unmounting the Grid', () => { const onUnmount = spy(); diff --git a/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx index be6c832a2d931..61c59d1c7593d 100644 --- a/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/filtering.DataGridPro.test.tsx @@ -23,11 +23,10 @@ import { expect } from 'chai'; import * as React from 'react'; import { spy } from 'sinon'; import { getColumnHeaderCell, getColumnValues, getSelectInput, grid } from 'test/utils/helperFn'; +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; const SUBMIT_FILTER_STROKE_TIME = DATA_GRID_PRO_PROPS_DEFAULT_VALUES.filterDebounceMs; -const isJSDOM = /jsdom/.test(window.navigator.userAgent); - describe(' - Filter', () => { const { clock, render } = createRenderer({ clock: 'fake' }); @@ -660,10 +659,8 @@ describe(' - Filter', () => { }); }); - it('should not scroll the page when a filter is removed from the panel', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } + // Needs layout + testSkipIf(isJSDOM)('should not scroll the page when a filter is removed from the panel', () => { render(
{/* To simulate a page that needs to be scrolled to reach the grid. */} @@ -694,40 +691,40 @@ describe(' - Filter', () => { expect(window.scrollY).to.equal(initialScrollPosition); }); - it('should not scroll the page when opening the filter panel and the operator=isAnyOf', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - - render( -
- {/* To simulate a page that needs to be scrolled to reach the grid. */} -
- { + render( +
+ {/* To simulate a page that needs to be scrolled to reach the grid. */} +
+ -
, - ); + filter: { + filterModel: { + logicOperator: GridLogicOperator.Or, + items: [{ id: 1, field: 'brand', operator: 'isAnyOf' }], + }, + }, + }} + /> +
, + ); - grid('root')!.scrollIntoView(); - const initialScrollPosition = window.scrollY; - expect(initialScrollPosition).not.to.equal(0); - act(() => apiRef.current.hidePreferences()); - clock.tick(100); - act(() => apiRef.current.showPreferences(GridPreferencePanelsValue.filters)); - expect(window.scrollY).to.equal(initialScrollPosition); - }); + grid('root')!.scrollIntoView(); + const initialScrollPosition = window.scrollY; + expect(initialScrollPosition).not.to.equal(0); + act(() => apiRef.current.hidePreferences()); + clock.tick(100); + act(() => apiRef.current.showPreferences(GridPreferencePanelsValue.filters)); + expect(window.scrollY).to.equal(initialScrollPosition); + }, + ); describe('Server', () => { it('should refresh the filter panel when adding filters', async () => { @@ -912,11 +909,8 @@ describe(' - Filter', () => { }); }); - it('should give a stable ID to the filter item used as placeholder', function test() { - if (isJSDOM) { - this.skip(); // It's not re-rendering the filter panel correctly - } - + // It's not re-rendering the filter panel correctly + testSkipIf(isJSDOM)('should give a stable ID to the filter item used as placeholder', () => { const { rerender } = render(); const filtersButton = screen.getByRole('button', { name: /Filters/i }); fireEvent.click(filtersButton); @@ -1228,7 +1222,7 @@ describe(' - Filter', () => { expect(getColumnValues(1)).to.deep.equal(['Puma']); }); - it('should allow updating logic operator even from read-only filters', function test() { + it('should allow updating logic operator even from read-only filters', () => { const newModel = { items: [ { diff --git a/packages/x-data-grid-pro/src/tests/infiniteLoader.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/infiniteLoader.DataGridPro.test.tsx index 5f8b9422490b9..0db2596aab8a6 100644 --- a/packages/x-data-grid-pro/src/tests/infiniteLoader.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/infiniteLoader.DataGridPro.test.tsx @@ -4,8 +4,7 @@ import { expect } from 'chai'; import { DataGridPro } from '@mui/x-data-grid-pro'; import { spy, restore } from 'sinon'; import { getColumnValues, sleep } from 'test/utils/helperFn'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Infnite loader', () => { afterEach(() => { @@ -14,191 +13,193 @@ describe(' - Infnite loader', () => { const { render } = createRenderer(); - it('should call `onRowsScrollEnd` when viewport scroll reaches the bottom', async function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - const baseRows = [ - { id: 0, brand: 'Nike' }, - { id: 1, brand: 'Adidas' }, - { id: 2, brand: 'Puma' }, - { id: 3, brand: 'Under Armor' }, - { id: 4, brand: 'Jordan' }, - { id: 5, brand: 'Reebok' }, - ]; - const handleRowsScrollEnd = spy(); - function TestCase({ rows }: { rows: typeof baseRows }) { - return ( -
- -
- ); - } - const { container, setProps } = render(); - const virtualScroller = container.querySelector('.MuiDataGrid-virtualScroller')!; - - await act(async () => { - // arbitrary number to make sure that the bottom of the grid window is reached. - virtualScroller.scrollTop = 12345; - virtualScroller.dispatchEvent(new Event('scroll')); - }); + // Needs layout + testSkipIf(isJSDOM)( + 'should call `onRowsScrollEnd` when viewport scroll reaches the bottom', + async () => { + const baseRows = [ + { id: 0, brand: 'Nike' }, + { id: 1, brand: 'Adidas' }, + { id: 2, brand: 'Puma' }, + { id: 3, brand: 'Under Armor' }, + { id: 4, brand: 'Jordan' }, + { id: 5, brand: 'Reebok' }, + ]; + const handleRowsScrollEnd = spy(); + function TestCase({ rows }: { rows: typeof baseRows }) { + return ( +
+ +
+ ); + } + const { container, setProps } = render(); + const virtualScroller = container.querySelector('.MuiDataGrid-virtualScroller')!; + + await act(async () => { + // arbitrary number to make sure that the bottom of the grid window is reached. + virtualScroller.scrollTop = 12345; + virtualScroller.dispatchEvent(new Event('scroll')); + }); - await waitFor(() => { - expect(handleRowsScrollEnd.callCount).to.equal(1); - }); - - await act(async () => { - setProps({ - rows: baseRows.concat( - { id: 6, brand: 'Gucci' }, - { id: 7, brand: "Levi's" }, - { id: 8, brand: 'Ray-Ban' }, - ), + await waitFor(() => { + expect(handleRowsScrollEnd.callCount).to.equal(1); }); - // Trigger a scroll again to notify the grid that we're not in the bottom area anymore - virtualScroller.dispatchEvent(new Event('scroll')); - }); + await act(async () => { + setProps({ + rows: baseRows.concat( + { id: 6, brand: 'Gucci' }, + { id: 7, brand: "Levi's" }, + { id: 8, brand: 'Ray-Ban' }, + ), + }); - expect(handleRowsScrollEnd.callCount).to.equal(1); + // Trigger a scroll again to notify the grid that we're not in the bottom area anymore + virtualScroller.dispatchEvent(new Event('scroll')); + }); - await act(async () => { - virtualScroller.scrollTop = 12345; - virtualScroller.dispatchEvent(new Event('scroll')); - }); + expect(handleRowsScrollEnd.callCount).to.equal(1); - await waitFor(() => { - expect(handleRowsScrollEnd.callCount).to.equal(2); - }); - }); + await act(async () => { + virtualScroller.scrollTop = 12345; + virtualScroller.dispatchEvent(new Event('scroll')); + }); - it('should call `onRowsScrollEnd` when there is not enough rows to cover the viewport height', async function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - - const allRows = [ - { id: 0, brand: 'Nike' }, - { id: 1, brand: 'Adidas' }, - { id: 2, brand: 'Puma' }, - { id: 3, brand: 'Under Armor' }, - { id: 4, brand: 'Jordan' }, - { id: 5, brand: 'Reebok' }, - ]; - const initialRows = [allRows[0]]; - const getRow = spy((id) => { - return allRows.find((row) => row.id === id); - }); - - const scrollEndThreshold = 60; - const rowHeight = 50; - const columnHeaderHeight = 50; - const gridHeight = - 4 * rowHeight + - columnHeaderHeight + - // border - 2; - - function TestCase() { - const [rows, setRows] = React.useState(initialRows); - const [loading, setLoading] = React.useState(false); - const handleRowsScrollEnd = React.useCallback(async () => { - setLoading(true); - setRows((prevRows) => { - const lastRowId = prevRows[prevRows.length - 1].id; - const nextRow = getRow(lastRowId + 1); - return nextRow ? prevRows.concat(nextRow) : prevRows; - }); - setLoading(false); - }, []); - return ( -
- -
- ); - } - render(); - - // Data Grid should have loaded 6 rows: - // 1 initial row - // 5 rows loaded one by one through `onRowsScrollEnd` callback - - const multiplier = 2; // `setRows` is called twice for each `handleRowsScrollEnd` call - await waitFor(() => { - expect(getRow.callCount).to.equal(5 * multiplier); - }); - - const getRowCalls = getRow.getCalls(); - for (let callIndex = 0; callIndex < getRowCalls.length; callIndex += multiplier) { - const call = getRowCalls[callIndex]; - expect(call.returnValue?.id).to.equal(callIndex / multiplier + 1); - } - - await waitFor(() => { - expect(getColumnValues(0)).to.deep.equal(['0', '1', '2', '3', '4', '5']); - }); - }); + await waitFor(() => { + expect(handleRowsScrollEnd.callCount).to.equal(2); + }); + }, + ); + + // Needs layout + testSkipIf(isJSDOM)( + 'should call `onRowsScrollEnd` when there is not enough rows to cover the viewport height', + async () => { + const allRows = [ + { id: 0, brand: 'Nike' }, + { id: 1, brand: 'Adidas' }, + { id: 2, brand: 'Puma' }, + { id: 3, brand: 'Under Armor' }, + { id: 4, brand: 'Jordan' }, + { id: 5, brand: 'Reebok' }, + ]; + const initialRows = [allRows[0]]; + const getRow = spy((id) => { + return allRows.find((row) => row.id === id); + }); - it('should not observe intersections with the rows pinned to the bottom', async function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - const baseRows = [ - { id: 0, brand: 'Nike' }, - { id: 1, brand: 'Adidas' }, - { id: 2, brand: 'Puma' }, - { id: 3, brand: 'Under Armor' }, - { id: 4, brand: 'Jordan' }, - { id: 5, brand: 'Reebok' }, - ]; - const basePinnedRows = { - bottom: [{ id: 6, brand: 'Unbranded' }], - }; - - const handleRowsScrollEnd = spy(); - const observe = spy(window.IntersectionObserver.prototype, 'observe'); - - function TestCase({ - rows, - pinnedRows, - }: { - rows: typeof baseRows; - pinnedRows: typeof basePinnedRows; - }) { - return ( -
- -
- ); - } - const { container } = render(); - const virtualScroller = container.querySelector('.MuiDataGrid-virtualScroller')!; - // on the initial render, last row is not visible and the `observe` method is not called - expect(observe.callCount).to.equal(0); - // arbitrary number to make sure that the bottom of the grid window is reached. - virtualScroller.scrollTop = 12345; - virtualScroller.dispatchEvent(new Event('scroll')); - // wait for the next render cycle - await sleep(0); - // observer was attached - expect(observe.callCount).to.equal(1); - }); + const scrollEndThreshold = 60; + const rowHeight = 50; + const columnHeaderHeight = 50; + const gridHeight = + 4 * rowHeight + + columnHeaderHeight + + // border + 2; + + function TestCase() { + const [rows, setRows] = React.useState(initialRows); + const [loading, setLoading] = React.useState(false); + const handleRowsScrollEnd = React.useCallback(async () => { + setLoading(true); + setRows((prevRows) => { + const lastRowId = prevRows[prevRows.length - 1].id; + const nextRow = getRow(lastRowId + 1); + return nextRow ? prevRows.concat(nextRow) : prevRows; + }); + setLoading(false); + }, []); + return ( +
+ +
+ ); + } + render(); + + // Data Grid should have loaded 6 rows: + // 1 initial row + // 5 rows loaded one by one through `onRowsScrollEnd` callback + + const multiplier = 2; // `setRows` is called twice for each `handleRowsScrollEnd` call + await waitFor(() => { + expect(getRow.callCount).to.equal(5 * multiplier); + }); + + const getRowCalls = getRow.getCalls(); + for (let callIndex = 0; callIndex < getRowCalls.length; callIndex += multiplier) { + const call = getRowCalls[callIndex]; + expect(call.returnValue?.id).to.equal(callIndex / multiplier + 1); + } + + await waitFor(() => { + expect(getColumnValues(0)).to.deep.equal(['0', '1', '2', '3', '4', '5']); + }); + }, + ); + + // Needs layout + testSkipIf(isJSDOM)( + 'should not observe intersections with the rows pinned to the bottom', + async () => { + const baseRows = [ + { id: 0, brand: 'Nike' }, + { id: 1, brand: 'Adidas' }, + { id: 2, brand: 'Puma' }, + { id: 3, brand: 'Under Armor' }, + { id: 4, brand: 'Jordan' }, + { id: 5, brand: 'Reebok' }, + ]; + const basePinnedRows = { + bottom: [{ id: 6, brand: 'Unbranded' }], + }; + + const handleRowsScrollEnd = spy(); + const observe = spy(window.IntersectionObserver.prototype, 'observe'); + + function TestCase({ + rows, + pinnedRows, + }: { + rows: typeof baseRows; + pinnedRows: typeof basePinnedRows; + }) { + return ( +
+ +
+ ); + } + const { container } = render(); + const virtualScroller = container.querySelector('.MuiDataGrid-virtualScroller')!; + // on the initial render, last row is not visible and the `observe` method is not called + expect(observe.callCount).to.equal(0); + // arbitrary number to make sure that the bottom of the grid window is reached. + virtualScroller.scrollTop = 12345; + virtualScroller.dispatchEvent(new Event('scroll')); + // wait for the next render cycle + await sleep(0); + // observer was attached + expect(observe.callCount).to.equal(1); + }, + ); }); diff --git a/packages/x-data-grid-pro/src/tests/layout.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/layout.DataGridPro.test.tsx index 34c8e9ded21ff..ea343ab78f6c6 100644 --- a/packages/x-data-grid-pro/src/tests/layout.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/layout.DataGridPro.test.tsx @@ -5,8 +5,9 @@ import { createTheme, ThemeProvider } from '@mui/material/styles'; import { GridApi, useGridApiRef, DataGridPro, DataGridProProps } from '@mui/x-data-grid-pro'; import { ptBR } from '@mui/x-data-grid-pro/locales'; import { grid } from 'test/utils/helperFn'; +import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; -describe(' - Layout', () => { +describeSkipIf(isJSDOM)(' - Layout', () => { const { render } = createRenderer(); const baselineProps = { @@ -27,13 +28,6 @@ describe(' - Layout', () => { columns: [{ field: 'brand', width: 100 }], }; - before(function beforeHook() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - }); - // Adaptation of describeConformance() describe('MUI component API', () => { it(`attaches the ref`, () => { @@ -170,11 +164,7 @@ describe(' - Layout', () => { expect(document.querySelector('[title="Ordenar"]')).not.to.equal(null); }); - it('should support the sx prop', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); // Doesn't work with mocked window.getComputedStyle - } - + it('should support the sx prop', () => { const theme = createTheme({ palette: { primary: { diff --git a/packages/x-data-grid-pro/src/tests/lazyLoader.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/lazyLoader.DataGridPro.test.tsx index 1f2dbac26b937..a253711b1e62a 100644 --- a/packages/x-data-grid-pro/src/tests/lazyLoader.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/lazyLoader.DataGridPro.test.tsx @@ -14,8 +14,7 @@ import { useGridApiRef, } from '@mui/x-data-grid-pro'; import { spy } from 'sinon'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Lazy loader', () => { const { render } = createRenderer(); @@ -57,20 +56,16 @@ describe(' - Lazy loader', () => { ); } - it('should not call onFetchRows if the viewport is fully loaded', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } + // Needs layout + testSkipIf(isJSDOM)('should not call onFetchRows if the viewport is fully loaded', () => { const handleFetchRows = spy(); const rows = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }, { id: 7 }]; render(); expect(handleFetchRows.callCount).to.equal(0); }); - it('should call onFetchRows when sorting is applied', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } + // Needs layout + testSkipIf(isJSDOM)('should call onFetchRows when sorting is applied', () => { const handleFetchRows = spy(); render(); @@ -80,16 +75,16 @@ describe(' - Lazy loader', () => { expect(handleFetchRows.callCount).to.equal(2); }); - it('should render skeleton cell if rowCount is bigger than the number of rows', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - - render(); + // Needs layout + testSkipIf(isJSDOM)( + 'should render skeleton cell if rowCount is bigger than the number of rows', + () => { + render(); - // The 4th row should be a skeleton one - expect(getRow(3).dataset.id).to.equal('auto-generated-skeleton-row-root-0'); - }); + // The 4th row should be a skeleton one + expect(getRow(3).dataset.id).to.equal('auto-generated-skeleton-row-root-0'); + }, + ); it('should update all rows accordingly when `apiRef.current.unstable_replaceRows` is called', () => { render(); diff --git a/packages/x-data-grid-pro/src/tests/rowPinning.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rowPinning.DataGridPro.test.tsx index dea9277b0605a..39a790cacec97 100644 --- a/packages/x-data-grid-pro/src/tests/rowPinning.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rowPinning.DataGridPro.test.tsx @@ -24,8 +24,7 @@ import { microtasks, } from 'test/utils/helperFn'; import { fireUserEvent } from 'test/utils/fireUserEvent'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Row pinning', () => { const { render } = createRenderer(); @@ -122,12 +121,8 @@ describe(' - Row pinning', () => { expect(screen.getByText(`Total Rows: ${rowCount - 2}`)).not.to.equal(null); }); - it('should keep rows pinned on rows scroll', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - + // Needs layouting + testSkipIf(isJSDOM)('should keep rows pinned on rows scroll', () => { render(); const virtualScroller = document.querySelector(`.${gridClasses.virtualScroller}`)!; @@ -417,12 +412,8 @@ describe(' - Row pinning', () => { expect(getActiveCellRowId()).to.equal('1'); }); - it('should work with pinned columns', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - + // Needs layouting + testSkipIf(isJSDOM)('should work with pinned columns', () => { function TestCase() { const data = getBasicGridData(5, 7); const [pinnedRow0, pinnedRow1, ...rows] = data.rows; @@ -486,12 +477,8 @@ describe(' - Row pinning', () => { }); }); - it('should work with variable row height', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - + // Needs layouting + testSkipIf(isJSDOM)('should work with variable row height', () => { let apiRef!: React.MutableRefObject; function TestCase() { apiRef = useGridApiRef(); @@ -519,12 +506,8 @@ describe(' - Row pinning', () => { expect(getRowById(1)?.clientHeight).to.equal(20); }); - it('should always update on `rowHeight` change', async function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - + // Needs layouting + testSkipIf(isJSDOM)('should always update on `rowHeight` change', async () => { const defaultRowHeight = 52; let apiRef!: React.MutableRefObject; @@ -556,12 +539,8 @@ describe(' - Row pinning', () => { expect(grid('pinnedRows--bottom')!.offsetHeight).to.equal(36); }); - it('should work with `autoHeight`', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - + // Needs layouting + testSkipIf(isJSDOM)('should work with `autoHeight`', () => { const columnHeaderHeight = 56; const rowHeight = 52; const rowCount = 10; @@ -580,12 +559,8 @@ describe(' - Row pinning', () => { expect(grid('main')!.clientHeight).to.equal(columnHeaderHeight + rowHeight * rowCount); }); - it('should work with `autoPageSize`', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - + // Needs layouting + testSkipIf(isJSDOM)('should work with `autoPageSize`', () => { render( - Row pinning', () => { expect(getRowById(1)!).to.have.class(className); }); - it('should support cell editing', async function test() { - if (isJSDOM) { - // flaky in JSDOM - this.skip(); - } + // flaky in JSDOM + testSkipIf(isJSDOM)('should support cell editing', async () => { const processRowUpdate = spy((row) => ({ ...row, currencyPair: 'USD-GBP' })); const columns: GridColDef[] = [{ field: 'id' }, { field: 'name', editable: true }]; render( @@ -801,11 +773,8 @@ describe(' - Row pinning', () => { expect(processRowUpdate.lastCall.args[0]).to.deep.equal({ id: 3, name: 'Marcus' }); }); - it('should support row editing', async function test() { - if (isJSDOM) { - // flaky in JSDOM - this.skip(); - } + // flaky in JSDOM + testSkipIf(isJSDOM)('should support row editing', async () => { const processRowUpdate = spy((row) => ({ ...row, currencyPair: 'USD-GBP' })); const columns: GridColDef[] = [{ field: 'id' }, { field: 'name', editable: true }]; render( diff --git a/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx index ba199d341d218..3bb0c605830fb 100644 --- a/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/rows.DataGridPro.test.tsx @@ -25,8 +25,7 @@ import { GridValidRowModel, } from '@mui/x-data-grid-pro'; import { useBasicDemoData, getBasicGridData } from '@mui/x-data-grid-generator'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; interface BaselineProps extends DataGridProProps { rows: GridValidRowModel[]; @@ -396,14 +395,8 @@ describe(' - Rows', () => { }); }); - describe('virtualization', () => { - before(function beforeHook() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - }); - + // Need layouting + describeSkipIf(isJSDOM)('virtualization', () => { let apiRef: React.MutableRefObject; function TestCaseVirtualization( props: Partial & { diff --git a/packages/x-data-grid-pro/src/tests/sorting.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/sorting.DataGridPro.test.tsx index f2778ad7cd932..f50357fb4dd7c 100644 --- a/packages/x-data-grid-pro/src/tests/sorting.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/sorting.DataGridPro.test.tsx @@ -11,8 +11,7 @@ import { createRenderer, fireEvent, act } from '@mui/internal-test-utils'; import { expect } from 'chai'; import { spy } from 'sinon'; import { getColumnValues, getCell, getColumnHeaderCell } from 'test/utils/helperFn'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Sorting', () => { const baselineProps: DataGridProProps = { @@ -170,47 +169,46 @@ describe(' - Sorting', () => { }); }); - it('should prune rendering on cells', function test() { - // The number of renders depends on the user-agent - if (!/HeadlessChrome/.test(window.navigator.userAgent) || !isJSDOM) { - this.skip(); - } - - let renderCellCount: number = 0; + // The number of renders depends on the user-agent + testSkipIf(!/HeadlessChrome/.test(window.navigator.userAgent) || !isJSDOM)( + 'should prune rendering on cells', + () => { + let renderCellCount: number = 0; + + function CounterRender(props: { value: string }) { + React.useEffect(() => { + if (props.value === 'Nike') { + renderCellCount += 1; + } + }); + return {props.value}; + } - function CounterRender(props: { value: string }) { - React.useEffect(() => { - if (props.value === 'Nike') { - renderCellCount += 1; - } - }); - return {props.value}; - } + const columns: GridColDef[] = [ + { + field: 'brand', + renderCell: (params) => , + }, + ]; - const columns: GridColDef[] = [ - { - field: 'brand', - renderCell: (params) => , - }, - ]; + function Test(props: Omit) { + return ( +
+ +
+ ); + } - function Test(props: Omit) { - return ( -
- -
- ); - } - - const { setProps } = render(); - expect(renderCellCount).to.equal(1); - const cell = getCell(1, 0); - cell.focus(); - fireEvent.click(cell); - expect(renderCellCount).to.equal(2); - setProps({ extra: true }); - expect(renderCellCount).to.equal(2); - }); + const { setProps } = render(); + expect(renderCellCount).to.equal(1); + const cell = getCell(1, 0); + cell.focus(); + fireEvent.click(cell); + expect(renderCellCount).to.equal(2); + setProps({ extra: true }); + expect(renderCellCount).to.equal(2); + }, + ); describe('control Sorting', () => { it('should update the sorting state when neither the model nor the onChange are set', () => { diff --git a/packages/x-data-grid/src/hooks/utils/useGridApiEventHandler.test.tsx b/packages/x-data-grid/src/hooks/utils/useGridApiEventHandler.test.tsx index 35df8e23d954f..8b20b171e5425 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridApiEventHandler.test.tsx +++ b/packages/x-data-grid/src/hooks/utils/useGridApiEventHandler.test.tsx @@ -3,6 +3,7 @@ import { spy } from 'sinon'; import { expect } from 'chai'; import { createRenderer, reactMajor } from '@mui/internal-test-utils'; import { sleep } from 'test/utils/helperFn'; +import { isJSDOM, testSkipIf } from 'test/utils/skipIf'; import { createUseGridApiEventHandler } from './useGridApiEventHandler'; import { FinalizationRegistryBasedCleanupTracking } from '../../utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking'; import { TimerBasedCleanupTracking } from '../../utils/cleanupTracking/TimerBasedCleanupTracking'; @@ -13,16 +14,10 @@ describe('useGridApiEventHandler', () => { const { render } = createRenderer(); describe('FinalizationRegistry-based implementation', () => { - it('should unsubscribe event listeners registered by uncommitted components', async function test() { - if ( - !/jsdom/.test(window.navigator.userAgent) || - typeof FinalizationRegistry === 'undefined' || - typeof global.gc === 'undefined' - ) { - // Needs ability to trigger the garbage collector and support for FinalizationRegistry (added in node 14) - this.skip(); - } - + // Needs ability to trigger the garbage collector and support for FinalizationRegistry (added in node 14) + testSkipIf( + !isJSDOM || typeof FinalizationRegistry === 'undefined' || typeof global.gc === 'undefined', + )('should unsubscribe event listeners registered by uncommitted components', async () => { const useGridApiEventHandler = createUseGridApiEventHandler({ registry: new FinalizationRegistryBasedCleanupTracking(), }); @@ -48,7 +43,7 @@ describe('useGridApiEventHandler', () => { expect(apiRef.current.subscribeEvent.callCount).to.equal(expectedCallCount); unmount(); - global.gc(); // Triggers garbage collector + global.gc?.(); // Triggers garbage collector await sleep(50); // Ensure that both event listeners were unsubscribed diff --git a/packages/x-data-grid/src/tests/cells.DataGrid.test.tsx b/packages/x-data-grid/src/tests/cells.DataGrid.test.tsx index 61c3a5b0f72da..4dfad663c55c9 100644 --- a/packages/x-data-grid/src/tests/cells.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/cells.DataGrid.test.tsx @@ -6,8 +6,7 @@ import { DataGrid, GridValueFormatter } from '@mui/x-data-grid'; import { getCell } from 'test/utils/helperFn'; import { fireUserEvent } from 'test/utils/fireUserEvent'; import { getBasicGridData } from '@mui/x-data-grid-generator'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { describeSkipIf, testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Cells', () => { const { render } = createRenderer({ clock: 'fake' }); @@ -53,7 +52,8 @@ describe(' - Cells', () => { }); }); - describe('prop: showCellVerticalBorder', () => { + // Doesn't work with mocked window.getComputedStyle + describeSkipIf(isJSDOM)('prop: showCellVerticalBorder', () => { function expectRightBorder(element: HTMLElement) { const computedStyle = window.getComputedStyle(element); const color = computedStyle.getPropertyValue('border-right-color'); @@ -64,12 +64,7 @@ describe(' - Cells', () => { expect(color).not.to.equal('rgba(0, 0, 0, 0)'); } - it('should add right border to cells', function test() { - if (isJSDOM) { - // Doesn't work with mocked window.getComputedStyle - this.skip(); - } - + it('should add right border to cells', () => { render(
- Cells', () => { }); // See https://github.com/mui/mui-x/issues/4122 - it('should add right border to cells in the last row', function test() { - if (isJSDOM) { - // Doesn't work with mocked window.getComputedStyle - this.skip(); - } - + it('should add right border to cells in the last row', () => { render(
- Cells', () => { }).toWarnDev(['MUI X: The cell with id=1 and field=brand received focus.']); }); - it('should keep the focused cell/row rendered in the DOM if it scrolls outside the viewport', function test() { - if (isJSDOM) { - this.skip(); - } - const rowHeight = 50; - const defaultData = getBasicGridData(20, 20); + testSkipIf(isJSDOM)( + 'should keep the focused cell/row rendered in the DOM if it scrolls outside the viewport', + () => { + const rowHeight = 50; + const defaultData = getBasicGridData(20, 20); - render( -
- -
, - ); + render( +
+ +
, + ); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; + const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - const cell = getCell(1, 3); - fireUserEvent.mousePress(cell); + const cell = getCell(1, 3); + fireUserEvent.mousePress(cell); - const activeElementTextContent = document.activeElement?.textContent; - const columnWidth = document.activeElement!.clientWidth; + const activeElementTextContent = document.activeElement?.textContent; + const columnWidth = document.activeElement!.clientWidth; - const scrollTop = 10 * rowHeight; - fireEvent.scroll(virtualScroller, { target: { scrollTop } }); - expect(document.activeElement?.textContent).to.equal(activeElementTextContent); + const scrollTop = 10 * rowHeight; + fireEvent.scroll(virtualScroller, { target: { scrollTop } }); + expect(document.activeElement?.textContent).to.equal(activeElementTextContent); - const scrollLeft = 10 * columnWidth; - fireEvent.scroll(virtualScroller, { target: { scrollLeft } }); + const scrollLeft = 10 * columnWidth; + fireEvent.scroll(virtualScroller, { target: { scrollLeft } }); - expect(document.activeElement?.textContent).to.equal(activeElementTextContent); - }); + expect(document.activeElement?.textContent).to.equal(activeElementTextContent); + }, + ); // See https://github.com/mui/mui-x/issues/6378 - it('should not cause scroll jump when focused cell mounts in the render zone', async function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - - const rowHeight = 50; - const columns = [{ field: 'id' }]; - const rows = []; - for (let i = 0; i < 20; i += 1) { - rows.push({ id: i }); - } + // Needs layout + testSkipIf(isJSDOM)( + 'should not cause scroll jump when focused cell mounts in the render zone', + async () => { + const rowHeight = 50; + const columns = [{ field: 'id' }]; + const rows = []; + for (let i = 0; i < 20; i += 1) { + rows.push({ id: i }); + } - render( -
- -
, - ); + render( +
+ +
, + ); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; + const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - const thirdRowCell = getCell(2, 0); - fireUserEvent.mousePress(thirdRowCell); + const thirdRowCell = getCell(2, 0); + fireUserEvent.mousePress(thirdRowCell); - let scrollTop = 6 * rowHeight; - virtualScroller.scrollTop = scrollTop; - virtualScroller.dispatchEvent(new Event('scroll')); - expect(virtualScroller.scrollTop).to.equal(scrollTop); + let scrollTop = 6 * rowHeight; + virtualScroller.scrollTop = scrollTop; + virtualScroller.dispatchEvent(new Event('scroll')); + expect(virtualScroller.scrollTop).to.equal(scrollTop); - scrollTop = 2 * rowHeight; - virtualScroller.scrollTop = scrollTop; - virtualScroller.dispatchEvent(new Event('scroll')); - expect(virtualScroller.scrollTop).to.equal(scrollTop); - }); + scrollTop = 2 * rowHeight; + virtualScroller.scrollTop = scrollTop; + virtualScroller.dispatchEvent(new Event('scroll')); + expect(virtualScroller.scrollTop).to.equal(scrollTop); + }, + ); }); diff --git a/packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx b/packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx index 198ba40ba9fc3..8d4c24bef6263 100644 --- a/packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/columnSpanning.DataGrid.test.tsx @@ -4,8 +4,7 @@ import { expect } from 'chai'; import { DataGrid, gridClasses, GridColDef } from '@mui/x-data-grid'; import { getCell, getActiveCell, getColumnHeaderCell } from 'test/utils/helperFn'; import { fireUserEvent } from 'test/utils/fireUserEvent'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Column spanning', () => { const { render, clock } = createRenderer({ clock: 'fake' }); @@ -268,12 +267,8 @@ describe(' - Column spanning', () => { expect(getActiveCell()).to.equal('0-0'); }); - it('should work with row virtualization', function test() { - if (isJSDOM) { - // needs virtualization - this.skip(); - } - + // needs virtualization + testSkipIf(isJSDOM)('should work with row virtualization', () => { const rows = [ { id: 0, @@ -333,11 +328,8 @@ describe(' - Column spanning', () => { expect(activeCell).to.equal('3-0'); }); - it('should work with column virtualization', function test() { - if (isJSDOM) { - this.skip(); // needs layout - } - + // needs layout + testSkipIf(isJSDOM)('should work with column virtualization', () => { render(
- Column spanning', () => { expect(getActiveCell()).to.equal('1-2'); }); - it('should scroll the whole cell into view when `colSpan` > 1', function test() { - if (isJSDOM) { - this.skip(); // needs layout - } - + // needs layout + testSkipIf(isJSDOM)('should scroll the whole cell into view when `colSpan` > 1', () => { render(
- Column spanning', () => { checkRows(2, ['Adidas', 'Puma']); }); - it('should work with column virtualization', function test() { - if (isJSDOM) { - // Need layouting for column virtualization - this.skip(); - } - + // Need layout for column virtualization + testSkipIf(isJSDOM)('should work with column virtualization', () => { render(
- Column spanning', () => { ); }); - it('should work with both column and row virtualization', function test() { - if (isJSDOM) { - // Need layouting for column virtualization - this.skip(); - } - + // Need layout for column virtualization + testSkipIf(isJSDOM)('should work with both column and row virtualization', () => { const rowHeight = 50; render( @@ -836,12 +817,8 @@ describe(' - Column spanning', () => { ); }); - it('should work with pagination and column virtualization', function test() { - if (isJSDOM) { - // Need layouting for column virtualization - this.skip(); - } - + // Need layout for column virtualization + testSkipIf(isJSDOM)('should work with pagination and column virtualization', () => { const rowHeight = 50; function TestCase() { diff --git a/packages/x-data-grid/src/tests/columns.DataGrid.test.tsx b/packages/x-data-grid/src/tests/columns.DataGrid.test.tsx index 652d3e9bf1539..4bd87f5160554 100644 --- a/packages/x-data-grid/src/tests/columns.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/columns.DataGrid.test.tsx @@ -3,8 +3,7 @@ import { expect } from 'chai'; import { createRenderer, fireEvent, screen } from '@mui/internal-test-utils'; import { DataGrid, DataGridProps, GridRowsProp, GridColDef, gridClasses } from '@mui/x-data-grid'; import { getCell, getColumnHeaderCell, getColumnHeadersTextContent } from 'test/utils/helperFn'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; const rows: GridRowsProp = [{ id: 1, idBis: 1 }]; @@ -123,11 +122,8 @@ describe(' - Columns', () => { }); // https://github.com/mui/mui-x/issues/13719 - it('should not crash when updating columns immediately after scrolling', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } - + // Needs layout + testSkipIf(isJSDOM)('should not crash when updating columns immediately after scrolling', () => { const data = [ { id: 1, value: 'A' }, { id: 2, value: 'B' }, diff --git a/packages/x-data-grid/src/tests/density.DataGrid.test.tsx b/packages/x-data-grid/src/tests/density.DataGrid.test.tsx index aaad84e1c4fe2..4901dc3491c66 100644 --- a/packages/x-data-grid/src/tests/density.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/density.DataGrid.test.tsx @@ -4,14 +4,14 @@ import { createRenderer, fireEvent, screen } from '@mui/internal-test-utils'; import { grid } from 'test/utils/helperFn'; import { expect } from 'chai'; import { DataGrid, DataGridProps, GridToolbar, gridClasses } from '@mui/x-data-grid'; +import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; import { COMFORTABLE_DENSITY_FACTOR, COMPACT_DENSITY_FACTOR, } from '../hooks/features/density/densitySelector'; -const isJSDOM = /jsdom/.test(window.navigator.userAgent); - -describe(' - Density', () => { +// JSDOM seem to not support CSS variables properly and `height: var(--height)` ends up being `height: ''` +describeSkipIf(isJSDOM)(' - Density', () => { const { render, clock } = createRenderer({ clock: 'fake' }); const baselineProps = { @@ -50,13 +50,6 @@ describe(' - Density', () => { ); } - before(function beforeHook() { - if (isJSDOM) { - // JSDOM seem to not support CSS variables properly and `height: var(--height)` ends up being `height: ''` - this.skip(); - } - }); - describe('prop: `initialState.density`', () => { it('should set the density to the value of initialState.density', () => { const rowHeight = 30; diff --git a/packages/x-data-grid/src/tests/export.DataGrid.test.tsx b/packages/x-data-grid/src/tests/export.DataGrid.test.tsx index fd39af7dd1ad2..494c587783369 100644 --- a/packages/x-data-grid/src/tests/export.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/export.DataGrid.test.tsx @@ -4,8 +4,10 @@ import { spy, SinonSpy } from 'sinon'; import { DataGrid, DataGridProps, GridToolbar, GridToolbarExport } from '@mui/x-data-grid'; import { useBasicDemoData } from '@mui/x-data-grid-generator'; import { createRenderer, screen, fireEvent } from '@mui/internal-test-utils'; +import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; -describe(' - Export', () => { +// We need `createObjectURL` to test the downloaded value +describeSkipIf(isJSDOM)(' - Export', () => { const { render, clock } = createRenderer({ clock: 'fake' }); function TestCase(props: Omit) { @@ -18,20 +20,14 @@ describe(' - Export', () => { ); } - // We need `createObjectURL` to test the downloaded value - before(function beforeHook() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - }); - let spyCreateObjectURL: SinonSpy; + // eslint-disable-next-line mocha/no-top-level-hooks beforeEach(() => { spyCreateObjectURL = spy(global.URL, 'createObjectURL'); }); + // eslint-disable-next-line mocha/no-top-level-hooks afterEach(() => { spyCreateObjectURL.restore(); }); diff --git a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx index 987b7c3c7a7b3..826c9a19f04bc 100644 --- a/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/keyboard.DataGrid.test.tsx @@ -21,8 +21,7 @@ import { } from '@mui/x-data-grid'; import { useBasicDemoData, getBasicGridData } from '@mui/x-data-grid-generator'; import RestoreIcon from '@mui/icons-material/Restore'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; const PAGE_SIZE = 10; const ROW_HEIGHT = 52; @@ -158,92 +157,87 @@ describe(' - Keyboard', () => { expect(getActiveCell()).to.equal('1-0'); // Already on the 1st cell }); - it('should move down by the amount of rows visible on screen when pressing "PageDown"', function test() { - if (isJSDOM) { - // This test is not relevant if we can't choose the actual height - this.skip(); - } - - render(); - const cell = getCell(1, 1); - fireUserEvent.mousePress(cell); - expect(getActiveCell()).to.equal('1-1'); - fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); - expect(getActiveCell()).to.equal(`6-1`); - fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); - expect(getActiveCell()).to.equal(`9-1`); - }); - - it('should move down by the amount of rows visible on screen when pressing Space key', function test() { - if (isJSDOM) { - // This test is not relevant if we can't choose the actual height - this.skip(); - } - - render(); - const cell = getCell(1, 1); - fireUserEvent.mousePress(cell); - expect(getActiveCell()).to.equal('1-1'); - fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); - expect(getActiveCell()).to.equal(`6-1`); - fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); - expect(getActiveCell()).to.equal(`9-1`); - }); - - it('should move up by the amount of rows visible on screen when pressing "PageUp"', function test() { - if (isJSDOM) { - // This test is not relevant if we can't choose the actual height - this.skip(); - } - - render(); - const cell = getCell(8, 1); - fireUserEvent.mousePress(cell); - expect(getActiveCell()).to.equal('8-1'); - fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); - expect(getActiveCell()).to.equal(`3-1`); - }); - - it('should move to the first row before moving to column header when pressing "PageUp"', function test() { - if (isJSDOM) { - // This test is not relevant if we can't choose the actual height - this.skip(); - } - - render(); - const cell = getCell(3, 1); - fireUserEvent.mousePress(cell); - expect(getActiveCell()).to.equal('3-1'); + // This test is not relevant if we can't choose the actual height + testSkipIf(isJSDOM)( + 'should move down by the amount of rows visible on screen when pressing "PageDown"', + () => { + render(); + const cell = getCell(1, 1); + fireUserEvent.mousePress(cell); + expect(getActiveCell()).to.equal('1-1'); + fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); + expect(getActiveCell()).to.equal(`6-1`); + fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); + expect(getActiveCell()).to.equal(`9-1`); + }, + ); - fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); - expect(getActiveCell()).to.equal(`0-1`, 'should focus first row'); + // This test is not relevant if we can't choose the actual height + testSkipIf(isJSDOM)( + 'should move down by the amount of rows visible on screen when pressing Space key', + () => { + render(); + const cell = getCell(1, 1); + fireUserEvent.mousePress(cell); + expect(getActiveCell()).to.equal('1-1'); + fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); + expect(getActiveCell()).to.equal(`6-1`); + fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); + expect(getActiveCell()).to.equal(`9-1`); + }, + ); - fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); - expect(getActiveCell()).to.equal(null); - expect(getActiveColumnHeader()).to.equal(`1`); - }); + // This test is not relevant if we can't choose the actual height + testSkipIf(isJSDOM)( + 'should move up by the amount of rows visible on screen when pressing "PageUp"', + () => { + render(); + const cell = getCell(8, 1); + fireUserEvent.mousePress(cell); + expect(getActiveCell()).to.equal('8-1'); + fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); + expect(getActiveCell()).to.equal(`3-1`); + }, + ); - it('should move to the first row before moving to column header when pressing "PageUp" on page > 0', function test() { - if (isJSDOM) { - // This test is not relevant if we can't choose the actual height - this.skip(); - } + // This test is not relevant if we can't choose the actual height + testSkipIf(isJSDOM)( + 'should move to the first row before moving to column header when pressing "PageUp"', + () => { + render(); + const cell = getCell(3, 1); + fireUserEvent.mousePress(cell); + expect(getActiveCell()).to.equal('3-1'); + + fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); + expect(getActiveCell()).to.equal(`0-1`, 'should focus first row'); + + fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); + expect(getActiveCell()).to.equal(null); + expect(getActiveColumnHeader()).to.equal(`1`); + }, + ); - render(); + // This test is not relevant if we can't choose the actual height + testSkipIf(isJSDOM)( + 'should move to the first row before moving to column header when pressing "PageUp" on page > 0', + () => { + render(); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); + fireEvent.click(screen.getByRole('button', { name: /next page/i })); - const cell = getCell(13, 1); - fireUserEvent.mousePress(cell); - expect(getActiveCell()).to.equal('13-1'); + const cell = getCell(13, 1); + fireUserEvent.mousePress(cell); + expect(getActiveCell()).to.equal('13-1'); - fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); - expect(getActiveCell()).to.equal(`10-1`, 'should focus first row'); + fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); + expect(getActiveCell()).to.equal(`10-1`, 'should focus first row'); - fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); - expect(getActiveCell()).to.equal(null); - expect(getActiveColumnHeader()).to.equal(`1`); - }); + fireEvent.keyDown(document.activeElement!, { key: 'PageUp' }); + expect(getActiveCell()).to.equal(null); + expect(getActiveColumnHeader()).to.equal(`1`); + }, + ); it('should navigate to the 1st cell of the current row when pressing "Home"', () => { render(); @@ -309,39 +303,43 @@ describe(' - Keyboard', () => { }); describe('column header navigation', () => { - it('should scroll horizontally when navigating between column headers with arrows', function test() { - if (isJSDOM) { - // Need layouting for column virtualization - this.skip(); - } - render( -
- -
, - ); - getColumnHeaderCell(0).focus(); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - expect(virtualScroller.scrollLeft).to.equal(0); - fireEvent.keyDown(document.activeElement!, { key: 'ArrowRight' }); - expect(virtualScroller.scrollLeft).not.to.equal(0); - }); + // Need layout for column virtualization + testSkipIf(isJSDOM)( + 'should scroll horizontally when navigating between column headers with arrows', + () => { + render( +
+ +
, + ); + getColumnHeaderCell(0).focus(); + const virtualScroller = document.querySelector( + '.MuiDataGrid-virtualScroller', + )!; + expect(virtualScroller.scrollLeft).to.equal(0); + fireEvent.keyDown(document.activeElement!, { key: 'ArrowRight' }); + expect(virtualScroller.scrollLeft).not.to.equal(0); + }, + ); - it('should scroll horizontally when navigating between column headers with arrows even if rows are empty', function test() { - if (isJSDOM) { - // Need layouting for column virtualization - this.skip(); - } - render( -
- -
, - ); - getColumnHeaderCell(0).focus(); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - expect(virtualScroller.scrollLeft).to.equal(0); - fireEvent.keyDown(document.activeElement!, { key: 'ArrowRight' }); - expect(virtualScroller.scrollLeft).not.to.equal(0); - }); + // Need layout for column virtualization + testSkipIf(isJSDOM)( + 'should scroll horizontally when navigating between column headers with arrows even if rows are empty', + () => { + render( +
+ +
, + ); + getColumnHeaderCell(0).focus(); + const virtualScroller = document.querySelector( + '.MuiDataGrid-virtualScroller', + )!; + expect(virtualScroller.scrollLeft).to.equal(0); + fireEvent.keyDown(document.activeElement!, { key: 'ArrowRight' }); + expect(virtualScroller.scrollLeft).not.to.equal(0); + }, + ); it('should move to the first row when pressing "ArrowDown" on a column header on the 1st page', () => { render(); @@ -379,25 +377,20 @@ describe(' - Keyboard', () => { expect(getActiveColumnHeader()).to.equal('0'); }); - it('should move down by the amount of rows visible on screen when pressing "PageDown"', function test() { - if (isJSDOM) { - // This test is not relevant if we can't choose the actual height - this.skip(); - } - - render(); - getColumnHeaderCell(1).focus(); - expect(getActiveColumnHeader()).to.equal('1'); - fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); - expect(getActiveCell()).to.equal(`5-1`); - }); - - it('should move focus when the focus is on a column header button', function test() { - if (isJSDOM) { - // This test is not relevant if we can't choose the actual height - this.skip(); - } + // This test is not relevant if we can't choose the actual height + testSkipIf(isJSDOM)( + 'should move down by the amount of rows visible on screen when pressing "PageDown"', + () => { + render(); + getColumnHeaderCell(1).focus(); + expect(getActiveColumnHeader()).to.equal('1'); + fireEvent.keyDown(document.activeElement!, { key: 'PageDown' }); + expect(getActiveCell()).to.equal(`5-1`); + }, + ); + // This test is not relevant if we can't choose the actual height + testSkipIf(isJSDOM)('should move focus when the focus is on a column header button', () => { render(); // get the sort button in column header 1 @@ -499,48 +492,52 @@ describe(' - Keyboard', () => { ); } - it('should scroll horizontally when navigating between column group headers with arrows', async function test() { - if (isJSDOM) { - // Need layouting for column virtualization - this.skip(); - } - const { user } = render( -
- -
, - ); - // Tab to the first column header - await user.keyboard('{Tab}'); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - expect(virtualScroller.scrollLeft).to.equal(0); - // We then need to move up to the group header, then right to the first named column - await user.keyboard('{ArrowUp}{ArrowUp}{ArrowRight}'); - expect(virtualScroller.scrollLeft).not.to.equal(0); - }); + // Need layouting for column virtualization + testSkipIf(isJSDOM)( + 'should scroll horizontally when navigating between column group headers with arrows', + async () => { + const { user } = render( +
+ +
, + ); + // Tab to the first column header + await user.keyboard('{Tab}'); + const virtualScroller = document.querySelector( + '.MuiDataGrid-virtualScroller', + )!; + expect(virtualScroller.scrollLeft).to.equal(0); + // We then need to move up to the group header, then right to the first named column + await user.keyboard('{ArrowUp}{ArrowUp}{ArrowRight}'); + expect(virtualScroller.scrollLeft).not.to.equal(0); + }, + ); - it('should scroll horizontally when navigating between column headers with arrows even if rows are empty', async function test() { - if (isJSDOM) { - // Need layouting for column virtualization - this.skip(); - } - const { user } = render( -
- -
, - ); - // Tab to the first column header - await user.keyboard('{Tab}'); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - expect(virtualScroller.scrollLeft).to.equal(0); - // We then need to move up to the group header, then right to the first named column - await user.keyboard('{ArrowUp}{ArrowUp}{ArrowRight}'); - expect(virtualScroller.scrollLeft).not.to.equal(0); - expect(document.activeElement!).toHaveAccessibleName('prices'); - }); + // Need layouting for column virtualization + testSkipIf(isJSDOM)( + 'should scroll horizontally when navigating between column headers with arrows even if rows are empty', + async () => { + const { user } = render( +
+ +
, + ); + // Tab to the first column header + await user.keyboard('{Tab}'); + const virtualScroller = document.querySelector( + '.MuiDataGrid-virtualScroller', + )!; + expect(virtualScroller.scrollLeft).to.equal(0); + // We then need to move up to the group header, then right to the first named column + await user.keyboard('{ArrowUp}{ArrowUp}{ArrowRight}'); + expect(virtualScroller.scrollLeft).not.to.equal(0); + expect(document.activeElement!).toHaveAccessibleName('prices'); + }, + ); it('should move to the group header below when pressing "ArrowDown" on a column group header', async () => { const { user } = render(); diff --git a/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx b/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx index c07c07fd3791f..41b19f9ba4d46 100644 --- a/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/layout.DataGrid.test.tsx @@ -30,6 +30,7 @@ import { getRow, sleep, } from 'test/utils/helperFn'; +import { describeSkipIf, testSkipIf, isJSDOM, isOSX } from 'test/utils/skipIf'; const getVariable = (name: string) => $('.MuiDataGrid-root')!.style.getPropertyValue(name); @@ -79,14 +80,8 @@ describe(' - Layout & warnings', () => { }); }); - describe('Layout', () => { - before(function beforeHook() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - }); - + // Need layout to be able to measure the columns + describeSkipIf(isJSDOM)('Layout', () => { it('should resize the width of the columns', async () => { interface TestCaseProps { width?: number; @@ -192,7 +187,7 @@ describe(' - Layout & warnings', () => { }); }); - describe('warnings', () => { + describe('layout warnings', () => { clock.withFakeTimers(); it('should error if the container has no intrinsic height', () => { @@ -699,38 +694,38 @@ describe(' - Layout & warnings', () => { ); }); - it('should include the scrollbar in the intrinsic height when there are more columns to show', function test() { - // On MacOS the scrollbar has zero width - if (/macintosh/i.test(window.navigator.userAgent)) { - this.skip(); - } - const columnHeaderHeight = 40; - const rowHeight = 30; - - let apiRef!: React.MutableRefObject; - function Test() { - apiRef = useGridApiRef(); - return ( -
- -
+ // On MacOS the scrollbar has zero width + testSkipIf(isOSX)( + 'should include the scrollbar in the intrinsic height when there are more columns to show', + () => { + const columnHeaderHeight = 40; + const rowHeight = 30; + + let apiRef!: React.MutableRefObject; + function Test() { + apiRef = useGridApiRef(); + return ( +
+ +
+ ); + } + render(); + + const scrollbarSize = apiRef.current.state.dimensions.scrollbarSize; + expect(scrollbarSize).not.to.equal(0); + expect(grid('main')!.clientHeight).to.equal( + scrollbarSize + columnHeaderHeight + rowHeight * baselineProps.rows.length, ); - } - render(); - - const scrollbarSize = apiRef.current.state.dimensions.scrollbarSize; - expect(scrollbarSize).not.to.equal(0); - expect(grid('main')!.clientHeight).to.equal( - scrollbarSize + columnHeaderHeight + rowHeight * baselineProps.rows.length, - ); - }); + }, + ); it('should give some space to the noRows overlay', () => { const rowHeight = 30; @@ -795,18 +790,18 @@ describe(' - Layout & warnings', () => { expect(gridVar('--DataGrid-hasScrollX')).to.equal('0'); }); - it('should have a horizontal scrollbar when there are more columns to show and no rows', function test() { - // On MacOS the scrollbar has zero width - if (/macintosh/i.test(window.navigator.userAgent)) { - this.skip(); - } - render( -
- -
, - ); - expect(gridVar('--DataGrid-hasScrollX')).to.equal('1'); - }); + // On MacOS the scrollbar has zero width + testSkipIf(isOSX)( + 'should have a horizontal scrollbar when there are more columns to show and no rows', + () => { + render( +
+ +
, + ); + expect(gridVar('--DataGrid-hasScrollX')).to.equal('1'); + }, + ); it('should not place the overlay on top of the horizontal scrollbar when rows=[]', () => { const columnHeaderHeight = 40; @@ -943,14 +938,9 @@ describe(' - Layout & warnings', () => { }).toErrorDev('MUI X: `` is not a valid prop.'); }); - it('should throw if the rows has no id', function test() { - // TODO is this fixed? - if (!/jsdom/.test(window.navigator.userAgent)) { - // can't catch render errors in the browser for unknown reason - // tried try-catch + error boundary + window onError preventDefault - this.skip(); - } - + // can't catch render errors in the browser for unknown reason + // tried try-catch + error boundary + window onError preventDefault + testSkipIf(!isJSDOM)('should throw if the rows has no id', () => { const rows = [ { brand: 'Nike', @@ -1029,10 +1019,7 @@ describe(' - Layout & warnings', () => { describe('non-strict mode', () => { const { render: innerRender } = createRenderer({ strict: false }); - it('should render in JSDOM', function test() { - if (!/jsdom/.test(window.navigator.userAgent)) { - this.skip(); // Only run in JSDOM - } + testSkipIf(!isJSDOM)('should render in JSDOM', () => { innerRender(
@@ -1043,11 +1030,8 @@ describe(' - Layout & warnings', () => { }); }); - it('should allow style customization using the theme', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); // Doesn't work with mocked window.getComputedStyle - } - + // Doesn't work with mocked window.getComputedStyle + testSkipIf(isJSDOM)('should allow style customization using the theme', () => { const theme = createTheme({ components: { MuiDataGrid: { @@ -1085,11 +1069,8 @@ describe(' - Layout & warnings', () => { expect(window.getComputedStyle(getCell(0, 0)).backgroundColor).to.equal('rgb(0, 128, 0)'); }); - it('should support the sx prop', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); // Doesn't work with mocked window.getComputedStyle - } - + // Doesn't work with mocked window.getComputedStyle + testSkipIf(isJSDOM)('should support the sx prop', () => { const theme = createTheme({ palette: { primary: { @@ -1167,13 +1148,8 @@ describe(' - Layout & warnings', () => { expect(NoRowsOverlay.callCount).not.to.equal(0); }); - describe('should not overflow parent', () => { - before(function beforeHook() { - if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); // Doesn't work with mocked window.getComputedStyle - } - }); - + // Doesn't work with mocked window.getComputedStyle + describeSkipIf(isJSDOM)('should not overflow parent', () => { const rows = [{ id: 1, username: '@MUI', age: 20 }]; const columns = [ { field: 'id', width: 300 }, @@ -1210,132 +1186,126 @@ describe(' - Layout & warnings', () => { }); // See https://github.com/mui/mui-x/issues/8737 - it('should not add horizontal scrollbar when .MuiDataGrid-main has border', async function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - - render( -
- -
, - ); + // Need layout + testSkipIf(isJSDOM)( + 'should not add horizontal scrollbar when .MuiDataGrid-main has border', + async () => { + render( +
+ +
, + ); - const virtualScroller = $('.MuiDataGrid-virtualScroller')!; - const initialVirtualScrollerWidth = virtualScroller.clientWidth; + const virtualScroller = $('.MuiDataGrid-virtualScroller')!; + const initialVirtualScrollerWidth = virtualScroller.clientWidth; - // It should not have a horizontal scrollbar - expect(getVariable('--DataGrid-hasScrollX')).to.equal('0'); + // It should not have a horizontal scrollbar + expect(getVariable('--DataGrid-hasScrollX')).to.equal('0'); - await sleep(200); - // The width should not increase infinitely - expect(virtualScroller.clientWidth).to.equal(initialVirtualScrollerWidth); - }); + await sleep(200); + // The width should not increase infinitely + expect(virtualScroller.clientWidth).to.equal(initialVirtualScrollerWidth); + }, + ); // See https://github.com/mui/mui-x/issues/8689#issuecomment-1582616570 - it('should not add scrollbars when the parent container has fractional size', async function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - - render( -
- -
, - ); + // Need layout + testSkipIf(isJSDOM)( + 'should not add scrollbars when the parent container has fractional size', + async () => { + render( +
+ +
, + ); - // It should not have a horizontal scrollbar - expect(getVariable('--DataGrid-hasScrollX')).to.equal('0'); - // It should not have a vertical scrollbar - expect(getVariable('--DataGrid-hasScrollY')).to.equal('0'); - }); + // It should not have a horizontal scrollbar + expect(getVariable('--DataGrid-hasScrollX')).to.equal('0'); + // It should not have a vertical scrollbar + expect(getVariable('--DataGrid-hasScrollY')).to.equal('0'); + }, + ); // See https://github.com/mui/mui-x/issues/9510 - it('should not exceed maximum call stack size when the parent container has fractional width', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - - render( -
- -
, - ); - }); + // Need layout + testSkipIf(isJSDOM)( + 'should not exceed maximum call stack size when the parent container has fractional width', + () => { + render( +
+ +
, + ); + }, + ); // See https://github.com/mui/mui-x/issues/9550 - it('should not exceed maximum call stack size with duplicated flex fields', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } + // Need layout + testSkipIf(isJSDOM)( + 'should not exceed maximum call stack size with duplicated flex fields', + () => { + expect(() => { + render( +
+ +
, + ); + }).toErrorDev([ + 'Encountered two children with the same key, `id`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.', + 'Encountered two children with the same key, `id`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.', + ]); + }, + ); - expect(() => { + // See https://github.com/mui/mui-x/issues/9550#issuecomment-1619020477 + // Need layout + testSkipIf(isJSDOM)( + 'should not exceed maximum call stack size caused by floating point precision error', + () => { render( -
+
, ); - }).toErrorDev([ - 'Encountered two children with the same key, `id`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.', - 'Encountered two children with the same key, `id`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.', - ]); - }); - - // See https://github.com/mui/mui-x/issues/9550#issuecomment-1619020477 - it('should not exceed maximum call stack size caused by floating point precision error', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - - render( -
- -
, - ); - }); + }, + ); // See https://github.com/mui/mui-x/issues/15721 - it('should not exceed maximum call stack size caused by subpixel rendering', function test() { - if (/jsdom/.test(window.navigator.userAgent)) { - // Need layouting - this.skip(); - } - - render( -
- -
, - ); - }); + // Need layout + testSkipIf(isJSDOM)( + 'should not exceed maximum call stack size caused by subpixel rendering', + () => { + render( +
+ +
, + ); + }, + ); }); diff --git a/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx b/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx index 3bcd4f342042b..c75469ee6f451 100644 --- a/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/pagination.DataGrid.test.tsx @@ -14,8 +14,7 @@ import { import { useBasicDemoData } from '@mui/x-data-grid-generator'; import { getCell, getColumnValues, getRows } from 'test/utils/helperFn'; import { fireUserEvent } from 'test/utils/fireUserEvent'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { isJSDOM, describeSkipIf } from 'test/utils/skipIf'; describe(' - Pagination', () => { const { render } = createRenderer(); @@ -32,14 +31,8 @@ describe(' - Pagination', () => { ); } - describe('prop: paginationModel and onPaginationModelChange', () => { - before(function beforeHook() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - }); - + // Need layouting + describeSkipIf(isJSDOM)('prop: paginationModel and onPaginationModelChange', () => { it('should display the rows of page given in props', () => { render(); expect(getColumnValues(0)).to.deep.equal(['1']); @@ -392,14 +385,8 @@ describe(' - Pagination', () => { }); }); - describe('prop: autoPageSize', () => { - before(function beforeHook() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - }); - + // Need layout + describeSkipIf(isJSDOM)('prop: autoPageSize', () => { function TestCaseAutoPageSize( props: Omit & { height: number; nbRows: number }, ) { @@ -639,14 +626,8 @@ describe(' - Pagination', () => { expect(getCell(1, 0)).to.have.attr('tabindex', '0'); }); - describe('prop: initialState.pagination', () => { - before(function beforeHook() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - }); - + // Need layout + describeSkipIf(isJSDOM)('prop: initialState.pagination', () => { it('should allow to initialize the paginationModel', () => { render( - Quick filter', () => { const { render, clock } = createRenderer(); @@ -645,10 +644,7 @@ describe(' - Quick filter', () => { }); // https://github.com/mui/mui-x/issues/6783 - it('should not override user input when typing', async function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should not override user input when typing', async () => { // Warning: this test doesn't fail consistently as it is timing-sensitive. const debounceMs = 50; diff --git a/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx b/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx index a7350f157e9ca..6e172932a004d 100644 --- a/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/rowSelection.DataGrid.test.tsx @@ -23,8 +23,7 @@ import { grid, } from 'test/utils/helperFn'; import { getBasicGridData } from '@mui/x-data-grid-generator'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; function getSelectedRowIds() { const hasCheckbox = !!document.querySelector('input[type="checkbox"]'); @@ -397,10 +396,7 @@ describe(' - Row selection', () => { expect(input2.checked).to.equal(true); }); - it('should remove the selection from rows that are filtered out', async function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should remove the selection from rows that are filtered out', async () => { render( - Row selection', () => { expect(getSelectedRowIds()).to.deep.equal([1, 2]); }); - it('should not jump during scroll while the focus is on the checkbox', async function test() { - if (isJSDOM) { - this.skip(); // HTMLElement.focus() only scrolls to the element on a real browser - } - const data = getBasicGridData(20, 1); - const { user } = render( - , - ); - const checkboxes = screen.queryAllByRole('checkbox', { name: /select row/i }); - await user.click(checkboxes[0]); - expect(checkboxes[0]).toHaveFocus(); - - await user.keyboard('{ArrowDown}'); - await user.keyboard('{ArrowDown}'); - await user.keyboard('{ArrowDown}'); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - virtualScroller.scrollTop = 250; // Scroll 5 rows - virtualScroller.dispatchEvent(new Event('scroll')); - expect(virtualScroller.scrollTop).to.equal(250); - }); + // HTMLElement.focus() only scrolls to the element on a real browser + testSkipIf(isJSDOM)( + 'should not jump during scroll while the focus is on the checkbox', + async () => { + const data = getBasicGridData(20, 1); + const { user } = render( + , + ); + const checkboxes = screen.queryAllByRole('checkbox', { name: /select row/i }); + await user.click(checkboxes[0]); + expect(checkboxes[0]).toHaveFocus(); + + await user.keyboard('{ArrowDown}'); + await user.keyboard('{ArrowDown}'); + await user.keyboard('{ArrowDown}'); + const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; + virtualScroller.scrollTop = 250; // Scroll 5 rows + virtualScroller.dispatchEvent(new Event('scroll')); + expect(virtualScroller.scrollTop).to.equal(250); + }, + ); it('should set tabindex=0 on the checkbox when the it receives focus', async () => { const { user } = render(); @@ -615,13 +612,9 @@ describe(' - Row selection', () => { // Skip on everything as this is failing on all environments on ubuntu/CI // describe('ripple', () => { // clock.withFakeTimers(); - - // it('should keep only one ripple visible when navigating between checkboxes', async function test() { - // if (isJSDOM) { - // // JSDOM doesn't fire "blur" when .focus is called in another element - // // FIXME Firefox doesn't show any ripple - // this.skip(); - // } + // // JSDOM doesn't fire "blur" when .focus is called in another element + // // FIXME Firefox doesn't show any ripple + // testSkipIf(isJSDOM)('should keep only one ripple visible when navigating between checkboxes', async () => { // render(); // const cell = getCell(1, 1); // fireUserEvent.mousePress(cell); diff --git a/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx b/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx index 868a5c28b2f10..65cfd5f2b6970 100644 --- a/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx @@ -4,8 +4,7 @@ import { expect } from 'chai'; import { spy } from 'sinon'; import { DataGrid, useGridApiRef, DataGridProps, GridApi } from '@mui/x-data-grid'; import { getCell, getActiveCell } from 'test/utils/helperFn'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Row spanning', () => { const { render } = createRenderer(); @@ -113,10 +112,7 @@ describe(' - Row spanning', () => { const rowHeight = 52; - it('should span the repeating row values', function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should span the repeating row values', () => { render(); const rowsWithSpannedCells = Object.keys(apiRef.current.state.rowSpanning.spannedCells); expect(rowsWithSpannedCells.length).to.equal(1); @@ -129,10 +125,7 @@ describe(' - Row spanning', () => { }); describe('sorting', () => { - it('should work with sorting when initializing sorting', function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should work with sorting when initializing sorting', () => { render( - Row spanning', () => { expect(spannedCell).to.have.style('height', `${rowHeight * spanValue.code}px`); }); - it('should work with sorting when controlling sorting', function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should work with sorting when controlling sorting', () => { render(); const rowsWithSpannedCells = Object.keys(apiRef.current.state.rowSpanning.spannedCells); expect(rowsWithSpannedCells.length).to.equal(1); @@ -165,10 +155,7 @@ describe(' - Row spanning', () => { }); describe('filtering', () => { - it('should work with filtering when initializing filter', function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should work with filtering when initializing filter', () => { render( - Row spanning', () => { expect(spannedCell).to.have.style('height', `${rowHeight * spanValue.code}px`); }); - it('should work with filtering when controlling filter', function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should work with filtering when controlling filter', () => { render( - Row spanning', () => { }); describe('pagination', () => { - it('should only compute the row spanning state for current page', async function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should only compute the row spanning state for current page', async () => { render( - Rows', () => { const { render } = createRenderer(); @@ -243,10 +242,7 @@ describe(' - Rows', () => { ); } - it('should throw an error if getActions is missing', function test() { - if (!isJSDOM) { - this.skip(); - } + testSkipIf(!isJSDOM)('should throw an error if getActions is missing', () => { expect(() => { render( @@ -501,14 +497,8 @@ describe(' - Rows', () => { }); }); - describe('prop: getRowHeight', () => { - before(function beforeHook() { - if (isJSDOM) { - // Need layouting - this.skip(); - } - }); - + // Need layouting + describeSkipIf(isJSDOM)('prop: getRowHeight', () => { describe('static row height', () => { const ROW_HEIGHT = 52; function TestCase(props: Partial) { @@ -818,35 +808,36 @@ describe(' - Rows', () => { expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); }); - it('should position correctly the render zone when the 2nd page has less rows than the 1st page', async function test() { - const { userAgent } = window.navigator; - if (!userAgent.includes('Headless') || /edg/i.test(userAgent)) { - this.skip(); // FIXME: We need a waitFor that works with fake clock - } - const data = getBasicGridData(120, 3); - const columnHeaderHeight = 50; - const measuredRowHeight = 100; - render( - measuredRowHeight} - getRowHeight={() => 'auto'} - rowBufferPx={0} - columnHeaderHeight={columnHeaderHeight} - getRowId={(row) => row.id} - hideFooter={false} - {...data} - />, - ); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - virtualScroller.scrollTop = 10e6; // Scroll to measure all cells - virtualScroller.dispatchEvent(new Event('scroll')); - - fireEvent.click(screen.getByRole('button', { name: /next page/i })); - - await waitFor(() => { - expect(gridOffsetTop()).to.equal(0); - }); - }); + // FIXME: We need a waitFor that works with fake clock + const { userAgent } = window.navigator; + testSkipIf(!userAgent.includes('Headless') || /edg/i.test(userAgent))( + 'should position correctly the render zone when the 2nd page has less rows than the 1st page', + async () => { + const data = getBasicGridData(120, 3); + const columnHeaderHeight = 50; + const measuredRowHeight = 100; + render( + measuredRowHeight} + getRowHeight={() => 'auto'} + rowBufferPx={0} + columnHeaderHeight={columnHeaderHeight} + getRowId={(row) => row.id} + hideFooter={false} + {...data} + />, + ); + const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; + virtualScroller.scrollTop = 10e6; // Scroll to measure all cells + virtualScroller.dispatchEvent(new Event('scroll')); + + fireEvent.click(screen.getByRole('button', { name: /next page/i })); + + await waitFor(() => { + expect(gridOffsetTop()).to.equal(0); + }); + }, + ); it('should position correctly the render zone when changing pageSize to a lower value', async () => { const data = getBasicGridData(120, 3); @@ -872,43 +863,43 @@ describe(' - Rows', () => { expect(gridOffsetTop()).to.equal(0); }); - it('should position correctly the render zone when changing pageSize to a lower value and moving to next page', async function test() { - const { userAgent } = window.navigator; - if (!userAgent.includes('Headless') || /edg/i.test(userAgent)) { - this.skip(); // In Chrome non-headless and Edge this test is flacky - } - const data = getBasicGridData(120, 3); - const columnHeaderHeight = 50; - const measuredRowHeight = 100; - - const { setProps } = render( - measuredRowHeight} - getRowHeight={() => 'auto'} - rowBufferPx={0} - columnHeaderHeight={columnHeaderHeight} - getRowId={(row) => row.id} - hideFooter={false} - initialState={{ pagination: { paginationModel: { pageSize: 25 } } }} - pageSizeOptions={[10, 25]} - height={columnHeaderHeight + 10 * measuredRowHeight} - {...data} - />, - ); + // In Chrome non-headless and Edge this test is flaky + testSkipIf(!userAgent.includes('Headless') || /edg/i.test(userAgent))( + 'should position correctly the render zone when changing pageSize to a lower value and moving to next page', + async () => { + const data = getBasicGridData(120, 3); + const columnHeaderHeight = 50; + const measuredRowHeight = 100; + + const { setProps } = render( + measuredRowHeight} + getRowHeight={() => 'auto'} + rowBufferPx={0} + columnHeaderHeight={columnHeaderHeight} + getRowId={(row) => row.id} + hideFooter={false} + initialState={{ pagination: { paginationModel: { pageSize: 25 } } }} + pageSizeOptions={[10, 25]} + height={columnHeaderHeight + 10 * measuredRowHeight} + {...data} + />, + ); - expect(gridOffsetTop()).to.equal(0); + expect(gridOffsetTop()).to.equal(0); - const virtualScroller = grid('virtualScroller')!; - virtualScroller.scrollTop = 10e6; // Scroll to measure all cells - virtualScroller.dispatchEvent(new Event('scroll')); + const virtualScroller = grid('virtualScroller')!; + virtualScroller.scrollTop = 10e6; // Scroll to measure all cells + virtualScroller.dispatchEvent(new Event('scroll')); - setProps({ pageSize: 10 }); - fireEvent.click(screen.getByRole('button', { name: /next page/i })); + setProps({ pageSize: 10 }); + fireEvent.click(screen.getByRole('button', { name: /next page/i })); - await waitFor(() => { - expect(gridOffsetTop()).to.equal(0); - }); - }); + await waitFor(() => { + expect(gridOffsetTop()).to.equal(0); + }); + }, + ); }); }); @@ -966,11 +957,8 @@ describe(' - Rows', () => { }); }); - it('should consider the spacing when computing the content size', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } + // Needs layout + testSkipIf(isJSDOM)('should consider the spacing when computing the content size', () => { const spacingTop = 5; const spacingBottom = 10; const rowHeight = 50; @@ -987,11 +975,8 @@ describe(' - Rows', () => { expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); }); - it('should update the content size when getRowSpacing is removed', function test() { - if (isJSDOM) { - // Need layouting - this.skip(); - } + // Needs layout + testSkipIf(isJSDOM)('should update the content size when getRowSpacing is removed', () => { const spacingTop = 5; const spacingBottom = 10; const rowHeight = 50; @@ -1122,46 +1107,47 @@ describe(' - Rows', () => { }); // https://github.com/mui/mui-x/issues/10373 - it('should set proper `data-rowindex` and `aria-rowindex` when focused row is out of the viewport', async function test() { - if (isJSDOM) { - // needs virtualization - this.skip(); - } - render( -
- -
, - ); + // needs virtualization + // needs virtualization + testSkipIf(isJSDOM)( + 'should set proper `data-rowindex` and `aria-rowindex` when focused row is out of the viewport', + async () => { + render( +
+ +
, + ); - const cell = getCell(0, 0); - fireUserEvent.mousePress(cell); + const cell = getCell(0, 0); + fireUserEvent.mousePress(cell); - const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; - virtualScroller.scrollTop = 1000; - virtualScroller.dispatchEvent(new Event('scroll')); + const virtualScroller = document.querySelector('.MuiDataGrid-virtualScroller')!; + virtualScroller.scrollTop = 1000; + virtualScroller.dispatchEvent(new Event('scroll')); - const focusedRow = getRow(0); - expect(focusedRow.getAttribute('data-id')).to.equal('0'); - expect(focusedRow.getAttribute('data-rowindex')).to.equal('0'); - expect(focusedRow.getAttribute('aria-rowindex')).to.equal('2'); // 1-based, 1 is the header + const focusedRow = getRow(0); + expect(focusedRow.getAttribute('data-id')).to.equal('0'); + expect(focusedRow.getAttribute('data-rowindex')).to.equal('0'); + expect(focusedRow.getAttribute('aria-rowindex')).to.equal('2'); // 1-based, 1 is the header - const lastRow = getRow(9); - expect(lastRow.getAttribute('data-id')).to.equal('9'); - expect(lastRow.getAttribute('data-rowindex')).to.equal('9'); - expect(lastRow.getAttribute('aria-rowindex')).to.equal('11'); // 1-based, 1 is the header - }); + const lastRow = getRow(9); + expect(lastRow.getAttribute('data-id')).to.equal('9'); + expect(lastRow.getAttribute('data-rowindex')).to.equal('9'); + expect(lastRow.getAttribute('aria-rowindex')).to.equal('11'); // 1-based, 1 is the header + }, + ); }); diff --git a/packages/x-data-grid/src/tests/slots.DataGrid.test.tsx b/packages/x-data-grid/src/tests/slots.DataGrid.test.tsx index 055f24b470593..39ee435cfe394 100644 --- a/packages/x-data-grid/src/tests/slots.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/slots.DataGrid.test.tsx @@ -10,6 +10,7 @@ import { expect } from 'chai'; import { spy } from 'sinon'; import { DataGrid, DataGridProps, GridOverlay } from '@mui/x-data-grid'; import { getCell, getRow } from 'test/utils/helperFn'; +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe(' - Slots', () => { const { render } = createRenderer(); @@ -159,14 +160,9 @@ describe(' - Slots', () => { }); }); - it('should throw if a component is used without providing the context', function test() { - // TODO is this fixed? - if (!/jsdom/.test(window.navigator.userAgent)) { - // can't catch render errors in the browser for unknown reason - // tried try-catch + error boundary + window onError preventDefault - this.skip(); - } - + // can't catch render errors in the browser for unknown reason + // tried try-catch + error boundary + window onError preventDefault + testSkipIf(!isJSDOM)('should throw if a component is used without providing the context', () => { expect(() => { render( diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx index 6cef402715228..6a69d1962043b 100644 --- a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx @@ -15,6 +15,7 @@ import { } from '@mui/x-date-pickers-pro/DateRangeCalendar'; import { DateRangePickerDay } from '@mui/x-date-pickers-pro/DateRangePickerDay'; import { describeConformance } from 'test/utils/describeConformance'; +import { testSkipIf } from 'test/utils/skipIf'; import { RangePosition } from '../models'; const getPickerDay = (name: string, picker = 'January 2018') => @@ -177,30 +178,30 @@ describe('', () => { expect(onChange.callCount).to.equal(0); }); - it('should not emit "onChange" when touch dragging is ended where it was started', function test() { - if (!document.elementFromPoint) { - this.skip(); - } - const onChange = spy(); - render( - , - ); - - const startDay = screen.getByRole('gridcell', { name: '1', selected: true }); - expect(onChange.callCount).to.equal(0); - - executeDateTouchDrag( - startDay, - rangeCalendarDayTouches['2018-01-01'], - rangeCalendarDayTouches['2018-01-02'], - rangeCalendarDayTouches['2018-01-01'], - ); - - expect(onChange.callCount).to.equal(0); - }); + testSkipIf(!document.elementFromPoint)( + 'should not emit "onChange" when touch dragging is ended where it was started', + () => { + const onChange = spy(); + render( + , + ); + + const startDay = screen.getByRole('gridcell', { name: '1', selected: true }); + expect(onChange.callCount).to.equal(0); + + executeDateTouchDrag( + startDay, + rangeCalendarDayTouches['2018-01-01'], + rangeCalendarDayTouches['2018-01-02'], + rangeCalendarDayTouches['2018-01-01'], + ); + + expect(onChange.callCount).to.equal(0); + }, + ); it('should emit "onChange" when dragging end date', () => { const onChange = spy(); @@ -245,50 +246,50 @@ describe('', () => { expect(document.activeElement).toHaveAccessibleName('2'); }); - it('should emit "onChange" when touch dragging end date', function test() { - if (!document.elementFromPoint) { - this.skip(); - } - const onChange = spy(); - const initialValue: [any, any] = [ - adapterToUse.date('2018-01-02'), - adapterToUse.date('2018-01-11'), - ]; - render(); - - // test range reduction - executeDateTouchDrag( - getPickerDay('11'), - rangeCalendarDayTouches['2018-01-11'], - rangeCalendarDayTouches['2018-01-10'], - ); - - expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0][0]).toEqualDateTime(initialValue[0]); - expect(onChange.lastCall.args[0][1]).toEqualDateTime(new Date(2018, 0, 10)); - - // test range expansion - executeDateTouchDrag( - getPickerDay('10'), - rangeCalendarDayTouches['2018-01-10'], - rangeCalendarDayTouches['2018-01-11'], - ); - - expect(onChange.callCount).to.equal(2); - expect(onChange.lastCall.args[0][0]).toEqualDateTime(initialValue[0]); - expect(onChange.lastCall.args[0][1]).toEqualDateTime(initialValue[1]); - - // test range flip - executeDateTouchDrag( - getPickerDay('11'), - rangeCalendarDayTouches['2018-01-11'], - rangeCalendarDayTouches['2018-01-01'], - ); - - expect(onChange.callCount).to.equal(3); - expect(onChange.lastCall.args[0][0]).toEqualDateTime(new Date(2018, 0, 1)); - expect(onChange.lastCall.args[0][1]).toEqualDateTime(initialValue[0]); - }); + testSkipIf(!document.elementFromPoint)( + 'should emit "onChange" when touch dragging end date', + () => { + const onChange = spy(); + const initialValue: [any, any] = [ + adapterToUse.date('2018-01-02'), + adapterToUse.date('2018-01-11'), + ]; + render(); + + // test range reduction + executeDateTouchDrag( + getPickerDay('11'), + rangeCalendarDayTouches['2018-01-11'], + rangeCalendarDayTouches['2018-01-10'], + ); + + expect(onChange.callCount).to.equal(1); + expect(onChange.lastCall.args[0][0]).toEqualDateTime(initialValue[0]); + expect(onChange.lastCall.args[0][1]).toEqualDateTime(new Date(2018, 0, 10)); + + // test range expansion + executeDateTouchDrag( + getPickerDay('10'), + rangeCalendarDayTouches['2018-01-10'], + rangeCalendarDayTouches['2018-01-11'], + ); + + expect(onChange.callCount).to.equal(2); + expect(onChange.lastCall.args[0][0]).toEqualDateTime(initialValue[0]); + expect(onChange.lastCall.args[0][1]).toEqualDateTime(initialValue[1]); + + // test range flip + executeDateTouchDrag( + getPickerDay('11'), + rangeCalendarDayTouches['2018-01-11'], + rangeCalendarDayTouches['2018-01-01'], + ); + + expect(onChange.callCount).to.equal(3); + expect(onChange.lastCall.args[0][0]).toEqualDateTime(new Date(2018, 0, 1)); + expect(onChange.lastCall.args[0][1]).toEqualDateTime(initialValue[0]); + }, + ); it('should emit "onChange" when dragging start date', () => { const onChange = spy(); @@ -323,50 +324,50 @@ describe('', () => { expect(document.activeElement).toHaveAccessibleName('22'); }); - it('should emit "onChange" when touch dragging start date', function test() { - if (!document.elementFromPoint) { - this.skip(); - } - const onChange = spy(); - const initialValue: [any, any] = [ - adapterToUse.date('2018-01-01'), - adapterToUse.date('2018-01-10'), - ]; - render(); - - // test range reduction - executeDateTouchDrag( - getPickerDay('1'), - rangeCalendarDayTouches['2018-01-01'], - rangeCalendarDayTouches['2018-01-02'], - ); - - expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.args[0][0]).toEqualDateTime(new Date(2018, 0, 2)); - expect(onChange.lastCall.args[0][1]).toEqualDateTime(initialValue[1]); - - // test range expansion - executeDateTouchDrag( - getPickerDay('2'), - rangeCalendarDayTouches['2018-01-02'], - rangeCalendarDayTouches['2018-01-01'], - ); - - expect(onChange.callCount).to.equal(2); - expect(onChange.lastCall.args[0][0]).toEqualDateTime(initialValue[0]); - expect(onChange.lastCall.args[0][1]).toEqualDateTime(initialValue[1]); - - // test range flip - executeDateTouchDrag( - getPickerDay('1'), - rangeCalendarDayTouches['2018-01-01'], - rangeCalendarDayTouches['2018-01-11'], - ); - - expect(onChange.callCount).to.equal(3); - expect(onChange.lastCall.args[0][0]).toEqualDateTime(initialValue[1]); - expect(onChange.lastCall.args[0][1]).toEqualDateTime(new Date(2018, 0, 11)); - }); + testSkipIf(!document.elementFromPoint)( + 'should emit "onChange" when touch dragging start date', + () => { + const onChange = spy(); + const initialValue: [any, any] = [ + adapterToUse.date('2018-01-01'), + adapterToUse.date('2018-01-10'), + ]; + render(); + + // test range reduction + executeDateTouchDrag( + getPickerDay('1'), + rangeCalendarDayTouches['2018-01-01'], + rangeCalendarDayTouches['2018-01-02'], + ); + + expect(onChange.callCount).to.equal(1); + expect(onChange.lastCall.args[0][0]).toEqualDateTime(new Date(2018, 0, 2)); + expect(onChange.lastCall.args[0][1]).toEqualDateTime(initialValue[1]); + + // test range expansion + executeDateTouchDrag( + getPickerDay('2'), + rangeCalendarDayTouches['2018-01-02'], + rangeCalendarDayTouches['2018-01-01'], + ); + + expect(onChange.callCount).to.equal(2); + expect(onChange.lastCall.args[0][0]).toEqualDateTime(initialValue[0]); + expect(onChange.lastCall.args[0][1]).toEqualDateTime(initialValue[1]); + + // test range flip + executeDateTouchDrag( + getPickerDay('1'), + rangeCalendarDayTouches['2018-01-01'], + rangeCalendarDayTouches['2018-01-11'], + ); + + expect(onChange.callCount).to.equal(3); + expect(onChange.lastCall.args[0][0]).toEqualDateTime(initialValue[1]); + expect(onChange.lastCall.args[0][1]).toEqualDateTime(new Date(2018, 0, 11)); + }, + ); it('should dynamically update "shouldDisableDate" when flip dragging', () => { const initialValue: [any, any] = [ @@ -398,39 +399,39 @@ describe('', () => { ).to.have.lengthOf(10); }); - it('should dynamically update "shouldDisableDate" when flip touch dragging', function test() { - if (!document.elementFromPoint) { - this.skip(); - } - const initialValue: [any, any] = [ - adapterToUse.date('2018-01-01'), - adapterToUse.date('2018-01-07'), - ]; - render( - , - ); - - expect(screen.getByRole('gridcell', { name: '5' })).to.have.attribute('disabled'); - expect( - screen.getAllByRole('gridcell').filter((c) => c.disabled), - ).to.have.lengthOf(6); - // flip date range - executeDateTouchDragWithoutEnd( - screen.getByRole('gridcell', { name: '1' }), - rangeCalendarDayTouches['2018-01-01'], - rangeCalendarDayTouches['2018-01-09'], - rangeCalendarDayTouches['2018-01-10'], - ); - - expect(screen.getByRole('gridcell', { name: '9' })).to.have.attribute('disabled'); - expect( - screen.getAllByRole('gridcell').filter((c) => c.disabled), - ).to.have.lengthOf(10); - }); + testSkipIf(!document.elementFromPoint)( + 'should dynamically update "shouldDisableDate" when flip touch dragging', + () => { + const initialValue: [any, any] = [ + adapterToUse.date('2018-01-01'), + adapterToUse.date('2018-01-07'), + ]; + render( + , + ); + + expect(screen.getByRole('gridcell', { name: '5' })).to.have.attribute('disabled'); + expect( + screen.getAllByRole('gridcell').filter((c) => c.disabled), + ).to.have.lengthOf(6); + // flip date range + executeDateTouchDragWithoutEnd( + screen.getByRole('gridcell', { name: '1' }), + rangeCalendarDayTouches['2018-01-01'], + rangeCalendarDayTouches['2018-01-09'], + rangeCalendarDayTouches['2018-01-10'], + ); + + expect(screen.getByRole('gridcell', { name: '9' })).to.have.attribute('disabled'); + expect( + screen.getAllByRole('gridcell').filter((c) => c.disabled), + ).to.have.lengthOf(10); + }, + ); }); }); diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/timezone.DateRangeCalendar.test.tsx b/packages/x-date-pickers-pro/src/DateRangeCalendar/timezone.DateRangeCalendar.test.tsx index 2d345ab1cf08b..b67c8252a8303 100644 --- a/packages/x-date-pickers-pro/src/DateRangeCalendar/timezone.DateRangeCalendar.test.tsx +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/timezone.DateRangeCalendar.test.tsx @@ -2,40 +2,39 @@ import * as React from 'react'; import { expect } from 'chai'; import { screen, fireEvent } from '@mui/internal-test-utils'; import { describeAdapters } from 'test/utils/pickers'; +import { describeSkipIf } from 'test/utils/skipIf'; import { DateRangeCalendar } from './DateRangeCalendar'; describe(' - Timezone', () => { describeAdapters('Timezone prop', DateRangeCalendar, ({ adapter, render }) => { - if (!adapter.isTimezoneCompatible) { - return; - } + describeSkipIf(!adapter.isTimezoneCompatible)('timezoneCompatible', () => { + it('should correctly render month days when timezone changes', () => { + function DateCalendarWithControlledTimezone() { + const [timezone, setTimezone] = React.useState('Europe/Paris'); + return ( + + + + + ); + } + render(); - it('should correctly render month days when timezone changes', () => { - function DateCalendarWithControlledTimezone() { - const [timezone, setTimezone] = React.useState('Europe/Paris'); - return ( - - - - - ); - } - render(); + expect( + screen.getAllByRole('gridcell', { + name: (_, element) => element.nodeName === 'BUTTON', + }).length, + ).to.equal(30); - expect( - screen.getAllByRole('gridcell', { - name: (_, element) => element.nodeName === 'BUTTON', - }).length, - ).to.equal(30); + fireEvent.click(screen.getByRole('button', { name: 'Switch timezone' })); - fireEvent.click(screen.getByRole('button', { name: 'Switch timezone' })); - - // the amount of rendered days should remain the same after changing timezone - expect( - screen.getAllByRole('gridcell', { - name: (_, element) => element.nodeName === 'BUTTON', - }).length, - ).to.equal(30); + // the amount of rendered days should remain the same after changing timezone + expect( + screen.getAllByRole('gridcell', { + name: (_, element) => element.nodeName === 'BUTTON', + }).length, + ).to.equal(30); + }); }); }); }); diff --git a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx index 807b5511d78ad..a79441429611e 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx @@ -16,8 +16,7 @@ import { getFieldSectionsContainer, getTextbox, } from 'test/utils/pickers'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; const getPickerDay = (name: string, picker = 'January 2018') => within(screen.getByRole('grid', { name: picker })).getByRole('gridcell', { name }); @@ -441,36 +440,35 @@ describe('', () => { expect(onClose.callCount).to.equal(0); }); - it('should call onClose when blur the current field without prior change', function test() { - // test:unit does not call `blur` when focusing another element. - if (isJSDOM) { - this.skip(); - } - - const onChange = spy(); - const onAccept = spy(); - const onClose = spy(); - - render( - - - - , - ); - - openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); - expect(screen.getByRole('tooltip')).toBeVisible(); + // test:unit does not call `blur` when focusing another element. + testSkipIf(isJSDOM)( + 'should call onClose when blur the current field without prior change', + () => { + const onChange = spy(); + const onAccept = spy(); + const onClose = spy(); + + render( + + + + , + ); + + openPicker({ type: 'date-range', variant: 'desktop', initialFocus: 'start' }); + expect(screen.getByRole('tooltip')).toBeVisible(); - document.querySelector('#test')!.focus(); - clock.runToLast(); + document.querySelector('#test')!.focus(); + clock.runToLast(); - expect(onChange.callCount).to.equal(0); - expect(onAccept.callCount).to.equal(0); - expect(onClose.callCount).to.equal(1); - }); + expect(onChange.callCount).to.equal(0); + expect(onAccept.callCount).to.equal(0); + expect(onClose.callCount).to.equal(1); + }, + ); it('should call onClose and onAccept when blur the current field', () => { const onChange = spy(); diff --git a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx index a6456ef66a570..6f099d50f759b 100644 --- a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx +++ b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx @@ -5,8 +5,7 @@ import { fireEvent, screen } from '@mui/internal-test-utils'; import { DateCalendar } from '@mui/x-date-pickers/DateCalendar'; import { PickersDay } from '@mui/x-date-pickers/PickersDay'; import { createPickerRenderer, adapterToUse } from 'test/utils/pickers'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe('', () => { const { render, clock } = createPickerRenderer({ @@ -525,10 +524,8 @@ describe('', () => { expect(screen.getByTestId('calendar-month-and-year-text')).to.have.text('January 2022'); }); - it('should scroll to show the selected year', function test() { - if (isJSDOM) { - this.skip(); // Needs layout - } + // Needs layout + testSkipIf(isJSDOM)('should scroll to show the selected year', () => { render( - Timezone', () => { describeAdapters('Timezone prop', DateCalendar, ({ adapter, render }) => { - if (!adapter.isTimezoneCompatible) { - return; - } + describeSkipIf(!adapter.isTimezoneCompatible)('timezoneCompatible', () => { + it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { + const onChange = spy(); + render(); - it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { - const onChange = spy(); - render(); + fireEvent.click(screen.getByRole('gridcell', { name: '25' })); + const expectedDate = adapter.setDate(adapter.date(undefined, 'default'), 25); - fireEvent.click(screen.getByRole('gridcell', { name: '25' })); - const expectedDate = adapter.setDate(adapter.date(undefined, 'default'), 25); + // Check the `onChange` value (uses default timezone, for example: UTC, see TZ env variable) + const actualDate = onChange.lastCall.firstArg; - // Check the `onChange` value (uses default timezone, for example: UTC, see TZ env variable) - const actualDate = onChange.lastCall.firstArg; - - // On dayjs, we are not able to know if a date is UTC because it's the system timezone or because it was created as UTC. - // In a real world scenario, this should probably never occur. - expect(adapter.getTimezone(actualDate)).to.equal(adapter.lib === 'dayjs' ? 'UTC' : 'system'); - expect(actualDate).toEqualDateTime(expectedDate); - }); + // On dayjs, we are not able to know if a date is UTC because it's the system timezone or because it was created as UTC. + // In a real world scenario, this should probably never occur. + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' ? 'UTC' : 'system', + ); + expect(actualDate).toEqualDateTime(expectedDate); + }); - it('should use "default" timezone for onChange when provided', () => { - const onChange = spy(); - const value = adapter.date('2022-04-25T15:30'); + it('should use "default" timezone for onChange when provided', () => { + const onChange = spy(); + const value = adapter.date('2022-04-25T15:30'); - render(); + render(); - fireEvent.click(screen.getByRole('gridcell', { name: '25' })); - const expectedDate = adapter.setDate(value, 25); + fireEvent.click(screen.getByRole('gridcell', { name: '25' })); + const expectedDate = adapter.setDate(value, 25); - // Check the `onChange` value (uses timezone prop) - const actualDate = onChange.lastCall.firstArg; - expect(adapter.getTimezone(actualDate)).to.equal(adapter.lib === 'dayjs' ? 'UTC' : 'system'); - expect(actualDate).toEqualDateTime(expectedDate); - }); - - it('should correctly render month days when timezone changes', () => { - function DateCalendarWithControlledTimezone() { - const [timezone, setTimezone] = React.useState('Europe/Paris'); - return ( - - - - + // Check the `onChange` value (uses timezone prop) + const actualDate = onChange.lastCall.firstArg; + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' ? 'UTC' : 'system', ); - } - render(); - - expect( - screen.getAllByRole('gridcell', { - name: (_, element) => element.nodeName === 'BUTTON', - }).length, - ).to.equal(30); - - fireEvent.click(screen.getByRole('button', { name: 'Switch timezone' })); - - // the amount of rendered days should remain the same after changing timezone - expect( - screen.getAllByRole('gridcell', { - name: (_, element) => element.nodeName === 'BUTTON', - }).length, - ).to.equal(30); - }); - - // See https://github.com/mui/mui-x/issues/14730 - it('should not render duplicate days when leaving DST in America/Asuncion', () => { - render(); - expect(screen.getAllByRole('gridcell', { name: '5' })).to.have.length(1); - }); - - TIMEZONE_TO_TEST.forEach((timezone) => { - describe(`Timezone: ${timezone}`, () => { - it('should use timezone prop for onChange when no value is provided', () => { - const onChange = spy(); - render(); - fireEvent.click(screen.getByRole('gridcell', { name: '25' })); - const expectedDate = adapter.setDate( - adapter.startOfDay(adapter.date(undefined, timezone)), - 25, - ); + expect(actualDate).toEqualDateTime(expectedDate); + }); - // Check the `onChange` value (uses timezone prop) - const actualDate = onChange.lastCall.firstArg; - expect(adapter.getTimezone(actualDate)).to.equal( - adapter.lib === 'dayjs' && timezone === 'system' ? 'UTC' : timezone, + it('should correctly render month days when timezone changes', () => { + function DateCalendarWithControlledTimezone() { + const [timezone, setTimezone] = React.useState('Europe/Paris'); + return ( + + + + ); - expect(actualDate).toEqualDateTime(expectedDate); - }); - - it('should use value timezone for onChange when a value is provided', () => { - const onChange = spy(); - const value = adapter.date('2022-04-25T15:30', timezone); - - render(); + } + render(); + + expect( + screen.getAllByRole('gridcell', { + name: (_, element) => element.nodeName === 'BUTTON', + }).length, + ).to.equal(30); + + fireEvent.click(screen.getByRole('button', { name: 'Switch timezone' })); + + // the amount of rendered days should remain the same after changing timezone + expect( + screen.getAllByRole('gridcell', { + name: (_, element) => element.nodeName === 'BUTTON', + }).length, + ).to.equal(30); + }); - fireEvent.click(screen.getByRole('gridcell', { name: '25' })); - const expectedDate = adapter.setDate(value, 25); + // See https://github.com/mui/mui-x/issues/14730 + it('should not render duplicate days when leaving DST in America/Asuncion', () => { + render(); + expect(screen.getAllByRole('gridcell', { name: '5' })).to.have.length(1); + }); - // Check the `onChange` value (uses timezone prop) - const actualDate = onChange.lastCall.firstArg; - expect(adapter.getTimezone(actualDate)).to.equal(timezone); - expect(actualDate).toEqualDateTime(expectedDate); + TIMEZONE_TO_TEST.forEach((timezone) => { + describe(`Timezone: ${timezone}`, () => { + it('should use timezone prop for onChange when no value is provided', () => { + const onChange = spy(); + render(); + fireEvent.click(screen.getByRole('gridcell', { name: '25' })); + const expectedDate = adapter.setDate( + adapter.startOfDay(adapter.date(undefined, timezone)), + 25, + ); + + // Check the `onChange` value (uses timezone prop) + const actualDate = onChange.lastCall.firstArg; + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' && timezone === 'system' ? 'UTC' : timezone, + ); + expect(actualDate).toEqualDateTime(expectedDate); + }); + + it('should use value timezone for onChange when a value is provided', () => { + const onChange = spy(); + const value = adapter.date('2022-04-25T15:30', timezone); + + render(); + + fireEvent.click(screen.getByRole('gridcell', { name: '25' })); + const expectedDate = adapter.setDate(value, 25); + + // Check the `onChange` value (uses timezone prop) + const actualDate = onChange.lastCall.firstArg; + expect(adapter.getTimezone(actualDate)).to.equal(timezone); + expect(actualDate).toEqualDateTime(expectedDate); + }); }); }); }); diff --git a/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx b/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx index 2a0313757239d..7f665ceec0678 100644 --- a/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx +++ b/packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx @@ -9,6 +9,7 @@ import { expectFieldValueV6, } from 'test/utils/pickers'; import { fireUserEvent } from 'test/utils/fireUserEvent'; +import { testSkipIf } from 'test/utils/skipIf'; describe(' - Editing', () => { describeAdapters('key: ArrowDown', DateField, ({ adapter, testFieldKeyPress }) => { @@ -935,12 +936,8 @@ describe(' - Editing', () => { }); }); - it('should support day with letter suffix', function test() { - // Luxon don't have any day format with a letter suffix - if (adapter.lib === 'luxon') { - this.skip(); - } - + // Luxon doesn't have any day format with a letter suffix + testSkipIf(adapter.lib === 'luxon')('should support day with letter suffix', () => { testFieldChange({ format: adapter.lib === 'date-fns' ? 'do' : 'Do', keyStrokes: [ diff --git a/packages/x-date-pickers/src/DateField/tests/format.DateField.test.tsx b/packages/x-date-pickers/src/DateField/tests/format.DateField.test.tsx index 77b4ae01ba758..77ef02f242320 100644 --- a/packages/x-date-pickers/src/DateField/tests/format.DateField.test.tsx +++ b/packages/x-date-pickers/src/DateField/tests/format.DateField.test.tsx @@ -6,11 +6,11 @@ import { describeAdapters, } from 'test/utils/pickers'; import { DateField } from '@mui/x-date-pickers/DateField'; +import { testSkipIf } from 'test/utils/skipIf'; describeAdapters(' - Format', DateField, ({ adapter, renderWithProps }) => { + const { start: startChar, end: endChar } = adapter.escapedCharacters; it('should support escaped characters in start separator', () => { - const { start: startChar, end: endChar } = adapter.escapedCharacters; - // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, @@ -38,8 +38,6 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp }); it('should support escaped characters between sections separator', () => { - const { start: startChar, end: endChar } = adapter.escapedCharacters; - // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, @@ -68,14 +66,9 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp expectFieldValueV6(input, 'January Escaped 2019'); }); - it('should support nested escaped characters', function test() { - const { start: startChar, end: endChar } = adapter.escapedCharacters; - // If your start character and end character are equal - // Then you can't have nested escaped characters - if (startChar === endChar) { - this.skip(); - } - + // If your start character and end character are equal + // Then you can't have nested escaped characters + testSkipIf(startChar === endChar)('should support nested escaped characters', () => { // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, @@ -105,8 +98,6 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp }); it('should support several escaped parts', () => { - const { start: startChar, end: endChar } = adapter.escapedCharacters; - // Test with accessible DOM structure let view = renderWithProps({ enableAccessibleFieldDOMStructure: true, @@ -135,9 +126,7 @@ describeAdapters(' - Format', DateField, ({ adapter, renderWithProp expectFieldValueV6(input, 'Escaped January Escaped 2019'); }); - it('should support format with only escaped parts', function test() { - const { start: startChar, end: endChar } = adapter.escapedCharacters; - + it('should support format with only escaped parts', () => { // Test with accessible DOM structure const view = renderWithProps({ enableAccessibleFieldDOMStructure: true, diff --git a/packages/x-date-pickers/src/DateTimeField/tests/timezone.DateTimeField.test.tsx b/packages/x-date-pickers/src/DateTimeField/tests/timezone.DateTimeField.test.tsx index ad50563ba3d84..3cbe351780baf 100644 --- a/packages/x-date-pickers/src/DateTimeField/tests/timezone.DateTimeField.test.tsx +++ b/packages/x-date-pickers/src/DateTimeField/tests/timezone.DateTimeField.test.tsx @@ -9,108 +9,109 @@ import { describeAdapters, buildFieldInteractions, } from 'test/utils/pickers'; +import { describeSkipIf } from 'test/utils/skipIf'; const TIMEZONE_TO_TEST = ['UTC', 'system', 'America/New_York']; describe(' - Timezone', () => { describeAdapters('Timezone prop', DateTimeField, ({ adapter, renderWithProps }) => { - if (!adapter.isTimezoneCompatible) { - return; - } - - const format = `${adapter.formats.keyboardDate} ${adapter.formats.hours24h}`; - - const fillEmptyValue = (v7Response: ReturnType, timezone: string) => { - v7Response.selectSection('month'); - - // Set month - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowDown' }); - fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowRight' }); - - // Set day - fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'ArrowDown' }); - fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'ArrowRight' }); - - // Set year - fireEvent.keyDown(v7Response.getActiveSection(2), { key: 'ArrowDown' }); - fireEvent.keyDown(v7Response.getActiveSection(2), { key: 'ArrowRight' }); - - // Set hours - fireEvent.keyDown(v7Response.getActiveSection(3), { key: 'ArrowDown' }); - fireEvent.keyDown(v7Response.getActiveSection(3), { key: 'ArrowRight' }); - - return adapter.setHours( - adapter.setDate(adapter.setMonth(adapter.date(undefined, timezone), 11), 31), - 23, - ); - }; - - it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { - const onChange = spy(); - const view = renderWithProps({ - enableAccessibleFieldDOMStructure: true, - onChange, - format, - }); + describeSkipIf(!adapter.isTimezoneCompatible)('timezoneCompatible', () => { + const format = `${adapter.formats.keyboardDate} ${adapter.formats.hours24h}`; + + const fillEmptyValue = (v7Response: ReturnType, timezone: string) => { + v7Response.selectSection('month'); + + // Set month + fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowDown' }); + fireEvent.keyDown(v7Response.getActiveSection(0), { key: 'ArrowRight' }); + + // Set day + fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'ArrowDown' }); + fireEvent.keyDown(v7Response.getActiveSection(1), { key: 'ArrowRight' }); + + // Set year + fireEvent.keyDown(v7Response.getActiveSection(2), { key: 'ArrowDown' }); + fireEvent.keyDown(v7Response.getActiveSection(2), { key: 'ArrowRight' }); + + // Set hours + fireEvent.keyDown(v7Response.getActiveSection(3), { key: 'ArrowDown' }); + fireEvent.keyDown(v7Response.getActiveSection(3), { key: 'ArrowRight' }); + + return adapter.setHours( + adapter.setDate(adapter.setMonth(adapter.date(undefined, timezone), 11), 31), + 23, + ); + }; + + it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { + const onChange = spy(); + const view = renderWithProps({ + enableAccessibleFieldDOMStructure: true, + onChange, + format, + }); - const expectedDate = fillEmptyValue(view, 'default'); + const expectedDate = fillEmptyValue(view, 'default'); - // Check the rendered value (uses default timezone, for example: UTC, see TZ env variable) - expectFieldValueV7(view.getSectionsContainer(), '12/31/2022 23'); + // Check the rendered value (uses default timezone, for example: UTC, see TZ env variable) + expectFieldValueV7(view.getSectionsContainer(), '12/31/2022 23'); - // Check the `onChange` value (uses default timezone, for example: UTC, see TZ env variable) - const actualDate = onChange.lastCall.firstArg; + // Check the `onChange` value (uses default timezone, for example: UTC, see TZ env variable) + const actualDate = onChange.lastCall.firstArg; - // On dayjs, we are not able to know if a date is UTC because it's the system timezone or because it was created as UTC. - // In a real world scenario, this should probably never occur. - expect(adapter.getTimezone(actualDate)).to.equal(adapter.lib === 'dayjs' ? 'UTC' : 'system'); - expect(actualDate).toEqualDateTime(expectedDate); - }); + // On dayjs, we are not able to know if a date is UTC because it's the system timezone or because it was created as UTC. + // In a real world scenario, this should probably never occur. + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' ? 'UTC' : 'system', + ); + expect(actualDate).toEqualDateTime(expectedDate); + }); - TIMEZONE_TO_TEST.forEach((timezone) => { - describe(`Timezone: ${timezone}`, () => { - it('should use timezone prop for onChange and rendering when no value is provided', () => { - const onChange = spy(); - const view = renderWithProps({ - enableAccessibleFieldDOMStructure: true, - onChange, - format, - timezone, + TIMEZONE_TO_TEST.forEach((timezone) => { + describe(`Timezone: ${timezone}`, () => { + it('should use timezone prop for onChange and rendering when no value is provided', () => { + const onChange = spy(); + const view = renderWithProps({ + enableAccessibleFieldDOMStructure: true, + onChange, + format, + timezone, + }); + const expectedDate = fillEmptyValue(view, timezone); + + // Check the rendered value (uses timezone prop) + expectFieldValueV7(view.getSectionsContainer(), '12/31/2022 23'); + + // Check the `onChange` value (uses timezone prop) + const actualDate = onChange.lastCall.firstArg; + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' && timezone === 'system' ? 'UTC' : timezone, + ); + expect(actualDate).toEqualDateTime(expectedDate); }); - const expectedDate = fillEmptyValue(view, timezone); - - // Check the rendered value (uses timezone prop) - expectFieldValueV7(view.getSectionsContainer(), '12/31/2022 23'); - // Check the `onChange` value (uses timezone prop) - const actualDate = onChange.lastCall.firstArg; - expect(adapter.getTimezone(actualDate)).to.equal( - adapter.lib === 'dayjs' && timezone === 'system' ? 'UTC' : timezone, - ); - expect(actualDate).toEqualDateTime(expectedDate); - }); - - it('should use timezone prop for rendering and value timezone for onChange when a value is provided', () => { - const onChange = spy(); - const view = renderWithProps({ - enableAccessibleFieldDOMStructure: true, - value: adapter.date(undefined, timezone), - onChange, - format, - timezone: 'America/Chicago', + it('should use timezone prop for rendering and value timezone for onChange when a value is provided', () => { + const onChange = spy(); + const view = renderWithProps({ + enableAccessibleFieldDOMStructure: true, + value: adapter.date(undefined, timezone), + onChange, + format, + timezone: 'America/Chicago', + }); + + view.selectSection('month'); + fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowDown' }); + + // Check the rendered value (uses America/Chicago timezone) + expectFieldValueV7(view.getSectionsContainer(), '05/14/2022 19'); + + // Check the `onChange` value (uses timezone prop) + const expectedDate = adapter.addMonths(adapter.date(undefined, timezone), -1); + const actualDate = onChange.lastCall.firstArg; + expect(adapter.getTimezone(actualDate)).to.equal(timezone); + expect(actualDate).toEqualDateTime(expectedDate); }); - - view.selectSection('month'); - fireEvent.keyDown(view.getActiveSection(0), { key: 'ArrowDown' }); - - // Check the rendered value (uses America/Chicago timezone) - expectFieldValueV7(view.getSectionsContainer(), '05/14/2022 19'); - - // Check the `onChange` value (uses timezone prop) - const expectedDate = adapter.addMonths(adapter.date(undefined, timezone), -1); - const actualDate = onChange.lastCall.firstArg; - expect(adapter.getTimezone(actualDate)).to.equal(timezone); - expect(actualDate).toEqualDateTime(expectedDate); }); }); }); diff --git a/packages/x-date-pickers/src/DesktopDatePicker/tests/DesktopDatePicker.test.tsx b/packages/x-date-pickers/src/DesktopDatePicker/tests/DesktopDatePicker.test.tsx index fe9e436f69261..d9650084b2a90 100644 --- a/packages/x-date-pickers/src/DesktopDatePicker/tests/DesktopDatePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDatePicker/tests/DesktopDatePicker.test.tsx @@ -6,8 +6,7 @@ import { inputBaseClasses } from '@mui/material/InputBase'; import { fireEvent, screen } from '@mui/internal-test-utils'; import { DesktopDatePicker } from '@mui/x-date-pickers/DesktopDatePicker'; import { createPickerRenderer, adapterToUse, openPicker } from 'test/utils/pickers'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { describeSkipIf, testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe('', () => { const { render, clock } = createPickerRenderer({ clock: 'fake' }); @@ -101,10 +100,7 @@ describe('', () => { expect(screen.getByRole('radio', { checked: true, name: 'January' })).not.to.equal(null); }); - it('should move the focus to the newly opened views', function test() { - if (isJSDOM) { - this.skip(); - } + testSkipIf(isJSDOM)('should move the focus to the newly opened views', () => { render(); openPicker({ type: 'date', variant: 'desktop' }); @@ -140,7 +136,8 @@ describe('', () => { }); }); - describe('scroll', () => { + // JSDOM has neither layout nor window.scrollTo + describeSkipIf(isJSDOM)('scroll', () => { const NoTransition = React.forwardRef(function NoTransition( props: TransitionProps & { children?: React.ReactNode }, ref: React.Ref, @@ -157,13 +154,6 @@ describe('', () => { ); }); - before(function beforeHook() { - // JSDOM has neither layout nor window.scrollTo - if (/jsdom/.test(window.navigator.userAgent)) { - this.skip(); - } - }); - let originalScrollX: number; let originalScrollY: number; diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/describes.DesktopDateTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/describes.DesktopDateTimePicker.test.tsx index 5d6ab646c7b95..1370af67311b2 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/tests/describes.DesktopDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/tests/describes.DesktopDateTimePicker.test.tsx @@ -17,7 +17,7 @@ import { describeConformance } from 'test/utils/describeConformance'; describe(' - Describes', () => { const { render, clock } = createPickerRenderer({ clock: 'fake' }); - it('should respect the `localeText` prop', function test() { + it('should respect the `localeText` prop', () => { render( { describe(' - Timezone', () => { describeAdapters('Timezone prop', DigitalClock, ({ adapter, render }) => { - if (!adapter.isTimezoneCompatible) { - return; - } + describeSkipIf(!adapter.isTimezoneCompatible)('timezoneCompatible', () => { + it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { + const onChange = spy(); + render(); - it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { - const onChange = spy(); - render(); + fireEvent.click(screen.getByRole('option', { name: '08:00 AM' })); - fireEvent.click(screen.getByRole('option', { name: '08:00 AM' })); + const expectedDate = adapter.setHours(adapter.date(), 8); - const expectedDate = adapter.setHours(adapter.date(), 8); + // Check the `onChange` value (uses default timezone, for example: UTC, see TZ env variable) + const actualDate = onChange.lastCall.firstArg; - // Check the `onChange` value (uses default timezone, for example: UTC, see TZ env variable) - const actualDate = onChange.lastCall.firstArg; - - // On dayjs, we are not able to know if a date is UTC because it's the system timezone or because it was created as UTC. - // In a real world scenario, this should probably never occur. - expect(adapter.getTimezone(actualDate)).to.equal(adapter.lib === 'dayjs' ? 'UTC' : 'system'); - expect(actualDate).toEqualDateTime(expectedDate); - }); + // On dayjs, we are not able to know if a date is UTC because it's the system timezone or because it was created as UTC. + // In a real world scenario, this should probably never occur. + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' ? 'UTC' : 'system', + ); + expect(actualDate).toEqualDateTime(expectedDate); + }); - it('should render correct time options when fall back DST occurs', () => { - render( - , - ); - const oneAM = adapter.setMinutes(adapter.setHours(adapter.date(undefined, 'default'), 1), 0); - const elevenPM = adapter.setMinutes( - adapter.setHours(adapter.date(undefined, 'default'), 23), - 0, - ); - expect( - screen.getAllByText( - adapter.format( - oneAM, - adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + it('should render correct time options when fall back DST occurs', () => { + render( + , + ); + const oneAM = adapter.setMinutes( + adapter.setHours(adapter.date(undefined, 'default'), 1), + 0, + ); + const elevenPM = adapter.setMinutes( + adapter.setHours(adapter.date(undefined, 'default'), 23), + 0, + ); + expect( + screen.getAllByText( + adapter.format( + oneAM, + adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + ), ), - ), - ).to.have.length(adapter.lib === 'dayjs' ? 1 : 2); - expect( - screen.getAllByText( - adapter.format( - elevenPM, - adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + ).to.have.length(adapter.lib === 'dayjs' ? 1 : 2); + expect( + screen.getAllByText( + adapter.format( + elevenPM, + adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + ), ), - ), - ).to.have.length(1); - }); + ).to.have.length(1); + }); - it('should contain time options until the end of day when spring forward DST occurs', () => { - render( - , - ); - const startOfDay = adapter.setMinutes( - adapter.setHours(adapter.date(undefined, 'default'), 0), - 0, - ); - const eleven30PM = adapter.setMinutes( - adapter.setHours(adapter.date(undefined, 'default'), 23), - 30, - ); - expect( - screen.getAllByText( - adapter.format( - startOfDay, - adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + it('should contain time options until the end of day when spring forward DST occurs', () => { + render( + , + ); + const startOfDay = adapter.setMinutes( + adapter.setHours(adapter.date(undefined, 'default'), 0), + 0, + ); + const eleven30PM = adapter.setMinutes( + adapter.setHours(adapter.date(undefined, 'default'), 23), + 30, + ); + expect( + screen.getAllByText( + adapter.format( + startOfDay, + adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + ), ), - ), - ).to.have.length(1); - expect( - screen.getAllByText( - adapter.format( - eleven30PM, - adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + ).to.have.length(1); + expect( + screen.getAllByText( + adapter.format( + eleven30PM, + adapter.is12HourCycleInCurrentLocale() ? 'fullTime12h' : 'fullTime24h', + ), ), - ), - ).to.have.length(1); - }); + ).to.have.length(1); + }); - TIMEZONE_TO_TEST.forEach((timezone) => { - describe(`Timezone: ${timezone}`, () => { - it('should use timezone prop for onChange when no value is provided', () => { - const onChange = spy(); - render(); - - fireEvent.click(screen.getByRole('option', { name: '08:00 AM' })); - - const expectedDate = adapter.setHours( - adapter.startOfDay(adapter.date(undefined, timezone)), - 8, - ); - - // Check the `onChange` value (uses timezone prop) - const actualDate = onChange.lastCall.firstArg; - expect(adapter.getTimezone(actualDate)).to.equal( - adapter.lib === 'dayjs' && timezone === 'system' ? 'UTC' : timezone, - ); - expect(actualDate).toEqualDateTime(expectedDate); - }); + TIMEZONE_TO_TEST.forEach((timezone) => { + describe(`Timezone: ${timezone}`, () => { + it('should use timezone prop for onChange when no value is provided', () => { + const onChange = spy(); + render(); + + fireEvent.click(screen.getByRole('option', { name: '08:00 AM' })); + + const expectedDate = adapter.setHours( + adapter.startOfDay(adapter.date(undefined, timezone)), + 8, + ); + + // Check the `onChange` value (uses timezone prop) + const actualDate = onChange.lastCall.firstArg; + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' && timezone === 'system' ? 'UTC' : timezone, + ); + expect(actualDate).toEqualDateTime(expectedDate); + }); - it('should use timezone prop for rendering and value timezone for onChange when a value is provided', () => { - const onChange = spy(); - const value = adapter.date('2022-04-17T04:30', timezone); + it('should use timezone prop for rendering and value timezone for onChange when a value is provided', () => { + const onChange = spy(); + const value = adapter.date('2022-04-17T04:30', timezone); - render( - , - ); + render( + , + ); - const renderedHourBefore = get24HourFromDigitalClock(); + const renderedHourBefore = get24HourFromDigitalClock(); - const offsetDiff = - getDateOffset(adapter, adapter.setTimezone(value, 'America/Chicago')) - - getDateOffset(adapter, value); + const offsetDiff = + getDateOffset(adapter, adapter.setTimezone(value, 'America/Chicago')) - + getDateOffset(adapter, value); - expect(renderedHourBefore).to.equal( - (adapter.getHours(value) + offsetDiff / 60 + 24) % 24, - ); + expect(renderedHourBefore).to.equal( + (adapter.getHours(value) + offsetDiff / 60 + 24) % 24, + ); - fireEvent.click(screen.getByRole('option', { name: '08:30 PM' })); + fireEvent.click(screen.getByRole('option', { name: '08:30 PM' })); - const actualDate = onChange.lastCall.firstArg; + const actualDate = onChange.lastCall.firstArg; - const renderedHourAfter = get24HourFromDigitalClock(); - expect(renderedHourAfter).to.equal( - (adapter.getHours(actualDate) + offsetDiff / 60 + 24) % 24, - ); + const renderedHourAfter = get24HourFromDigitalClock(); + expect(renderedHourAfter).to.equal( + (adapter.getHours(actualDate) + offsetDiff / 60 + 24) % 24, + ); - const expectedDate = adapter.addHours(value, renderedHourAfter - renderedHourBefore); + const expectedDate = adapter.addHours(value, renderedHourAfter - renderedHourBefore); - expect(adapter.getTimezone(actualDate)).to.equal(timezone); - expect(actualDate).toEqualDateTime(expectedDate); + expect(adapter.getTimezone(actualDate)).to.equal(timezone); + expect(actualDate).toEqualDateTime(expectedDate); + }); }); }); }); diff --git a/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx b/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx index da2eb24c177ed..16a032c454a06 100644 --- a/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileDateTimePicker/tests/MobileDateTimePicker.test.tsx @@ -10,6 +10,7 @@ import { getClockTouchEvent, getFieldSectionsContainer, } from 'test/utils/pickers'; +import { hasTouchSupport, testSkipIf } from 'test/utils/skipIf'; describe('', () => { const { render, clock } = createPickerRenderer({ clock: 'fake' }); @@ -94,11 +95,7 @@ describe('', () => { expect(screen.queryByRole('dialog')).toBeVisible(); }); - it('should call onChange when selecting each view', function test() { - if (typeof window.Touch === 'undefined' || typeof window.TouchEvent === 'undefined') { - this.skip(); - } - + testSkipIf(!hasTouchSupport)('should call onChange when selecting each view', () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); diff --git a/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx b/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx index e5442cc502769..b902e6808b17b 100644 --- a/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileTimePicker/tests/MobileTimePicker.test.tsx @@ -10,6 +10,7 @@ import { getClockTouchEvent, getFieldSectionsContainer, } from 'test/utils/pickers'; +import { testSkipIf, hasTouchSupport } from 'test/utils/skipIf'; describe('', () => { const { render } = createPickerRenderer({ clock: 'fake' }); @@ -45,11 +46,7 @@ describe('', () => { expect(handleChange.firstCall.args[0]).toEqualDateTime(new Date(2019, 0, 1, 16, 20)); }); - it('should call onChange when selecting each view', function test() { - if (typeof window.Touch === 'undefined' || typeof window.TouchEvent === 'undefined') { - this.skip(); - } - + testSkipIf(!hasTouchSupport)('should call onChange when selecting each view', () => { const onChange = spy(); const onAccept = spy(); const onClose = spy(); diff --git a/packages/x-date-pickers/src/MobileTimePicker/tests/timezone.MobileTimePicker.test.tsx b/packages/x-date-pickers/src/MobileTimePicker/tests/timezone.MobileTimePicker.test.tsx index 306b4dad76c53..47834200f5a0b 100644 --- a/packages/x-date-pickers/src/MobileTimePicker/tests/timezone.MobileTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileTimePicker/tests/timezone.MobileTimePicker.test.tsx @@ -3,39 +3,38 @@ import { screen } from '@mui/internal-test-utils'; import { describeAdapters } from 'test/utils/pickers'; import { MobileTimePicker } from '@mui/x-date-pickers/MobileTimePicker'; import { expect } from 'chai'; +import { describeSkipIf } from 'test/utils/skipIf'; describe(' - Timezone', () => { describeAdapters('Timezone prop', MobileTimePicker, ({ adapter, render }) => { - if (!adapter.isTimezoneCompatible) { - return; - } + describeSkipIf(!adapter.isTimezoneCompatible)('timezoneCompatible', () => { + it('should use the timezone prop for the value displayed in the toolbar', () => { + render( + , + ); - it('should use the timezone prop for the value displayed in the toolbar', () => { - render( - , - ); + expect(screen.getByTestId('hours')).to.have.text('11'); + }); - expect(screen.getByTestId('hours')).to.have.text('11'); - }); - - it('should use the updated timezone prop for the value displayed in the toolbar', () => { - const { setProps } = render( - , - ); + it('should use the updated timezone prop for the value displayed in the toolbar', () => { + const { setProps } = render( + , + ); - expect(screen.getByTestId('hours')).to.have.text('03'); + expect(screen.getByTestId('hours')).to.have.text('03'); - setProps({ timezone: 'America/New_York' }); + setProps({ timezone: 'America/New_York' }); - expect(screen.getByTestId('hours')).to.have.text('11'); + expect(screen.getByTestId('hours')).to.have.text('11'); + }); }); }); }); diff --git a/packages/x-date-pickers/src/StaticDatePicker/tests/StaticDatePicker.test.tsx b/packages/x-date-pickers/src/StaticDatePicker/tests/StaticDatePicker.test.tsx index a1c02cfab51c2..249578da15d61 100644 --- a/packages/x-date-pickers/src/StaticDatePicker/tests/StaticDatePicker.test.tsx +++ b/packages/x-date-pickers/src/StaticDatePicker/tests/StaticDatePicker.test.tsx @@ -3,8 +3,7 @@ import { expect } from 'chai'; import { fireEvent, screen } from '@mui/internal-test-utils'; import { StaticDatePicker } from '@mui/x-date-pickers/StaticDatePicker'; import { createPickerRenderer, adapterToUse } from 'test/utils/pickers'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; describe('', () => { const { render } = createPickerRenderer({ clock: 'fake' }); @@ -42,22 +41,14 @@ describe('', () => { ); } - it('should take focus when `autoFocus=true`', function test() { - if (isJSDOM) { - this.skip(); - } - + testSkipIf(isJSDOM)('should take focus when `autoFocus=true`', () => { render(); const isInside = document.getElementById('pickerWrapper')?.contains(document.activeElement); expect(isInside).to.equal(true); }); - it('should not take focus when `autoFocus=false`', function test() { - if (isJSDOM) { - this.skip(); - } - + testSkipIf(isJSDOM)('should not take focus when `autoFocus=false`', () => { render(); const isInside = document.getElementById('pickerWrapper')?.contains(document.activeElement); diff --git a/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.test.tsx b/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.test.tsx index f3eb9f6c1597c..72cca57d935d7 100644 --- a/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.test.tsx +++ b/packages/x-date-pickers/src/StaticTimePicker/StaticTimePicker.test.tsx @@ -5,6 +5,7 @@ import { fireTouchChangedEvent, screen, within, fireEvent } from '@mui/internal- import { adapterToUse, createPickerRenderer, describeValidation } from 'test/utils/pickers'; import { StaticTimePicker } from '@mui/x-date-pickers/StaticTimePicker'; import { describeConformance } from 'test/utils/describeConformance'; +import { testSkipIf, hasTouchSupport } from 'test/utils/skipIf'; describe('', () => { const { render, clock } = createPickerRenderer({ @@ -35,106 +36,104 @@ describe('', () => { ], })); - it('should allow view modification, but not update value when `readOnly` prop is passed', function test() { - // Only run in supported browsers - if (typeof Touch === 'undefined') { - this.skip(); - } - const selectEvent = { - changedTouches: [ - { - clientX: 150, - clientY: 60, - }, - ], - }; - const onChange = spy(); - const onViewChange = spy(); - render( - , - ); - - // Can switch between views - fireEvent.click(screen.getByTestId('minutes')); - expect(onViewChange.callCount).to.equal(1); - - fireEvent.click(screen.getByTestId('hours')); - expect(onViewChange.callCount).to.equal(2); - - // Can not switch between meridiem - fireEvent.click(screen.getByRole('button', { name: /AM/i })); - expect(onChange.callCount).to.equal(0); - fireEvent.click(screen.getByRole('button', { name: /PM/i })); - expect(onChange.callCount).to.equal(0); - - // Can not set value - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', selectEvent); - expect(onChange.callCount).to.equal(0); - - // hours are not disabled - const hoursContainer = screen.getByRole('listbox'); - const hours = within(hoursContainer).getAllByRole('option'); - const disabledHours = hours.filter((day) => day.getAttribute('aria-disabled') === 'true'); - - expect(hours.length).to.equal(12); - expect(disabledHours.length).to.equal(0); - }); - - it('should allow switching between views and display disabled options when `disabled` prop is passed', function test() { - // Only run in supported browsers - if (typeof Touch === 'undefined') { - this.skip(); - } - const selectEvent = { - changedTouches: [ - { - clientX: 150, - clientY: 60, - }, - ], - }; - const onChange = spy(); - const onViewChange = spy(); - render( - , - ); - - // Can switch between views - fireEvent.click(screen.getByTestId('minutes')); - expect(onViewChange.callCount).to.equal(1); - - fireEvent.click(screen.getByTestId('hours')); - expect(onViewChange.callCount).to.equal(2); - - // Can not switch between meridiem - fireEvent.click(screen.getByRole('button', { name: /AM/i })); - expect(onChange.callCount).to.equal(0); - fireEvent.click(screen.getByRole('button', { name: /PM/i })); - expect(onChange.callCount).to.equal(0); - - // Can not set value - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', selectEvent); - expect(onChange.callCount).to.equal(0); - - // hours are disabled - const hoursContainer = screen.getByRole('listbox'); - const hours = within(hoursContainer).getAllByRole('option'); - const disabledHours = hours.filter((hour) => hour.getAttribute('aria-disabled') === 'true'); - expect(hours.length).to.equal(12); - expect(disabledHours.length).to.equal(12); - - // meridiem are disabled - expect(screen.getByRole('button', { name: /AM/i })).to.have.attribute('disabled'); - expect(screen.getByRole('button', { name: /PM/i })).to.have.attribute('disabled'); - }); + testSkipIf(!hasTouchSupport)( + 'should allow view modification, but not update value when `readOnly` prop is passed', + () => { + const selectEvent = { + changedTouches: [ + { + clientX: 150, + clientY: 60, + }, + ], + }; + const onChange = spy(); + const onViewChange = spy(); + render( + , + ); + + // Can switch between views + fireEvent.click(screen.getByTestId('minutes')); + expect(onViewChange.callCount).to.equal(1); + + fireEvent.click(screen.getByTestId('hours')); + expect(onViewChange.callCount).to.equal(2); + + // Can not switch between meridiem + fireEvent.click(screen.getByRole('button', { name: /AM/i })); + expect(onChange.callCount).to.equal(0); + fireEvent.click(screen.getByRole('button', { name: /PM/i })); + expect(onChange.callCount).to.equal(0); + + // Can not set value + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', selectEvent); + expect(onChange.callCount).to.equal(0); + + // hours are not disabled + const hoursContainer = screen.getByRole('listbox'); + const hours = within(hoursContainer).getAllByRole('option'); + const disabledHours = hours.filter((day) => day.getAttribute('aria-disabled') === 'true'); + + expect(hours.length).to.equal(12); + expect(disabledHours.length).to.equal(0); + }, + ); + + testSkipIf(!hasTouchSupport)( + 'should allow switching between views and display disabled options when `disabled` prop is passed', + () => { + const selectEvent = { + changedTouches: [ + { + clientX: 150, + clientY: 60, + }, + ], + }; + const onChange = spy(); + const onViewChange = spy(); + render( + , + ); + + // Can switch between views + fireEvent.click(screen.getByTestId('minutes')); + expect(onViewChange.callCount).to.equal(1); + + fireEvent.click(screen.getByTestId('hours')); + expect(onViewChange.callCount).to.equal(2); + + // Can not switch between meridiem + fireEvent.click(screen.getByRole('button', { name: /AM/i })); + expect(onChange.callCount).to.equal(0); + fireEvent.click(screen.getByRole('button', { name: /PM/i })); + expect(onChange.callCount).to.equal(0); + + // Can not set value + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', selectEvent); + expect(onChange.callCount).to.equal(0); + + // hours are disabled + const hoursContainer = screen.getByRole('listbox'); + const hours = within(hoursContainer).getAllByRole('option'); + const disabledHours = hours.filter((hour) => hour.getAttribute('aria-disabled') === 'true'); + expect(hours.length).to.equal(12); + expect(disabledHours.length).to.equal(12); + + // meridiem are disabled + expect(screen.getByRole('button', { name: /AM/i })).to.have.attribute('disabled'); + expect(screen.getByRole('button', { name: /PM/i })).to.have.attribute('disabled'); + }, + ); }); diff --git a/packages/x-date-pickers/src/TimeClock/tests/TimeClock.test.tsx b/packages/x-date-pickers/src/TimeClock/tests/TimeClock.test.tsx index eb3dc73ff7ce1..3dcee962b564b 100644 --- a/packages/x-date-pickers/src/TimeClock/tests/TimeClock.test.tsx +++ b/packages/x-date-pickers/src/TimeClock/tests/TimeClock.test.tsx @@ -4,6 +4,7 @@ import { spy } from 'sinon'; import { fireEvent, fireTouchChangedEvent, screen, within } from '@mui/internal-test-utils'; import { TimeClock } from '@mui/x-date-pickers/TimeClock'; import { createPickerRenderer, adapterToUse, timeClockHandler } from 'test/utils/pickers'; +import { testSkipIf, hasTouchSupport, describeSkipIf } from 'test/utils/skipIf'; describe('', () => { const { render } = createPickerRenderer(); @@ -220,69 +221,65 @@ describe('', () => { }); }); - it('should display options, but not update value when readOnly prop is passed', function test() { - // Only run in supported browsers - if (typeof Touch === 'undefined') { - this.skip(); - } - const selectEvent = { - changedTouches: [ - { - clientX: 150, - clientY: 60, - }, - ], - }; - const onChangeMock = spy(); - render(); - - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchstart', selectEvent); - expect(onChangeMock.callCount).to.equal(0); + testSkipIf(!hasTouchSupport)( + 'should display options, but not update value when readOnly prop is passed', + () => { + const selectEvent = { + changedTouches: [ + { + clientX: 150, + clientY: 60, + }, + ], + }; + const onChangeMock = spy(); + render( + , + ); - // hours are not disabled - const hoursContainer = screen.getByRole('listbox'); - const hours = within(hoursContainer).getAllByRole('option'); - const disabledHours = hours.filter((hour) => hour.getAttribute('aria-disabled') === 'true'); + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchstart', selectEvent); + expect(onChangeMock.callCount).to.equal(0); - expect(hours.length).to.equal(12); - expect(disabledHours.length).to.equal(0); - }); + // hours are not disabled + const hoursContainer = screen.getByRole('listbox'); + const hours = within(hoursContainer).getAllByRole('option'); + const disabledHours = hours.filter((hour) => hour.getAttribute('aria-disabled') === 'true'); - it('should display disabled options when disabled prop is passed', function test() { - // Only run in supported browsers - if (typeof Touch === 'undefined') { - this.skip(); - } - const selectEvent = { - changedTouches: [ - { - clientX: 150, - clientY: 60, - }, - ], - }; - const onChangeMock = spy(); - render(); + expect(hours.length).to.equal(12); + expect(disabledHours.length).to.equal(0); + }, + ); - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchstart', selectEvent); - expect(onChangeMock.callCount).to.equal(0); + testSkipIf(!hasTouchSupport)( + 'should display disabled options when disabled prop is passed', + () => { + const selectEvent = { + changedTouches: [ + { + clientX: 150, + clientY: 60, + }, + ], + }; + const onChangeMock = spy(); + render( + , + ); - // hours are disabled - const hoursContainer = screen.getByRole('listbox'); - const hours = within(hoursContainer).getAllByRole('option'); - const disabledHours = hours.filter((hour) => hour.getAttribute('aria-disabled') === 'true'); + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchstart', selectEvent); + expect(onChangeMock.callCount).to.equal(0); - expect(hours.length).to.equal(12); - expect(disabledHours.length).to.equal(12); - }); + // hours are disabled + const hoursContainer = screen.getByRole('listbox'); + const hours = within(hoursContainer).getAllByRole('option'); + const disabledHours = hours.filter((hour) => hour.getAttribute('aria-disabled') === 'true'); - describe('Time validation on touch ', () => { - before(function beforeHook() { - if (typeof window.Touch === 'undefined' || typeof window.TouchEvent === 'undefined') { - this.skip(); - } - }); + expect(hours.length).to.equal(12); + expect(disabledHours.length).to.equal(12); + }, + ); + describeSkipIf(!hasTouchSupport)('Time validation on touch ', () => { const clockTouchEvent = { '13:--': { changedTouches: [ diff --git a/packages/x-date-pickers/src/TimeClock/tests/timezone.TimeClock.test.tsx b/packages/x-date-pickers/src/TimeClock/tests/timezone.TimeClock.test.tsx index d135cd767dad1..5bacfaed27e37 100644 --- a/packages/x-date-pickers/src/TimeClock/tests/timezone.TimeClock.test.tsx +++ b/packages/x-date-pickers/src/TimeClock/tests/timezone.TimeClock.test.tsx @@ -9,94 +9,95 @@ import { getDateOffset, describeAdapters, } from 'test/utils/pickers'; +import { describeSkipIf } from 'test/utils/skipIf'; const TIMEZONE_TO_TEST = ['UTC', 'system', 'America/New_York']; describe(' - Timezone', () => { describeAdapters('Timezone prop', TimeClock, ({ adapter, render }) => { - if (!adapter.isTimezoneCompatible) { - return; - } - - it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { - const onChange = spy(); - render(); - - const hourClockEvent = getClockTouchEvent(8, '12hours'); - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', hourClockEvent); - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchend', hourClockEvent); - - const expectedDate = adapter.setHours(adapter.date(), 8); - - // Check the `onChange` value (uses default timezone, for example: UTC, see TZ env variable) - const actualDate = onChange.lastCall.firstArg; - - // On dayjs, we are not able to know if a date is UTC because it's the system timezone or because it was created as UTC. - // In a real world scenario, this should probably never occur. - expect(adapter.getTimezone(actualDate)).to.equal(adapter.lib === 'dayjs' ? 'UTC' : 'system'); - expect(actualDate).toEqualDateTime(expectedDate); - }); - - TIMEZONE_TO_TEST.forEach((timezone) => { - describe(`Timezone: ${timezone}`, () => { - it('should use timezone prop for onChange when no value is provided', () => { - const onChange = spy(); - render(); - - const hourClockEvent = getClockTouchEvent(8, '12hours'); - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', hourClockEvent); - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchend', hourClockEvent); - - const expectedDate = adapter.setHours( - adapter.startOfDay(adapter.date(undefined, timezone)), - 8, - ); - - // Check the `onChange` value (uses timezone prop) - const actualDate = onChange.lastCall.firstArg; - expect(adapter.getTimezone(actualDate)).to.equal( - adapter.lib === 'dayjs' && timezone === 'system' ? 'UTC' : timezone, - ); - expect(actualDate).toEqualDateTime(expectedDate); - }); - - it('should use timezone prop for rendering and value timezone for onChange when a value is provided', () => { - const onChange = spy(); - const value = adapter.date('2022-04-17T04:30', timezone); - - render( - , - ); - - const renderedHourBefore = getTimeClockValue(); - const offsetDiff = - getDateOffset(adapter, adapter.setTimezone(value, 'America/Chicago')) - - getDateOffset(adapter, value); - - expect(renderedHourBefore).to.equal( - (adapter.getHours(value) + offsetDiff / 60 + 12) % 12, - ); - - const hourClockEvent = getClockTouchEvent(8, '12hours'); - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', hourClockEvent); - fireTouchChangedEvent(screen.getByTestId('clock'), 'touchend', hourClockEvent); - - const actualDate = onChange.lastCall.firstArg; - - const renderedHourAfter = getTimeClockValue(); - expect(renderedHourAfter).to.equal( - (adapter.getHours(actualDate) + offsetDiff / 60 + 12) % 12, - ); - - const expectedDate = adapter.addHours(value, renderedHourAfter - renderedHourBefore); + describeSkipIf(!adapter.isTimezoneCompatible)('timezoneCompatible', () => { + it('should use default timezone for rendering and onChange when no value and no timezone prop are provided', () => { + const onChange = spy(); + render(); + + const hourClockEvent = getClockTouchEvent(8, '12hours'); + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', hourClockEvent); + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchend', hourClockEvent); + + const expectedDate = adapter.setHours(adapter.date(), 8); + + // Check the `onChange` value (uses default timezone, for example: UTC, see TZ env variable) + const actualDate = onChange.lastCall.firstArg; + + // On dayjs, we are not able to know if a date is UTC because it's the system timezone or because it was created as UTC. + // In a real world scenario, this should probably never occur. + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' ? 'UTC' : 'system', + ); + expect(actualDate).toEqualDateTime(expectedDate); + }); - expect(adapter.getTimezone(actualDate)).to.equal(timezone); - expect(actualDate).toEqualDateTime(expectedDate); + TIMEZONE_TO_TEST.forEach((timezone) => { + describe(`Timezone: ${timezone}`, () => { + it('should use timezone prop for onChange when no value is provided', () => { + const onChange = spy(); + render(); + + const hourClockEvent = getClockTouchEvent(8, '12hours'); + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', hourClockEvent); + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchend', hourClockEvent); + + const expectedDate = adapter.setHours( + adapter.startOfDay(adapter.date(undefined, timezone)), + 8, + ); + + // Check the `onChange` value (uses timezone prop) + const actualDate = onChange.lastCall.firstArg; + expect(adapter.getTimezone(actualDate)).to.equal( + adapter.lib === 'dayjs' && timezone === 'system' ? 'UTC' : timezone, + ); + expect(actualDate).toEqualDateTime(expectedDate); + }); + + it('should use timezone prop for rendering and value timezone for onChange when a value is provided', () => { + const onChange = spy(); + const value = adapter.date('2022-04-17T04:30', timezone); + + render( + , + ); + + const renderedHourBefore = getTimeClockValue(); + const offsetDiff = + getDateOffset(adapter, adapter.setTimezone(value, 'America/Chicago')) - + getDateOffset(adapter, value); + + expect(renderedHourBefore).to.equal( + (adapter.getHours(value) + offsetDiff / 60 + 12) % 12, + ); + + const hourClockEvent = getClockTouchEvent(8, '12hours'); + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchmove', hourClockEvent); + fireTouchChangedEvent(screen.getByTestId('clock'), 'touchend', hourClockEvent); + + const actualDate = onChange.lastCall.firstArg; + + const renderedHourAfter = getTimeClockValue(); + expect(renderedHourAfter).to.equal( + (adapter.getHours(actualDate) + offsetDiff / 60 + 12) % 12, + ); + + const expectedDate = adapter.addHours(value, renderedHourAfter - renderedHourBefore); + + expect(adapter.getTimezone(actualDate)).to.equal(timezone); + expect(actualDate).toEqualDateTime(expectedDate); + }); }); }); }); diff --git a/packages/x-license/src/useLicenseVerifier/useLicenseVerifier.test.tsx b/packages/x-license/src/useLicenseVerifier/useLicenseVerifier.test.tsx index 1cfd9ab49dd68..92742bd3c5522 100644 --- a/packages/x-license/src/useLicenseVerifier/useLicenseVerifier.test.tsx +++ b/packages/x-license/src/useLicenseVerifier/useLicenseVerifier.test.tsx @@ -8,6 +8,7 @@ import { Unstable_LicenseInfoProvider as LicenseInfoProvider, MuiCommercialPackageName, } from '@mui/x-license'; +import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; import { sharedLicenseStatuses } from './useLicenseVerifier'; import { generateReleaseInfo } from '../verifyLicense'; @@ -20,16 +21,13 @@ function TestComponent(props: { packageName?: MuiCommercialPackageName }) { return
Status: {licenseStatus.status}
; } -describe('useLicenseVerifier', function test() { - // Can't change the process.env.NODE_ENV in Karma - if (!/jsdom/.test(window.navigator.userAgent)) { - return; - } - +// Can't change the process.env.NODE_ENV in Karma +describeSkipIf(!isJSDOM)('useLicenseVerifier', function test() { const { render } = createRenderer(); let env: any; + // eslint-disable-next-line mocha/no-top-level-hooks beforeEach(() => { env = process.env.NODE_ENV; // Avoid Karma "Invalid left-hand side in assignment" SyntaxError @@ -37,6 +35,7 @@ describe('useLicenseVerifier', function test() { process.env['NODE_' + 'ENV'] = 'test'; }); + // eslint-disable-next-line mocha/no-top-level-hooks afterEach(() => { // Avoid Karma "Invalid left-hand side in assignment" SyntaxError // eslint-disable-next-line no-useless-concat diff --git a/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.test.tsx b/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.test.tsx index cfd604e7ac70a..f895e7ca23df5 100644 --- a/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.test.tsx +++ b/packages/x-tree-view-pro/src/internals/plugins/useTreeViewItemsReordering/useTreeViewItemsReordering.test.tsx @@ -8,6 +8,7 @@ import { UseTreeViewExpansionSignature, UseTreeViewItemsSignature, } from '@mui/x-tree-view/internals'; +import { describeSkipIf } from 'test/utils/skipIf'; import { chooseActionToApply } from './useTreeViewItemsReordering.utils'; import { TreeViewItemItemReorderingValidActions } from './useTreeViewItemsReordering.types'; @@ -58,169 +59,169 @@ const buildTreeViewDragInteractions = (dataTransfer: DataTransfer) => { describeTreeView< [UseTreeViewItemsReorderingSignature, UseTreeViewItemsSignature, UseTreeViewExpansionSignature] >('useTreeViewItemsReordering', ({ render, treeViewComponentName }) => { - if (treeViewComponentName === 'SimpleTreeView' || treeViewComponentName === 'RichTreeView') { - return; - } - - let dragEvents: ReturnType; - // eslint-disable-next-line mocha/no-top-level-hooks - beforeEach(() => { - const dataTransfer = new MockedDataTransfer(); - dragEvents = buildTreeViewDragInteractions(dataTransfer); - }); - - // eslint-disable-next-line mocha/no-top-level-hooks - afterEach(() => { - dragEvents = {} as typeof dragEvents; - }); - - describe('itemReordering prop', () => { - it('should allow to drag and drop items when props.itemsReordering={true}', () => { - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1' }, { id: '2' }, { id: '3' }], - itemsReordering: true, - }); + describeSkipIf( + treeViewComponentName === 'SimpleTreeView' || treeViewComponentName === 'RichTreeView', + )('reordering', () => { + let dragEvents: ReturnType; + // eslint-disable-next-line mocha/no-top-level-hooks + beforeEach(() => { + const dataTransfer = new MockedDataTransfer(); + dragEvents = buildTreeViewDragInteractions(dataTransfer); + }); - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(view.getItemIdTree()).to.deep.equal([ - { id: '2', children: [{ id: '1' }] }, - { id: '3' }, - ]); + // eslint-disable-next-line mocha/no-top-level-hooks + afterEach(() => { + dragEvents = {} as typeof dragEvents; }); - it('should not allow to drag and drop items when props.itemsReordering={false}', () => { - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1' }, { id: '2' }, { id: '3' }], - itemsReordering: false, + describe('itemReordering prop', () => { + it('should allow to drag and drop items when props.itemsReordering={true}', () => { + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: true, + }); + + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([ + { id: '2', children: [{ id: '1' }] }, + { id: '3' }, + ]); }); - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); - }); + it('should not allow to drag and drop items when props.itemsReordering={false}', () => { + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: false, + }); - it('should not allow to drag and drop items when props.itemsReordering is not defined', () => { - const view = render({ - items: [{ id: '1' }, { id: '2' }, { id: '3' }], + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); }); - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); - }); + it('should not allow to drag and drop items when props.itemsReordering is not defined', () => { + const view = render({ + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + }); - it('should allow to expand the new parent of the dragged item when it was not expandable before', () => { - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], - itemsReordering: true, - defaultExpandedItems: ['1'], + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); }); - dragEvents.fullDragSequence(view.getItemRoot('1.1'), view.getItemContent('2')); + it('should allow to expand the new parent of the dragged item when it was not expandable before', () => { + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + itemsReordering: true, + defaultExpandedItems: ['1'], + }); - fireEvent.focus(view.getItemRoot('2')); - fireEvent.keyDown(view.getItemRoot('2'), { key: 'Enter' }); + dragEvents.fullDragSequence(view.getItemRoot('1.1'), view.getItemContent('2')); - expect(view.getItemIdTree()).to.deep.equal([ - { id: '1' }, - { id: '2', children: [{ id: '1.1' }] }, - ]); - }); - }); + fireEvent.focus(view.getItemRoot('2')); + fireEvent.keyDown(view.getItemRoot('2'), { key: 'Enter' }); - describe('onItemPositionChange prop', () => { - it('should call onItemPositionChange when an item is moved', () => { - const onItemPositionChange = spy(); - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1' }, { id: '2' }, { id: '3' }], - itemsReordering: true, - onItemPositionChange, + expect(view.getItemIdTree()).to.deep.equal([ + { id: '1' }, + { id: '2', children: [{ id: '1.1' }] }, + ]); }); - - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(onItemPositionChange.callCount).to.equal(1); - expect(onItemPositionChange.lastCall.firstArg).to.deep.equal({ - itemId: '1', - oldPosition: { parentId: null, index: 0 }, - newPosition: { parentId: '2', index: 0 }, - }); - }); - }); - - describe('isItemReorderable prop', () => { - it('should not allow to drag an item when isItemReorderable returns false', () => { - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1' }, { id: '2' }, { id: '3' }], - itemsReordering: true, - isItemReorderable: () => false, - }); - - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); }); - it('should allow to drag an item when isItemReorderable returns true', () => { - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1' }, { id: '2' }, { id: '3' }], - itemsReordering: true, - isItemReorderable: () => true, + describe('onItemPositionChange prop', () => { + it('should call onItemPositionChange when an item is moved', () => { + const onItemPositionChange = spy(); + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: true, + onItemPositionChange, + }); + + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(onItemPositionChange.callCount).to.equal(1); + expect(onItemPositionChange.lastCall.firstArg).to.deep.equal({ + itemId: '1', + oldPosition: { parentId: null, index: 0 }, + newPosition: { parentId: '2', index: 0 }, + }); }); - - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(view.getItemIdTree()).to.deep.equal([ - { id: '2', children: [{ id: '1' }] }, - { id: '3' }, - ]); }); - }); - describe('canMoveItemToNewPosition prop', () => { - it('should call canMoveItemToNewPosition with the correct parameters', () => { - const canMoveItemToNewPosition = spy(); - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1' }, { id: '2' }, { id: '3' }], - itemsReordering: true, - canMoveItemToNewPosition, + describe('isItemReorderable prop', () => { + it('should not allow to drag an item when isItemReorderable returns false', () => { + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: true, + isItemReorderable: () => false, + }); + + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); }); - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(canMoveItemToNewPosition.lastCall.firstArg).to.deep.equal({ - itemId: '1', - oldPosition: { parentId: null, index: 0 }, - newPosition: { parentId: null, index: 1 }, + it('should allow to drag an item when isItemReorderable returns true', () => { + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: true, + isItemReorderable: () => true, + }); + + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([ + { id: '2', children: [{ id: '1' }] }, + { id: '3' }, + ]); }); }); - it('should not allow to drop an item when canMoveItemToNewPosition returns false', () => { - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1' }, { id: '2' }, { id: '3' }], - itemsReordering: true, - canMoveItemToNewPosition: () => false, + describe('canMoveItemToNewPosition prop', () => { + it('should call canMoveItemToNewPosition with the correct parameters', () => { + const canMoveItemToNewPosition = spy(); + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: true, + canMoveItemToNewPosition, + }); + + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(canMoveItemToNewPosition.lastCall.firstArg).to.deep.equal({ + itemId: '1', + oldPosition: { parentId: null, index: 0 }, + newPosition: { parentId: null, index: 1 }, + }); }); - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); - }); + it('should not allow to drop an item when canMoveItemToNewPosition returns false', () => { + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: true, + canMoveItemToNewPosition: () => false, + }); - it('should allow to drop an item when canMoveItemToNewPosition returns true', () => { - const view = render({ - experimentalFeatures: { itemsReordering: true }, - items: [{ id: '1' }, { id: '2' }, { id: '3' }], - itemsReordering: true, - canMoveItemToNewPosition: () => true, + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([{ id: '1' }, { id: '2' }, { id: '3' }]); }); - dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); - expect(view.getItemIdTree()).to.deep.equal([ - { id: '2', children: [{ id: '1' }] }, - { id: '3' }, - ]); + it('should allow to drop an item when canMoveItemToNewPosition returns true', () => { + const view = render({ + experimentalFeatures: { itemsReordering: true }, + items: [{ id: '1' }, { id: '2' }, { id: '3' }], + itemsReordering: true, + canMoveItemToNewPosition: () => true, + }); + + dragEvents.fullDragSequence(view.getItemRoot('1'), view.getItemContent('2')); + expect(view.getItemIdTree()).to.deep.equal([ + { id: '2', children: [{ id: '1' }] }, + { id: '3' }, + ]); + }); }); }); }); diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx index b355540aa30c0..e98a778129053 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx @@ -205,7 +205,7 @@ describeTreeView<[UseTreeViewExpansionSignature]>('useTreeViewExpansion plugin', expect(view.isItemExpanded('1')).to.equal(true); }); - it('should be able to limit the expansion to the icon', function test() { + it('should be able to limit the expansion to the icon', () => { const CustomTreeItem = React.forwardRef(function MyTreeItem( props: TreeItemProps, ref: React.Ref, diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.test.tsx index 625a54a3a8c15..b3e8461bdd0d0 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewItems/useTreeViewItems.test.tsx @@ -9,20 +9,18 @@ import { UseTreeViewSelectionSignature, } from '@mui/x-tree-view/internals'; import { TreeItemLabel } from '@mui/x-tree-view/TreeItem'; +import { describeSkipIf, testSkipIf, isJSDOM } from 'test/utils/skipIf'; describeTreeView< [UseTreeViewItemsSignature, UseTreeViewExpansionSignature, UseTreeViewSelectionSignature] >( 'useTreeViewItems plugin', ({ render, renderFromJSX, treeViewComponentName, TreeViewComponent, TreeItemComponent }) => { - it('should throw an error when two items have the same ID', function test() { - // TODO is this fixed? - if (!/jsdom/.test(window.navigator.userAgent)) { - // can't catch render errors in the browser for unknown reason - // tried try-catch + error boundary + window onError preventDefault - this.skip(); - } + const isRichTreeView = treeViewComponentName.startsWith('RichTreeView'); + // can't catch render errors in the browser for unknown reason + // tried try-catch + error boundary + window onError preventDefault + testSkipIf(!isJSDOM)('should throw an error when two items have the same ID', () => { if (treeViewComponentName === 'SimpleTreeView') { expect(() => render({ items: [{ id: '1' }, { id: '1' }], withErrorBoundary: true }), @@ -47,12 +45,8 @@ describeTreeView< } }); - it('should be able to use a custom id attribute', function test() { - // For now, only SimpleTreeView can use custom id attributes - if (treeViewComponentName.startsWith('RichTreeView')) { - this.skip(); - } - + // For now, only SimpleTreeView can use custom id attributes + testSkipIf(isRichTreeView)('should be able to use a custom id attribute', () => { const view = render({ items: [{ id: '1' }], slotProps: { @@ -120,55 +114,52 @@ describeTreeView< expect(view.getItemRoot('1')).not.to.have.attribute('aria-expanded'); }); - it('should mark an item as not expandable if it has only empty conditional arrays', function test() { - if (treeViewComponentName.startsWith('RichTreeView')) { - this.skip(); - } - - const view = renderFromJSX( - - - {[]} - {[]} - - , - ); - - expect(view.isItemExpanded('1')).to.equal(false); - }); - - it('should mark an item as expandable if it has two array as children, one of which is empty (SimpleTreeView only)', function test() { - if (treeViewComponentName.startsWith('RichTreeView')) { - this.skip(); - } - - const view = renderFromJSX( - - - {[]} - {[]} - - , - ); - - expect(view.isItemExpanded('1')).to.equal(true); - }); - - it('should mark an item as not expandable if it has one array containing an empty array as a children (SimpleTreeView only)', function test() { - if (treeViewComponentName.startsWith('RichTreeView')) { - this.skip(); - } - - const view = renderFromJSX( - - - {[[]]} - - , - ); - - expect(view.isItemExpanded('1')).to.equal(false); - }); + testSkipIf(isRichTreeView)( + 'should mark an item as not expandable if it has only empty conditional arrays', + () => { + const view = renderFromJSX( + + + {[]} + {[]} + + , + ); + + expect(view.isItemExpanded('1')).to.equal(false); + }, + ); + + testSkipIf(isRichTreeView)( + 'should mark an item as expandable if it has two array as children, one of which is empty (SimpleTreeView only)', + () => { + const view = renderFromJSX( + + + {[]} + {[]} + + , + ); + + expect(view.isItemExpanded('1')).to.equal(true); + }, + ); + + testSkipIf(isRichTreeView)( + 'should mark an item as not expandable if it has one array containing an empty array as a children (SimpleTreeView only)', + () => { + const view = renderFromJSX( + + + {[[]]} + + , + ); + + expect(view.isItemExpanded('1')).to.equal(false); + }, + ); }); describe('disabled prop', () => { @@ -226,105 +217,97 @@ describeTreeView< }); describe('Memoization (Rich Tree View only)', () => { - it('should not re-render any children when the Tree View re-renders (flat tree)', function test() { - if (!treeViewComponentName.startsWith('RichTreeView')) { - this.skip(); - } - - const spyLabel = spy((props) => ); - const view = render({ - items: Array.from({ length: 10 }, (_, i) => ({ id: i.toString() })), - slotProps: { item: { slots: { label: spyLabel } } }, - }); - - spyLabel.resetHistory(); - - view.setProps({ onClick: () => {} }); - - const renders = spyLabel.getCalls().map((call) => call.args[0].children); - expect(renders).to.deep.equal([]); - }); + testSkipIf(!isRichTreeView)( + 'should not re-render any children when the Tree View re-renders (flat tree)', + () => { + const spyLabel = spy((props) => ); + const view = render({ + items: Array.from({ length: 10 }, (_, i) => ({ id: i.toString() })), + slotProps: { item: { slots: { label: spyLabel } } }, + }); - it('should not re-render every children when updating the state on an item (flat tree)', function test() { - if (!treeViewComponentName.startsWith('RichTreeView')) { - this.skip(); - } + spyLabel.resetHistory(); - const spyLabel = spy((props) => ); - const view = render({ - items: Array.from({ length: 10 }, (_, i) => ({ id: i.toString() })), - selectedItems: [], - slotProps: { item: { slots: { label: spyLabel } } }, - }); + view.setProps({ onClick: () => {} }); - spyLabel.resetHistory(); + const renders = spyLabel.getCalls().map((call) => call.args[0].children); + expect(renders).to.deep.equal([]); + }, + ); - view.setProps({ selectedItems: ['1'] }); + testSkipIf(!isRichTreeView)( + 'should not re-render every children when updating the state on an item (flat tree)', + () => { + const spyLabel = spy((props) => ); + const view = render({ + items: Array.from({ length: 10 }, (_, i) => ({ id: i.toString() })), + selectedItems: [], + slotProps: { item: { slots: { label: spyLabel } } }, + }); - const renders = spyLabel.getCalls().map((call) => call.args[0].children); + spyLabel.resetHistory(); - // 2 renders of the 1st item to remove to tabIndex={0} - // 2 renders of the selected item to change its visual state - expect(renders).to.deep.equal(['0', '0', '1', '1']); - }); + view.setProps({ selectedItems: ['1'] }); - it('should not re-render any children when the Tree View re-renders (nested tree)', function test() { - if (!treeViewComponentName.startsWith('RichTreeView')) { - this.skip(); - } + const renders = spyLabel.getCalls().map((call) => call.args[0].children); - const spyLabel = spy((props) => ); - const view = render({ - items: Array.from({ length: 5 }, (_, i) => ({ - id: i.toString(), - children: Array.from({ length: 5 }, (_el, j) => ({ id: `${i}.${j}` })), - })), - slotProps: { item: { slots: { label: spyLabel } } }, - }); + // 2 renders of the 1st item to remove to tabIndex={0} + // 2 renders of the selected item to change its visual state + expect(renders).to.deep.equal(['0', '0', '1', '1']); + }, + ); - spyLabel.resetHistory(); + testSkipIf(!isRichTreeView)( + 'should not re-render any children when the Tree View re-renders (nested tree)', + () => { + const spyLabel = spy((props) => ); + const view = render({ + items: Array.from({ length: 5 }, (_, i) => ({ + id: i.toString(), + children: Array.from({ length: 5 }, (_el, j) => ({ id: `${i}.${j}` })), + })), + slotProps: { item: { slots: { label: spyLabel } } }, + }); - view.setProps({ onClick: () => {} }); + spyLabel.resetHistory(); - const renders = spyLabel.getCalls().map((call) => call.args[0].children); - expect(renders).to.deep.equal([]); - }); + view.setProps({ onClick: () => {} }); - it('should not re-render every children when updating the state on an item (nested tree)', function test() { - if (!treeViewComponentName.startsWith('RichTreeView')) { - this.skip(); - } + const renders = spyLabel.getCalls().map((call) => call.args[0].children); + expect(renders).to.deep.equal([]); + }, + ); - const spyLabel = spy((props) => ); - const view = render({ - items: Array.from({ length: 5 }, (_, i) => ({ - id: i.toString(), - children: Array.from({ length: 5 }, (_el, j) => ({ id: `${i}.${j}` })), - })), - defaultExpandedItems: Array.from({ length: 5 }, (_, i) => i.toString()), - selectedItems: [], - slotProps: { item: { slots: { label: spyLabel } } }, - }); + testSkipIf(!isRichTreeView)( + 'should not re-render every children when updating the state on an item (nested tree)', + () => { + const spyLabel = spy((props) => ); + const view = render({ + items: Array.from({ length: 5 }, (_, i) => ({ + id: i.toString(), + children: Array.from({ length: 5 }, (_el, j) => ({ id: `${i}.${j}` })), + })), + defaultExpandedItems: Array.from({ length: 5 }, (_, i) => i.toString()), + selectedItems: [], + slotProps: { item: { slots: { label: spyLabel } } }, + }); - spyLabel.resetHistory(); + spyLabel.resetHistory(); - view.setProps({ selectedItems: ['1'] }); + view.setProps({ selectedItems: ['1'] }); - const renders = spyLabel.getCalls().map((call) => call.args[0].children); + const renders = spyLabel.getCalls().map((call) => call.args[0].children); - // 2 renders of the 1st item to remove to tabIndex={0} - // 2 renders of the selected item to change its visual state - expect(renders).to.deep.equal(['0', '0', '1', '1']); - }); + // 2 renders of the 1st item to remove to tabIndex={0} + // 2 renders of the selected item to change its visual state + expect(renders).to.deep.equal(['0', '0', '1', '1']); + }, + ); }); describe('API methods', () => { - describe('getItem', () => { - // This method is only usable with Rich Tree View components - if (treeViewComponentName === 'SimpleTreeView') { - return; - } - + // This method is only usable with Rich Tree View components + describeSkipIf(treeViewComponentName === 'SimpleTreeView')('getItem', () => { it('should return the tree', () => { const view = render({ items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], @@ -376,74 +359,83 @@ describeTreeView< }); }); - describe('getItemTree', () => { - // This method is only usable with Rich Tree View components - if (treeViewComponentName === 'SimpleTreeView') { - return; - } - - it('should return the tree', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + // This method is only usable with Rich Tree View components + describeSkipIf(treeViewComponentName === 'SimpleTreeView')( + 'getItemTree with RichTreeView', + () => { + // eslint-disable-next-line mocha/no-identical-title + it('should return the tree', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + }); + + expect(view.apiRef.current.getItemTree()).to.deep.equal([ + { id: '1', children: [{ id: '1.1' }] }, + { id: '2' }, + ]); }); - expect(view.apiRef.current.getItemTree()).to.deep.equal([ - { id: '1', children: [{ id: '1.1' }] }, - { id: '2' }, - ]); - }); + // eslint-disable-next-line mocha/no-identical-title + it('should have up to date tree when props.items changes', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + }); - it('should have up to date tree when props.items changes', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], - }); - - view.setItems([{ id: '1' }, { id: '2' }]); + view.setItems([{ id: '1' }, { id: '2' }]); - expect(view.apiRef.current.getItemTree()).to.deep.equal([{ id: '1' }, { id: '2' }]); - }); - - it('should contain custom item properties', () => { - const view = render({ - items: [{ id: '1', customProp: 'foo' }], + expect(view.apiRef.current.getItemTree()).to.deep.equal([{ id: '1' }, { id: '2' }]); }); - expect(view.apiRef.current.getItemTree()).to.deep.equal([{ id: '1', customProp: 'foo' }]); - }); - }); + // eslint-disable-next-line mocha/no-identical-title + it('should contain custom item properties', () => { + const view = render({ + items: [{ id: '1', customProp: 'foo' }], + }); - describe('getItemOrderedChildrenIds', () => { - // This method is only usable with Rich Tree View components - if (treeViewComponentName === 'SimpleTreeView') { - return; - } - - it('should return the children of an item in their rendering order', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }, { id: '1.2' }] }], + expect(view.apiRef.current.getItemTree()).to.deep.equal([ + { id: '1', customProp: 'foo' }, + ]); }); - - expect(view.apiRef.current.getItemOrderedChildrenIds('1')).to.deep.equal(['1.1', '1.2']); - }); - - it('should work for the root items', () => { - const view = render({ - items: [{ id: '1' }, { id: '2' }], + }, + ); + + // This method is only usable with Rich Tree View components + describeSkipIf(treeViewComponentName === 'SimpleTreeView')( + 'getItemOrderedChildrenIds', + () => { + it('should return the children of an item in their rendering order', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }, { id: '1.2' }] }], + }); + + expect(view.apiRef.current.getItemOrderedChildrenIds('1')).to.deep.equal([ + '1.1', + '1.2', + ]); }); - expect(view.apiRef.current.getItemOrderedChildrenIds(null)).to.deep.equal(['1', '2']); - }); + it('should work for the root items', () => { + const view = render({ + items: [{ id: '1' }, { id: '2' }], + }); - it('should have up to date children when props.items changes', () => { - const view = render({ - items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + expect(view.apiRef.current.getItemOrderedChildrenIds(null)).to.deep.equal(['1', '2']); }); - view.setItems([{ id: '1', children: [{ id: '1.1' }, { id: '1.2' }] }]); + it('should have up to date children when props.items changes', () => { + const view = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }, { id: '2' }], + }); - expect(view.apiRef.current.getItemOrderedChildrenIds('1')).to.deep.equal(['1.1', '1.2']); - }); - }); + view.setItems([{ id: '1', children: [{ id: '1.1' }, { id: '1.2' }] }]); + + expect(view.apiRef.current.getItemOrderedChildrenIds('1')).to.deep.equal([ + '1.1', + '1.2', + ]); + }); + }, + ); }); }, ); diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.test.tsx index 2db89b9501b1c..2540e108c0a10 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewKeyboardNavigation/useTreeViewKeyboardNavigation.test.tsx @@ -9,6 +9,7 @@ import { UseTreeViewKeyboardNavigationSignature, UseTreeViewSelectionSignature, } from '@mui/x-tree-view/internals'; +import { testSkipIf } from 'test/utils/skipIf'; describeTreeView< [ @@ -1169,35 +1170,34 @@ describeTreeView< expect(view.getFocusedItemId()).to.equal('1'); }); - it('should work with ReactElement label', function test() { - // Only the SimpleTreeView can have React Element labels. - if (treeViewComponentName !== 'SimpleTreeView') { - this.skip(); - } - - const view = render({ - items: [ - { id: '1', label: one }, - { id: '2', label: two }, - { id: '3', label: three }, - { id: '4', label: four }, - ], - }); + // Only the SimpleTreeView can have React Element labels. + testSkipIf(treeViewComponentName !== 'SimpleTreeView')( + 'should work with ReactElement label', + () => { + const view = render({ + items: [ + { id: '1', label: one }, + { id: '2', label: two }, + { id: '3', label: three }, + { id: '4', label: four }, + ], + }); - act(() => { - view.getItemRoot('1').focus(); - }); - expect(view.getFocusedItemId()).to.equal('1'); + act(() => { + view.getItemRoot('1').focus(); + }); + expect(view.getFocusedItemId()).to.equal('1'); - fireEvent.keyDown(view.getItemRoot('1'), { key: 't' }); - expect(view.getFocusedItemId()).to.equal('2'); + fireEvent.keyDown(view.getItemRoot('1'), { key: 't' }); + expect(view.getFocusedItemId()).to.equal('2'); - fireEvent.keyDown(view.getItemRoot('2'), { key: 'f' }); - expect(view.getFocusedItemId()).to.equal('4'); + fireEvent.keyDown(view.getItemRoot('2'), { key: 'f' }); + expect(view.getFocusedItemId()).to.equal('4'); - fireEvent.keyDown(view.getItemRoot('4'), { key: 'o' }); - expect(view.getFocusedItemId()).to.equal('1'); - }); + fireEvent.keyDown(view.getItemRoot('4'), { key: 'o' }); + expect(view.getFocusedItemId()).to.equal('1'); + }, + ); it('should work after adding / removing items', () => { const view = render({ diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.test.tsx index 375cc7a066a13..14c69e6604996 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewLabel/useTreeViewLabel.test.tsx @@ -2,17 +2,17 @@ import { expect } from 'chai'; import { act, fireEvent } from '@mui/internal-test-utils'; import { describeTreeView } from 'test/utils/tree-view/describeTreeView'; import { UseTreeViewLabelSignature } from '@mui/x-tree-view/internals'; +import { describeSkipIf } from 'test/utils/skipIf'; describeTreeView<[UseTreeViewLabelSignature]>( 'useTreeViewLabel plugin', ({ render, treeViewComponentName }) => { - describe('interaction', () => { + const isSimpleTreeView = treeViewComponentName.startsWith('SimpleTreeView'); + + describeSkipIf(isSimpleTreeView)('interaction', () => { describe('render labelInput when needed', () => { - it('should not render labelInput when double clicked if item is not editable', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + // This test is not relevant for the TreeItem component or the SimpleTreeView. + it('should not render labelInput when double clicked if item is not editable', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', editable: false }], @@ -26,11 +26,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabelInput('1')).to.equal(null); }); - it('should render labelInput when double clicked if item is editable', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should render labelInput when double clicked if item is editable', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', editable: true }], @@ -44,11 +40,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabelInput('1')).not.to.equal(null); }); - it('should not render label when double clicked if item is editable', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should not render label when double clicked if item is editable', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', editable: true }], @@ -62,11 +54,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabel('1')).to.equal(null); }); - it('should not render labelInput on Enter if item is not editable', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should not render labelInput on Enter if item is not editable', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', editable: false }], @@ -81,11 +69,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabel('1')).not.to.equal(null); }); - it('should render labelInput on Enter if item is editable', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should render labelInput on Enter if item is editable', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', editable: true }], @@ -99,11 +83,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabelInput('1')).not.to.equal(null); }); - it('should unmount labelInput after save', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should unmount labelInput after save', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', label: 'test', editable: true }], @@ -119,11 +99,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabel('1')).not.to.equal(null); }); - it('should unmount labelInput after cancel', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should unmount labelInput after cancel', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', label: 'test', editable: true }], @@ -141,11 +117,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( }); describe('labelInput value', () => { - it('should equal label value on first render', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should equal label value on first render', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', label: 'test', editable: true }], @@ -159,11 +131,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabelInput('1').value).to.equal('test'); }); - it('should save new value on Enter', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should save new value on Enter', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', label: 'test', editable: true }], @@ -179,11 +147,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabel('1').textContent).to.equal('new value'); }); - it('should hold new value on render after save', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should hold new value on render after save', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', label: 'test', editable: true }], @@ -200,11 +164,7 @@ describeTreeView<[UseTreeViewLabelSignature]>( expect(view.getItemLabelInput('1').value).to.equal('new value'); }); - it('should hold initial value on render after cancel', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + it('should hold initial value on render after cancel', () => { const view = render({ experimentalFeatures: { labelEditing: true }, items: [{ id: '1', label: 'test', editable: true }], @@ -223,12 +183,8 @@ describeTreeView<[UseTreeViewLabelSignature]>( }); }); }); - describe('updateItemLabel api method', () => { - it('should change the label value', function test() { - // This test is not relevant for the TreeItem component or the SimpleTreeView. - if (treeViewComponentName.startsWith('SimpleTreeView')) { - this.skip(); - } + describeSkipIf(isSimpleTreeView)('updateItemLabel api method', () => { + it('should change the label value', () => { const view = render({ items: [{ id: '1', label: 'test' }], }); diff --git a/test/utils/pickers/describeGregorianAdapter/testCalculations.ts b/test/utils/pickers/describeGregorianAdapter/testCalculations.ts index 8e698658e7e0a..505c8b374b1dd 100644 --- a/test/utils/pickers/describeGregorianAdapter/testCalculations.ts +++ b/test/utils/pickers/describeGregorianAdapter/testCalculations.ts @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { MuiPickersAdapter, PickersTimezone, PickerValidDate } from '@mui/x-date-pickers/models'; import { getDateOffset } from 'test/utils/pickers'; +import { testSkipIf } from 'test/utils/skipIf'; import { DescribeGregorianAdapterTestSuite } from './describeGregorianAdapter.types'; import { TEST_DATE_ISO_STRING, TEST_DATE_LOCALE_STRING } from './describeGregorianAdapter.utils'; @@ -92,11 +93,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ } }); - it('should parse undefined', () => { - if (adapterTZ.lib !== 'dayjs') { - return; - } - + testSkipIf(adapterTZ.lib !== 'dayjs')('should parse undefined', () => { if (adapter.isTimezoneCompatible) { const testTodayZone = (timezone: PickersTimezone) => { const dateWithZone = adapterTZ.date(undefined, timezone); @@ -123,11 +120,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ }); }); - it('Method: getTimezone', () => { - if (!adapter.isTimezoneCompatible) { - return; - } - + testSkipIf(!adapter.isTimezoneCompatible)('Method: getTimezone', () => { const testTimezone = (timezone: string, expectedTimezone = timezone) => { expect(adapter.getTimezone(adapter.date(undefined, timezone))).to.equal(expectedTimezone); }; @@ -142,13 +135,13 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ setDefaultTimezone(undefined); }); - it('should not mix Europe/London and UTC in winter', () => { - if (!adapter.isTimezoneCompatible) { - return; - } - const dateWithZone = adapter.date('2023-10-30T11:44:00.000Z', 'Europe/London'); - expect(adapter.getTimezone(dateWithZone)).to.equal('Europe/London'); - }); + testSkipIf(!adapter.isTimezoneCompatible)( + 'should not mix Europe/London and UTC in winter', + () => { + const dateWithZone = adapter.date('2023-10-30T11:44:00.000Z', 'Europe/London'); + expect(adapter.getTimezone(dateWithZone)).to.equal('Europe/London'); + }, + ); it('Method: setTimezone', () => { if (adapter.isTimezoneCompatible) { @@ -219,11 +212,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.isEqual(null, testDateLocale)).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { const dateInLondonTZ = adapterTZ.setTimezone(testDateIso, 'Europe/London'); const dateInParisTZ = adapterTZ.setTimezone(testDateIso, 'Europe/Paris'); @@ -247,11 +236,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ ).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { // Both dates below have the same timestamp, but they are not in the same year when represented in their respective timezone. // The adapter should still consider that they are in the same year. const dateInLondonTZ = adapterTZ.endOfYear( @@ -280,11 +265,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ ).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { // Both dates below have the same timestamp, but they are not in the same month when represented in their respective timezone. // The adapter should still consider that they are in the same month. const dateInLondonTZ = adapterTZ.endOfMonth( @@ -313,11 +294,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ ); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { // Both dates below have the same timestamp, but they are not in the same day when represented in their respective timezone. // The adapter should still consider that they are in the same day. const dateInLondonTZ = adapterTZ.endOfDay( @@ -340,11 +317,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ ); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { // Both dates below have the same timestamp, but they are not in the same day when represented in their respective timezone. // The adapter should still consider that they are in the same day. const dateInLondonTZ = adapterTZ.setTimezone(testDateIso, 'Europe/London'); @@ -364,11 +337,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.isAfter(testDateLocale, adapter.date()!)).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { const dateInLondonTZ = adapterTZ.endOfDay( adapterTZ.setTimezone(testDateIso, 'Europe/London'), ); @@ -393,11 +362,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.isAfterYear(testDateLocale, nextYearLocale)).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { // Both dates below have the same timestamp, but they are not in the same year when represented in their respective timezone. // The adapter should still consider that they are in the same year. const dateInLondonTZ = adapterTZ.endOfYear( @@ -421,11 +386,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.isAfterDay(testDateLocale, nextDayLocale)).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { // Both dates below have the same timestamp, but they are not in the same day when represented in their respective timezone. // The adapter should still consider that they are in the same day. const dateInLondonTZ = adapterTZ.endOfDay( @@ -461,11 +422,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.isBefore(adapter.date()!, testDateLocale)).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { const dateInLondonTZ = adapterTZ.endOfDay( adapterTZ.setTimezone(testDateIso, 'Europe/London'), ); @@ -490,11 +447,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.isBeforeYear(testDateLocale, nextYearLocale)).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { // Both dates below have the same timestamp, but they are not in the same year when represented in their respective timezone. // The adapter should still consider that they are in the same year. const dateInLondonTZ = adapterTZ.endOfYear( @@ -518,11 +471,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.isBeforeDay(testDateLocale, previousDayLocale)).to.equal(false); }); - it('should work with different timezones', function test() { - if (!adapter.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should work with different timezones', () => { // Both dates below have the same timestamp, but they are not in the same day when represented in their respective timezone. // The adapter should still consider that they are in the same day. const dateInLondonTZ = adapterTZ.endOfDay( @@ -656,11 +605,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.endOfMonth(testDateLocale)).toEqualDateTime(expected); }); - it('should update the offset when entering DST', function test() { - if (!adapterTZ.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should update the offset when entering DST', () => { expectSameTimeInMonacoTZ(adapterTZ, testDateLastNonDSTDay); expectSameTimeInMonacoTZ(adapterTZ, adapterTZ.endOfMonth(testDateLastNonDSTDay)); }); @@ -689,11 +634,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.addMonths(testDateIso, 3)).toEqualDateTime('2019-01-30T11:44:00.000Z'); }); - it('should update the offset when entering DST', function test() { - if (!adapterTZ.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should update the offset when entering DST', () => { expectSameTimeInMonacoTZ(adapterTZ, testDateLastNonDSTDay); expectSameTimeInMonacoTZ(adapterTZ, adapterTZ.addMonths(testDateLastNonDSTDay, 1)); }); @@ -705,11 +646,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.addWeeks(testDateIso, -2)).toEqualDateTime('2018-10-16T11:44:00.000Z'); }); - it('should update the offset when entering DST', function test() { - if (!adapterTZ.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should update the offset when entering DST', () => { expectSameTimeInMonacoTZ(adapterTZ, testDateLastNonDSTDay); expectSameTimeInMonacoTZ(adapterTZ, adapterTZ.addWeeks(testDateLastNonDSTDay, 1)); }); @@ -721,11 +658,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ expect(adapter.addDays(testDateIso, -2)).toEqualDateTime('2018-10-28T11:44:00.000Z'); }); - it('should update the offset when entering DST', function test() { - if (!adapterTZ.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should update the offset when entering DST', () => { expectSameTimeInMonacoTZ(adapterTZ, testDateLastNonDSTDay); expectSameTimeInMonacoTZ(adapterTZ, adapterTZ.addDays(testDateLastNonDSTDay, 1)); }); @@ -830,11 +763,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ }); }); - it('should respect the DST', function test() { - if (!adapterTZ.isTimezoneCompatible) { - this.skip(); - } - + testSkipIf(!adapter.isTimezoneCompatible)('should respect the DST', () => { const referenceDate = adapterTZ.date('2022-03-17', 'Europe/Paris'); const weekArray = adapterTZ.getWeekArray(referenceDate); let expectedDate = adapter.startOfWeek(adapter.startOfMonth(referenceDate)); @@ -853,7 +782,7 @@ export const testCalculations: DescribeGregorianAdapterTestSuite = ({ }); }); - it('should respect the locale of the adapter, not the locale of the date', function test() { + it('should respect the locale of the adapter, not the locale of the date', () => { const dateFr = adapterFr.date('2022-03-17', 'default'); const weekArray = adapter.getWeekArray(dateFr); diff --git a/test/utils/pickers/describePicker/describePicker.tsx b/test/utils/pickers/describePicker/describePicker.tsx index 4332a4c15710e..5362abab6eaa5 100644 --- a/test/utils/pickers/describePicker/describePicker.tsx +++ b/test/utils/pickers/describePicker/describePicker.tsx @@ -4,6 +4,7 @@ import { expect } from 'chai'; import { spy } from 'sinon'; import { screen, fireEvent, createDescribe } from '@mui/internal-test-utils'; import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon'; +import { testSkipIf } from 'test/utils/skipIf'; import { DescribePickerOptions } from './describePicker.types'; function innerDescribePicker(ElementToTest: React.ElementType, options: DescribePickerOptions) { @@ -11,23 +12,18 @@ function innerDescribePicker(ElementToTest: React.ElementType, options: Describe const propsToOpen = variant === 'static' ? {} : { open: true }; - it('should forward the `inputRef` prop to the text field ( textfield DOM structure only)', function test() { - if (fieldType === 'multi-input' || variant === 'static') { - this.skip(); - } + testSkipIf(fieldType === 'multi-input' || variant === 'static')( + 'should forward the `inputRef` prop to the text field ( textfield DOM structure only)', + () => { + const inputRef = React.createRef(); + render(); - const inputRef = React.createRef(); - render(); - - expect(inputRef.current).to.have.tagName('input'); - }); + expect(inputRef.current).to.have.tagName('input'); + }, + ); describe('Localization', () => { - it('should respect the `localeText` prop', function test() { - if (hasNoView) { - this.skip(); - } - + testSkipIf(Boolean(hasNoView))('should respect the `localeText` prop', () => { render( { - it('should render custom component', function test() { - if (variant === 'static' || fieldType === 'multi-input') { - this.skip(); - } - - function HomeIcon(props: SvgIconProps) { - return ( - - - + testSkipIf(variant === 'static' || fieldType === 'multi-input')( + 'should render custom component', + () => { + function HomeIcon(props: SvgIconProps) { + return ( + + + + ); + } + + const { queryAllByTestId } = render( + , ); - } - - const { queryAllByTestId } = render( - , - ); - const shouldRenderOpenPickerIcon = !hasNoView && variant !== 'mobile'; + const shouldRenderOpenPickerIcon = !hasNoView && variant !== 'mobile'; - expect(queryAllByTestId('component-test')).to.have.length(shouldRenderOpenPickerIcon ? 1 : 0); - }); + expect(queryAllByTestId('component-test')).to.have.length( + shouldRenderOpenPickerIcon ? 1 : 0, + ); + }, + ); }); describe('Component slot: DesktopPaper', () => { - it('should forward onClick and onTouchStart', function test() { - if (hasNoView || variant !== 'desktop') { - this.skip(); - } - - const handleClick = spy(); - const handleTouchStart = spy(); - render( - , - ); - const paper = screen.getByTestId('paper'); + testSkipIf(hasNoView || variant !== 'desktop')( + 'should forward onClick and onTouchStart', + () => { + const handleClick = spy(); + const handleTouchStart = spy(); + render( + , + ); + const paper = screen.getByTestId('paper'); - fireEvent.click(paper); - fireEvent.touchStart(paper); + fireEvent.click(paper); + fireEvent.touchStart(paper); - expect(handleClick.callCount).to.equal(1); - expect(handleTouchStart.callCount).to.equal(1); - }); + expect(handleClick.callCount).to.equal(1); + expect(handleTouchStart.callCount).to.equal(1); + }, + ); }); describe('Component slot: Popper', () => { - it('should forward onClick and onTouchStart', function test() { - if (hasNoView || variant !== 'desktop') { - this.skip(); - } - - const handleClick = spy(); - const handleTouchStart = spy(); - render( - , - ); - const popper = screen.getByTestId('popper'); + testSkipIf(hasNoView || variant !== 'desktop')( + 'should forward onClick and onTouchStart', + () => { + const handleClick = spy(); + const handleTouchStart = spy(); + render( + , + ); + const popper = screen.getByTestId('popper'); - fireEvent.click(popper); - fireEvent.touchStart(popper); + fireEvent.click(popper); + fireEvent.touchStart(popper); - expect(handleClick.callCount).to.equal(1); - expect(handleTouchStart.callCount).to.equal(1); - }); + expect(handleClick.callCount).to.equal(1); + expect(handleTouchStart.callCount).to.equal(1); + }, + ); }); describe('Component slot: Toolbar', () => { - it('should render toolbar on mobile but not on desktop when `hidden` is not defined', function test() { - if (hasNoView) { - this.skip(); - } - - render( - , - ); - - if (variant === 'desktop') { - expect(screen.queryByTestId('pickers-toolbar')).to.equal(null); - } else { - expect(screen.getByTestId('pickers-toolbar')).toBeVisible(); - } - }); + testSkipIf(Boolean(hasNoView))( + 'should render toolbar on mobile but not on desktop when `hidden` is not defined', + () => { + render( + , + ); - it('should render toolbar when `hidden` is `false`', function test() { - if (hasNoView) { - this.skip(); - } + if (variant === 'desktop') { + expect(screen.queryByTestId('pickers-toolbar')).to.equal(null); + } else { + expect(screen.getByTestId('pickers-toolbar')).toBeVisible(); + } + }, + ); + testSkipIf(Boolean(hasNoView))('should render toolbar when `hidden` is `false`', () => { render( { render( { - it('should not render the open picker button, but still render the picker if its open', function test() { - if (variant === 'static') { - this.skip(); - } - - render( - { + render( + , - ); + }} + />, + ); - expect(screen.queryByRole('button', { name: /Choose/ })).to.equal(null); - // check if anything has been rendered inside the layout content wrapper - expect(document.querySelector('.test-pickers-content-wrapper')?.hasChildNodes()).to.equal( - true, - ); - }); + expect(screen.queryByRole('button', { name: /Choose/ })).to.equal(null); + // check if anything has been rendered inside the layout content wrapper + expect(document.querySelector('.test-pickers-content-wrapper')?.hasChildNodes()).to.equal( + true, + ); + }, + ); }); } diff --git a/test/utils/pickers/describeRangeValidation/testDayViewRangeValidation.tsx b/test/utils/pickers/describeRangeValidation/testDayViewRangeValidation.tsx index 4a3c51de3d094..4d1760b12ae22 100644 --- a/test/utils/pickers/describeRangeValidation/testDayViewRangeValidation.tsx +++ b/test/utils/pickers/describeRangeValidation/testDayViewRangeValidation.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { screen } from '@mui/internal-test-utils'; import { adapterToUse } from 'test/utils/pickers'; +import { describeSkipIf } from 'test/utils/skipIf'; const isDisabled = (el: HTMLElement) => el.getAttribute('disabled') !== null; @@ -31,145 +32,145 @@ const testMonthSwitcherAreDisable = (areDisable: [boolean, boolean]) => { }; export function testDayViewRangeValidation(ElementToTest, getOptions) { - describe('validation in day view:', () => { - const { componentFamily, views, variant = 'desktop' } = getOptions(); - - if (!views.includes('day') || componentFamily === 'field') { - return; - } - - const isDesktop = variant === 'desktop'; - const includesTimeView = views.includes('hours'); - - const defaultProps = { - referenceDate: adapterToUse.date('2018-03-12'), - open: true, - ...(componentFamily === 'field' || componentFamily === 'picker' - ? { enableAccessibleFieldDOMStructure: true } - : {}), - }; - - it('should apply shouldDisableDate', function test() { - const { render } = getOptions(); - render( - adapterToUse.isAfter(date, adapterToUse.date('2018-03-10'))} - />, - ); - - testDisabledDate('10', [false, true], !isDesktop || includesTimeView); - testDisabledDate('11', [true, true], !isDesktop || includesTimeView); - }); - - it('should apply disablePast', function test() { - const { render, clock } = getOptions(); - - let now; - function WithFakeTimer(props) { - now = adapterToUse.date(); - const { referenceDate, ...otherProps } = props; - return ; - } - const { setProps } = render(); - - const tomorrow = adapterToUse.addDays(now, 1); - const yesterday = adapterToUse.addDays(now, -1); - - testDisabledDate( - adapterToUse.format(now, 'dayOfMonth'), - [false, false], - !isDesktop || includesTimeView, - ); - testDisabledDate( - adapterToUse.format(tomorrow, 'dayOfMonth'), - [false, false], - !isDesktop || includesTimeView, - ); - - if (!adapterToUse.isSameMonth(yesterday, tomorrow)) { - setProps({ value: [yesterday, null] }); - clock.runToLast(); - } - testDisabledDate( - adapterToUse.format(yesterday, 'dayOfMonth'), - [true, false], - !isDesktop || includesTimeView, - ); - }); - - it('should apply disableFuture', function test() { - const { render, clock } = getOptions(); - - let now; - function WithFakeTimer(props) { - now = adapterToUse.date(); - const { referenceDate, ...otherProps } = props; - return ; - } - const { setProps } = render(); - - const tomorrow = adapterToUse.addDays(now, 1); - const yesterday = adapterToUse.addDays(now, -1); - - testDisabledDate( - adapterToUse.format(now, 'dayOfMonth'), - [false, true], - !isDesktop || includesTimeView, - ); - testDisabledDate( - adapterToUse.format(tomorrow, 'dayOfMonth'), - [true, true], - !isDesktop || includesTimeView, - ); - - if (!adapterToUse.isSameMonth(yesterday, tomorrow)) { - setProps({ value: [yesterday, null] }); - clock.runToLast(); - } - testDisabledDate( - adapterToUse.format(yesterday, 'dayOfMonth'), - [false, true], - !isDesktop || includesTimeView, - ); - }); - - it('should apply minDate', function test() { - const { render } = getOptions(); - - render( - , - ); - - testDisabledDate('1', [true, false], !isDesktop || includesTimeView); - testDisabledDate('3', [true, false], !isDesktop || includesTimeView); - testDisabledDate('4', [false, false], !isDesktop || includesTimeView); - testDisabledDate('15', [false, false], !isDesktop || includesTimeView); - - testMonthSwitcherAreDisable([true, false]); - }); - - it('should apply maxDate', function test() { - const { render } = getOptions(); - - render( - , - ); - - testDisabledDate('1', [false, true], !isDesktop || includesTimeView); - testDisabledDate('4', [false, true], !isDesktop || includesTimeView); - testDisabledDate('5', [true, true], !isDesktop || includesTimeView); - testDisabledDate('15', [true, true], !isDesktop || includesTimeView); - - testMonthSwitcherAreDisable([false, true]); - }); - }); + const { componentFamily, views, variant = 'desktop' } = getOptions(); + describeSkipIf(!views.includes('day') || componentFamily === 'field')( + 'validation in day view:', + () => { + const isDesktop = variant === 'desktop'; + const includesTimeView = views.includes('hours'); + + const defaultProps = { + referenceDate: adapterToUse.date('2018-03-12'), + open: true, + ...(componentFamily === 'field' || componentFamily === 'picker' + ? { enableAccessibleFieldDOMStructure: true } + : {}), + }; + + it('should apply shouldDisableDate', () => { + const { render } = getOptions(); + render( + + adapterToUse.isAfter(date, adapterToUse.date('2018-03-10')) + } + />, + ); + + testDisabledDate('10', [false, true], !isDesktop || includesTimeView); + testDisabledDate('11', [true, true], !isDesktop || includesTimeView); + }); + + it('should apply disablePast', () => { + const { render, clock } = getOptions(); + + let now; + function WithFakeTimer(props) { + now = adapterToUse.date(); + const { referenceDate, ...otherProps } = props; + return ; + } + const { setProps } = render(); + + const tomorrow = adapterToUse.addDays(now, 1); + const yesterday = adapterToUse.addDays(now, -1); + + testDisabledDate( + adapterToUse.format(now, 'dayOfMonth'), + [false, false], + !isDesktop || includesTimeView, + ); + testDisabledDate( + adapterToUse.format(tomorrow, 'dayOfMonth'), + [false, false], + !isDesktop || includesTimeView, + ); + + if (!adapterToUse.isSameMonth(yesterday, tomorrow)) { + setProps({ value: [yesterday, null] }); + clock.runToLast(); + } + testDisabledDate( + adapterToUse.format(yesterday, 'dayOfMonth'), + [true, false], + !isDesktop || includesTimeView, + ); + }); + + it('should apply disableFuture', () => { + const { render, clock } = getOptions(); + + let now; + function WithFakeTimer(props) { + now = adapterToUse.date(); + const { referenceDate, ...otherProps } = props; + return ; + } + const { setProps } = render(); + + const tomorrow = adapterToUse.addDays(now, 1); + const yesterday = adapterToUse.addDays(now, -1); + + testDisabledDate( + adapterToUse.format(now, 'dayOfMonth'), + [false, true], + !isDesktop || includesTimeView, + ); + testDisabledDate( + adapterToUse.format(tomorrow, 'dayOfMonth'), + [true, true], + !isDesktop || includesTimeView, + ); + + if (!adapterToUse.isSameMonth(yesterday, tomorrow)) { + setProps({ value: [yesterday, null] }); + clock.runToLast(); + } + testDisabledDate( + adapterToUse.format(yesterday, 'dayOfMonth'), + [false, true], + !isDesktop || includesTimeView, + ); + }); + + it('should apply minDate', () => { + const { render } = getOptions(); + + render( + , + ); + + testDisabledDate('1', [true, false], !isDesktop || includesTimeView); + testDisabledDate('3', [true, false], !isDesktop || includesTimeView); + testDisabledDate('4', [false, false], !isDesktop || includesTimeView); + testDisabledDate('15', [false, false], !isDesktop || includesTimeView); + + testMonthSwitcherAreDisable([true, false]); + }); + + it('should apply maxDate', () => { + const { render } = getOptions(); + + render( + , + ); + + testDisabledDate('1', [false, true], !isDesktop || includesTimeView); + testDisabledDate('4', [false, true], !isDesktop || includesTimeView); + testDisabledDate('5', [true, true], !isDesktop || includesTimeView); + testDisabledDate('15', [true, true], !isDesktop || includesTimeView); + + testMonthSwitcherAreDisable([false, true]); + }); + }, + ); } diff --git a/test/utils/pickers/describeRangeValidation/testTextFieldKeyboardRangeValidation.tsx b/test/utils/pickers/describeRangeValidation/testTextFieldKeyboardRangeValidation.tsx index 37ce52823b4a2..87b2f8b7e8f26 100644 --- a/test/utils/pickers/describeRangeValidation/testTextFieldKeyboardRangeValidation.tsx +++ b/test/utils/pickers/describeRangeValidation/testTextFieldKeyboardRangeValidation.tsx @@ -3,6 +3,7 @@ import { expect } from 'chai'; import { spy } from 'sinon'; import { adapterToUse, getAllFieldInputRoot } from 'test/utils/pickers'; import { act } from '@mui/internal-test-utils/createRenderer'; +import { describeSkipIf, testSkipIf } from 'test/utils/skipIf'; import { DescribeRangeValidationTestSuite } from './describeRangeValidation.types'; const testInvalidStatus = (expectedAnswer: boolean[], isSingleInput?: boolean) => { @@ -22,11 +23,10 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu ) => { const { componentFamily, render, isSingleInput, withDate, withTime, setValue } = getOptions(); - if (componentFamily !== 'field' || !setValue) { - return; - } + describeSkipIf(componentFamily !== 'field' || !setValue)('text field keyboard:', () => { + // eslint-disable-next-line @typescript-eslint/no-shadow + const setValue = getOptions().setValue!; - describe('text field keyboard:', () => { it('should not accept end date prior to start state', () => { const onErrorMock = spy(); render(); @@ -45,11 +45,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu testInvalidStatus([true, true], isSingleInput); }); - it('should apply shouldDisableDate', function test() { - if (!withDate) { - return; - } - + testSkipIf(!withDate)('should apply shouldDisableDate', () => { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const now = adapterToUse.date(); render(); @@ -136,7 +132,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu testInvalidStatus([false, true], isSingleInput); }); - it('should apply disableFuture', function test() { + it('should apply disableFuture', () => { const onErrorMock = spy(); const now = adapterToUse.date(); render(); @@ -178,11 +174,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu testInvalidStatus([false, true], isSingleInput); }); - it('should apply minDate', function test() { - if (!withDate) { - return; - } - + testSkipIf(!withDate)('should apply minDate', () => { const onErrorMock = spy(); render(); @@ -215,11 +207,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu testInvalidStatus([false, false], isSingleInput); }); - it('should apply maxDate', function test() { - if (!withDate) { - return; - } - + testSkipIf(!withDate)('should apply maxDate', () => { const onErrorMock = spy(); render(); @@ -244,11 +232,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu testInvalidStatus([true, true], isSingleInput); }); - it('should apply minTime', function test() { - if (!withTime) { - return; - } - + testSkipIf(!withTime)('should apply minTime', () => { const onErrorMock = spy(); render( , @@ -284,11 +268,7 @@ export const testTextFieldKeyboardRangeValidation: DescribeRangeValidationTestSu testInvalidStatus([false, false], isSingleInput); }); - it('should apply maxTime', function test() { - if (!withTime) { - return; - } - + testSkipIf(!withTime)('should apply maxTime', () => { const onErrorMock = spy(); render( , diff --git a/test/utils/pickers/describeRangeValidation/testTextFieldRangeValidation.tsx b/test/utils/pickers/describeRangeValidation/testTextFieldRangeValidation.tsx index 3bc1182de0b76..9cd2a5c6aac63 100644 --- a/test/utils/pickers/describeRangeValidation/testTextFieldRangeValidation.tsx +++ b/test/utils/pickers/describeRangeValidation/testTextFieldRangeValidation.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; import { adapterToUse, getAllFieldInputRoot } from 'test/utils/pickers'; +import { describeSkipIf, testSkipIf } from 'test/utils/skipIf'; import { DescribeRangeValidationTestSuite } from './describeRangeValidation.types'; const testInvalidStatus = (expectedAnswer: boolean[], isSingleInput: boolean | undefined) => { @@ -21,11 +22,7 @@ export const testTextFieldRangeValidation: DescribeRangeValidationTestSuite = ( ) => { const { componentFamily, render, isSingleInput, withDate, withTime } = getOptions(); - if (!['picker', 'field'].includes(componentFamily)) { - return; - } - - describe('text field:', () => { + describeSkipIf(!['picker', 'field'].includes(componentFamily))('text field:', () => { it('should accept single day range', () => { const onErrorMock = spy(); render( @@ -56,11 +53,7 @@ export const testTextFieldRangeValidation: DescribeRangeValidationTestSuite = ( testInvalidStatus([true, true], isSingleInput); }); - it('should apply shouldDisableDate', function test() { - if (!withDate) { - return; - } - + testSkipIf(!withDate)('should apply shouldDisableDate', () => { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); let now; function WithFakeTimer(props) { @@ -234,7 +219,7 @@ export const testTextFieldRangeValidation: DescribeRangeValidationTestSuite = ( testInvalidStatus([true, true], isSingleInput); }); - it('should apply disableFuture', function test() { + it('should apply disableFuture', () => { const onErrorMock = spy(); let now; function WithFakeTimer(props) { @@ -273,11 +258,7 @@ export const testTextFieldRangeValidation: DescribeRangeValidationTestSuite = ( testInvalidStatus([true, true], isSingleInput); }); - it('should apply minDate', function test() { - if (!withDate) { - return; - } - + testSkipIf(!withDate)('should apply minDate', () => { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const { componentFamily, views, render, clock, withDate, withTime } = getOptions(); - if (componentFamily === 'field' || !views.includes('day')) { - return; - } - - describe('day view:', () => { + describeSkipIf(componentFamily === 'field' || !views.includes('day'))('day view:', () => { const defaultProps = { onChange: () => {}, open: true, @@ -20,7 +17,7 @@ export const testDayViewValidation: DescribeValidationTestSuite = (ElementToTest slotProps: { toolbar: { hidden: true } }, }; - it('should apply shouldDisableDate', function test() { + it('should apply shouldDisableDate', () => { render( { const { setProps } = render( { const { setProps } = render( { let now; function WithFakeTimer(props: any) { now = adapterToUse.date(); @@ -111,7 +108,7 @@ export const testDayViewValidation: DescribeValidationTestSuite = (ElementToTest ).to.have.attribute('disabled'); }); - it('should apply disableFuture', function test() { + it('should apply disableFuture', () => { let now; function WithFakeTimer(props: any) { now = adapterToUse.date(); @@ -143,7 +140,7 @@ export const testDayViewValidation: DescribeValidationTestSuite = (ElementToTest ).not.to.have.attribute('disabled'); }); - it('should apply minDate', function test() { + it('should apply minDate', () => { render( { render( { render( { render( @@ -13,16 +14,9 @@ export const testMinutesViewValidation: DescribeValidationTestSuite = ( ) => { const { componentFamily, views, render, clock, withDate, withTime, variant } = getOption(); - if ( - !views.includes('minutes') || - !variant || - componentFamily !== 'picker' || - variant === 'desktop' - ) { - return; - } - - describe('minutes view:', () => { + describeSkipIf( + !views.includes('minutes') || !variant || componentFamily !== 'picker' || variant === 'desktop', + )('minutes view:', () => { const defaultProps = { onChange: () => {}, open: true, @@ -32,7 +26,7 @@ export const testMinutesViewValidation: DescribeValidationTestSuite = ( slotProps: { toolbar: { hidden: true } }, }; - it('should apply shouldDisableTime', function test() { + it('should apply shouldDisableTime', () => { render( { let now; function WithFakeTimer(props) { now = adapterToUse.date(); @@ -108,7 +102,7 @@ export const testMinutesViewValidation: DescribeValidationTestSuite = ( ).not.to.have.attribute('aria-disabled'); }); - it('should apply disableFuture', function test() { + it('should apply disableFuture', () => { let now; function WithFakeTimer(props) { now = adapterToUse.date(); @@ -153,7 +147,7 @@ export const testMinutesViewValidation: DescribeValidationTestSuite = ( ).not.to.have.attribute('aria-disabled'); }); - it('should apply maxTime', function test() { + it('should apply maxTime', () => { render( { render( { const { views, componentFamily, render, clock } = getOptions(); - if (componentFamily === 'field' || !views.includes('month')) { - return; - } - - describe('month view:', () => { + describeSkipIf(componentFamily === 'field' || !views.includes('month'))('month view:', () => { const defaultProps = { onChange: () => {}, ...(views.length > 1 && { @@ -26,7 +23,7 @@ export const testMonthViewValidation: DescribeValidationTestSuite = (ElementToTe }), }; - it('should apply shouldDisableMonth', function test() { + it('should apply shouldDisableMonth', () => { render( { let now; function WithFakeTimer(props) { now = adapterToUse.date(); @@ -74,7 +71,7 @@ export const testMonthViewValidation: DescribeValidationTestSuite = (ElementToTe // TODO: define what appends when value is `null` }); - it('should apply disableFuture', function test() { + it('should apply disableFuture', () => { let now; function WithFakeTimer(props) { now = adapterToUse.date(); @@ -108,7 +105,7 @@ export const testMonthViewValidation: DescribeValidationTestSuite = (ElementToTe // TODO: define what appends when value is `null` }); - it('should apply minDate', function test() { + it('should apply minDate', () => { render( { render( { const { componentFamily, render, withDate, withTime } = getOptions(); - if (!['picker', 'field'].includes(componentFamily)) { - return; - } - - describe('text field:', () => { - it('should apply shouldDisableDate', function test() { - if (['picker', 'field'].includes(componentFamily) && !withDate) { - return; - } - - const onErrorMock = spy(); - const { setProps } = render( - - adapterToUse.isAfter(date, adapterToUse.date('2018-03-10')) - } - />, - ); - - if (withDate) { - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); - expect(onErrorMock.callCount).to.equal(1); - expect(onErrorMock.lastCall.args[0]).to.equal('shouldDisableDate'); - - setProps({ value: adapterToUse.date('2018-03-09') }); - - expect(onErrorMock.callCount).to.equal(2); - expect(onErrorMock.lastCall.args[0]).to.equal(null); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } else { - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - expect(onErrorMock.callCount).to.equal(0); - } - }); - - it('should apply shouldDisableYear', function test() { - if (!withDate) { - // Early return to remove when DateTimePickers will support those props - return; - } - + describeSkipIf(!['picker', 'field'].includes(componentFamily))('text field:', () => { + testSkipIf(['picker', 'field'].includes(componentFamily) && !withDate)( + 'should apply shouldDisableDate', + () => { + const onErrorMock = spy(); + const { setProps } = render( + + adapterToUse.isAfter(date, adapterToUse.date('2018-03-10')) + } + />, + ); + + if (withDate) { + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); + expect(onErrorMock.callCount).to.equal(1); + expect(onErrorMock.lastCall.args[0]).to.equal('shouldDisableDate'); + + setProps({ value: adapterToUse.date('2018-03-09') }); + + expect(onErrorMock.callCount).to.equal(2); + expect(onErrorMock.lastCall.args[0]).to.equal(null); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } else { + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + expect(onErrorMock.callCount).to.equal(0); + } + }, + ); + + // TODO: Remove when DateTimePickers will support those props + testSkipIf(!withDate)('should apply shouldDisableYear', () => { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( { let now; function WithFakeTimer(props: any) { now = adapterToUse.date(); @@ -186,11 +166,7 @@ export const testTextFieldValidation: DescribeValidationTestSuite = (ElementToTe expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); }); - it('should apply disableFuture', function test() { - if (!withDate) { - return; - } - + testSkipIf(!withDate)('should apply disableFuture', () => { let now; function WithFakeTimer(props: any) { now = adapterToUse.date(); @@ -217,129 +193,120 @@ export const testTextFieldValidation: DescribeValidationTestSuite = (ElementToTe expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); }); - it('should apply minDate', function test() { - if (['picker', 'field'].includes(componentFamily) && !withDate) { - return; - } - - const onErrorMock = spy(); - const { setProps } = render( - , - ); - - if (withDate) { - expect(onErrorMock.callCount).to.equal(1); - expect(onErrorMock.lastCall.args[0]).to.equal('minDate'); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); - - setProps({ value: adapterToUse.date('2019-06-20') }); - - expect(onErrorMock.callCount).to.equal(2); - expect(onErrorMock.lastCall.args[0]).to.equal(null); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } else { - expect(onErrorMock.callCount).to.equal(0); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } - }); - - it('should apply maxDate', function test() { - if (['picker', 'field'].includes(componentFamily) && !withDate) { - return; - } - - const onErrorMock = spy(); - const { setProps } = render( - , - ); - - if (withDate) { - expect(onErrorMock.callCount).to.equal(1); - expect(onErrorMock.lastCall.args[0]).to.equal('maxDate'); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); - - setProps({ value: adapterToUse.date('2019-06-10') }); - - expect(onErrorMock.callCount).to.equal(2); - expect(onErrorMock.lastCall.args[0]).to.equal(null); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } else { - expect(onErrorMock.callCount).to.equal(0); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } - }); - - it('should apply minTime', function test() { - if (['picker', 'field'].includes(componentFamily) && !withTime) { - return; - } - - const onErrorMock = spy(); - const { setProps } = render( - , - ); - if (withTime) { - expect(onErrorMock.callCount).to.equal(1); - expect(onErrorMock.lastCall.args[0]).to.equal('minTime'); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); - - setProps({ value: adapterToUse.date('2019-06-15T13:10:00') }); - - expect(onErrorMock.callCount).to.equal(2); - expect(onErrorMock.lastCall.args[0]).to.equal(null); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } else { - expect(onErrorMock.callCount).to.equal(0); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } - }); - - it('should apply maxTime', function test() { - if (['picker', 'field'].includes(componentFamily) && !withTime) { - return; - } - - const onErrorMock = spy(); - const { setProps } = render( - , - ); - if (withTime) { - expect(onErrorMock.callCount).to.equal(0); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - - setProps({ value: adapterToUse.date('2019-06-15T13:10:00') }); - - expect(onErrorMock.callCount).to.equal(1); - expect(onErrorMock.lastCall.args[0]).to.equal('maxTime'); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); - } else { - expect(onErrorMock.callCount).to.equal(0); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } - }); - - it('should apply maxDateTime', function test() { - if (!withDate || !withTime) { - // prop only available on DateTime pickers - return; - } - + testSkipIf(['picker', 'field'].includes(componentFamily) && !withDate)( + 'should apply minDate', + () => { + const onErrorMock = spy(); + const { setProps } = render( + , + ); + + if (withDate) { + expect(onErrorMock.callCount).to.equal(1); + expect(onErrorMock.lastCall.args[0]).to.equal('minDate'); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); + + setProps({ value: adapterToUse.date('2019-06-20') }); + + expect(onErrorMock.callCount).to.equal(2); + expect(onErrorMock.lastCall.args[0]).to.equal(null); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } else { + expect(onErrorMock.callCount).to.equal(0); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } + }, + ); + + testSkipIf(['picker', 'field'].includes(componentFamily) && !withDate)( + 'should apply maxDate', + () => { + const onErrorMock = spy(); + const { setProps } = render( + , + ); + + if (withDate) { + expect(onErrorMock.callCount).to.equal(1); + expect(onErrorMock.lastCall.args[0]).to.equal('maxDate'); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); + + setProps({ value: adapterToUse.date('2019-06-10') }); + + expect(onErrorMock.callCount).to.equal(2); + expect(onErrorMock.lastCall.args[0]).to.equal(null); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } else { + expect(onErrorMock.callCount).to.equal(0); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } + }, + ); + + testSkipIf(['picker', 'field'].includes(componentFamily) && !withTime)( + 'should apply minTime', + () => { + const onErrorMock = spy(); + const { setProps } = render( + , + ); + if (withTime) { + expect(onErrorMock.callCount).to.equal(1); + expect(onErrorMock.lastCall.args[0]).to.equal('minTime'); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); + + setProps({ value: adapterToUse.date('2019-06-15T13:10:00') }); + + expect(onErrorMock.callCount).to.equal(2); + expect(onErrorMock.lastCall.args[0]).to.equal(null); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } else { + expect(onErrorMock.callCount).to.equal(0); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } + }, + ); + + testSkipIf(['picker', 'field'].includes(componentFamily) && !withTime)( + 'should apply maxTime', + () => { + const onErrorMock = spy(); + const { setProps } = render( + , + ); + if (withTime) { + expect(onErrorMock.callCount).to.equal(0); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + + setProps({ value: adapterToUse.date('2019-06-15T13:10:00') }); + + expect(onErrorMock.callCount).to.equal(1); + expect(onErrorMock.lastCall.args[0]).to.equal('maxTime'); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); + } else { + expect(onErrorMock.callCount).to.equal(0); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } + }, + ); + + testSkipIf(!withDate || !withTime)('should apply maxDateTime', () => { const onErrorMock = spy(); const { setProps } = render( { const onErrorMock = spy(); const { setProps } = render( , - ); - if (withTime) { - expect(onErrorMock.callCount).to.equal(1); - expect(onErrorMock.lastCall.args[0]).to.equal('minutesStep'); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); - - setProps({ value: adapterToUse.date('2019-06-15T10:30:00') }); - - expect(onErrorMock.callCount).to.equal(2); - expect(onErrorMock.lastCall.args[0]).to.equal(null); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } else { - expect(onErrorMock.callCount).to.equal(0); - expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); - } - }); + testSkipIf(['picker', 'field'].includes(componentFamily) && !withTime)( + 'should apply minutesStep', + () => { + const onErrorMock = spy(); + const { setProps } = render( + , + ); + if (withTime) { + expect(onErrorMock.callCount).to.equal(1); + expect(onErrorMock.lastCall.args[0]).to.equal('minutesStep'); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'true'); + + setProps({ value: adapterToUse.date('2019-06-15T10:30:00') }); + + expect(onErrorMock.callCount).to.equal(2); + expect(onErrorMock.lastCall.args[0]).to.equal(null); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } else { + expect(onErrorMock.callCount).to.equal(0); + expect(getFieldInputRoot()).to.have.attribute('aria-invalid', 'false'); + } + }, + ); }); }; diff --git a/test/utils/pickers/describeValidation/testYearViewValidation.tsx b/test/utils/pickers/describeValidation/testYearViewValidation.tsx index eb593cadbcef6..090ab64d5a37e 100644 --- a/test/utils/pickers/describeValidation/testYearViewValidation.tsx +++ b/test/utils/pickers/describeValidation/testYearViewValidation.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { screen } from '@mui/internal-test-utils'; import { adapterToUse } from 'test/utils/pickers'; +import { describeSkipIf } from 'test/utils/skipIf'; import { DescribeValidationTestSuite } from './describeValidation.types'; const queryByTextInView = (text: string) => { @@ -19,11 +20,7 @@ const queryByTextInView = (text: string) => { export const testYearViewValidation: DescribeValidationTestSuite = (ElementToTest, getOptions) => { const { views, componentFamily, render } = getOptions(); - if (componentFamily === 'field' || !views.includes('year')) { - return; - } - - describe('year view:', () => { + describeSkipIf(componentFamily === 'field' || !views.includes('year'))('year view:', () => { const defaultProps = { onChange: () => {}, ...(views.length > 1 && { @@ -38,7 +35,7 @@ export const testYearViewValidation: DescribeValidationTestSuite = (ElementToTes }), }; - it('should apply shouldDisableYear', function test() { + it('should apply shouldDisableYear', () => { render( { let now; function WithFakeTimer(props: any) { now = adapterToUse.date(); @@ -72,7 +69,7 @@ export const testYearViewValidation: DescribeValidationTestSuite = (ElementToTes ); }); - it('should apply disableFuture', function test() { + it('should apply disableFuture', () => { let now; function WithFakeTimer(props: any) { now = adapterToUse.date(); @@ -92,7 +89,7 @@ export const testYearViewValidation: DescribeValidationTestSuite = (ElementToTes ); }); - it('should apply minDate', function test() { + it('should apply minDate', () => { render( { render( { + describeSkipIf(componentFamily !== 'picker')('Picker open / close lifecycle', () => { it('should not open on mount if `props.open` is false', () => { render(); expect(screen.queryByRole(viewWrapperRole)).to.equal(null); @@ -89,21 +86,20 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite { - if (pickerParams.variant !== 'mobile') { - return; - } + testSkipIf(pickerParams.variant !== 'mobile')( + 'should not select input content after closing on mobile', + () => { + const { selectSection, pressKey } = renderWithProps( + { enableAccessibleFieldDOMStructure: true, defaultValue: values[0] }, + { componentFamily }, + ); - const { selectSection, pressKey } = renderWithProps( - { enableAccessibleFieldDOMStructure: true, defaultValue: values[0] }, - { componentFamily }, - ); - - // Change the value - setNewValue(values[0], { selectSection, pressKey }); - const fieldRoot = getFieldInputRoot(); - expect(fieldRoot.scrollLeft).to.be.equal(0); - }); + // Change the value + setNewValue(values[0], { selectSection, pressKey }); + const fieldRoot = getFieldInputRoot(); + expect(fieldRoot.scrollLeft).to.be.equal(0); + }, + ); it('should call onChange, onClose and onAccept when selecting a value and `props.closeOnSelect` is true', () => { const onChange = spy(); @@ -182,9 +178,8 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite { // increase the timeout of this test as it tends to sometimes fail on CI with `DesktopDateTimeRangePicker` or `MobileDateTimeRangePicker` - this.timeout(10000); const onChange = spy(); const onAccept = spy(); const onClose = spy(); @@ -287,67 +282,67 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite, - ); - - // Dismiss the picker - fireUserEvent.mousePress(document.body); - expect(onChange.callCount).to.equal(0); - expect(onAccept.callCount).to.equal(0); - expect(onClose.callCount).to.equal(1); - }); - - it('should call onClose and onAccept with the live value when clicking outside of the picker', function test() { - // TODO: Fix this test and enable it on mobile and date-range - if (pickerParams.variant === 'mobile' || isRangeType) { - this.skip(); - } - - const onChange = spy(); - const onAccept = spy(); - const onClose = spy(); + // TODO: Fix this test and enable it on mobile and date-range + testSkipIf(pickerParams.variant === 'mobile' || isRangeType)( + 'should call onClose when clicking outside of the picker without prior change', + () => { + const onChange = spy(); + const onAccept = spy(); + const onClose = spy(); + + render( + , + ); - const { selectSection, pressKey } = renderWithProps( - { - enableAccessibleFieldDOMStructure: true, - onChange, - onAccept, - onClose, - defaultValue: values[0], - open: true, - closeOnSelect: false, - }, - { componentFamily }, - ); + // Dismiss the picker + fireUserEvent.mousePress(document.body); + expect(onChange.callCount).to.equal(0); + expect(onAccept.callCount).to.equal(0); + expect(onClose.callCount).to.equal(1); + }, + ); + + // TODO: Fix this test and enable it on mobile and date-range + testSkipIf(pickerParams.variant === 'mobile' || isRangeType)( + 'should call onClose and onAccept with the live value when clicking outside of the picker', + () => { + const onChange = spy(); + const onAccept = spy(); + const onClose = spy(); + + const { selectSection, pressKey } = renderWithProps( + { + enableAccessibleFieldDOMStructure: true, + onChange, + onAccept, + onClose, + defaultValue: values[0], + open: true, + closeOnSelect: false, + }, + { componentFamily }, + ); - // Change the value (already tested) - const newValue = setNewValue(values[0], { isOpened: true, selectSection, pressKey }); + // Change the value (already tested) + const newValue = setNewValue(values[0], { isOpened: true, selectSection, pressKey }); - // Dismiss the picker - fireUserEvent.keyPress(document.activeElement!, { key: 'Escape' }); - expect(onChange.callCount).to.equal(getExpectedOnChangeCount(componentFamily, pickerParams)); - expect(onAccept.callCount).to.equal(1); - expect(onAccept.lastCall.args[0]).toEqualDateTime(newValue); - expect(onClose.callCount).to.equal(1); - }); + // Dismiss the picker + fireUserEvent.keyPress(document.activeElement!, { key: 'Escape' }); + expect(onChange.callCount).to.equal( + getExpectedOnChangeCount(componentFamily, pickerParams), + ); + expect(onAccept.callCount).to.equal(1); + expect(onAccept.lastCall.args[0]).toEqualDateTime(newValue); + expect(onClose.callCount).to.equal(1); + }, + ); it('should not call onClose or onAccept when clicking outside of the picker if not opened', () => { const onChange = spy(); diff --git a/test/utils/skipIf.ts b/test/utils/skipIf.ts new file mode 100644 index 0000000000000..1a5f1dba0dcf4 --- /dev/null +++ b/test/utils/skipIf.ts @@ -0,0 +1,27 @@ +// Shim for vitest describe.skipIf to be able to run mocha and vitest side-by-side +/** + * Skip a test suite if a condition is met. + * @param {boolean} condition - The condition to check. + * @returns {Function} The test suite function. + */ +export const describeSkipIf: (condition: boolean) => Mocha.PendingSuiteFunction = + (describe as any).skipIf ?? + function describeSkipIf(condition: boolean) { + return condition ? describe.skip : describe; + }; + +/** + * Skip a test if a condition is met. + * @param {boolean} condition - The condition to check. + * @returns {Function} The test function. + */ +export const testSkipIf: (condition: boolean) => Mocha.PendingTestFunction = + (it as any).skipIf ?? + function testSkipIf(condition: boolean) { + return condition ? it.skip : it; + }; + +export const isJSDOM = /jsdom/.test(window.navigator.userAgent); +export const isOSX = /macintosh/i.test(window.navigator.userAgent); +export const hasTouchSupport = + typeof window.Touch !== 'undefined' && typeof window.TouchEvent !== 'undefined';