diff --git a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputDate.tsx b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputDate.tsx
index a613c573a082..528af026ff58 100644
--- a/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputDate.tsx
+++ b/packages/x-data-grid/src/components/panel/filterPanel/GridFilterInputDate.tsx
@@ -26,6 +26,9 @@ function convertFilterItemValueToInputValue(
return '';
}
const dateCopy = new Date(itemValue);
+ if (Number.isNaN(dateCopy.getTime())) {
+ return '';
+ }
// The date picker expects the date to be in the local timezone.
// But .toISOString() converts it to UTC with zero offset.
// So we need to subtract the timezone offset.
@@ -69,7 +72,8 @@ function GridFilterInputDate(props: GridFilterInputDateProps) {
setIsApplying(true);
filterTimeout.start(rootProps.filterDebounceMs, () => {
- applyValue({ ...item, value: new Date(value) });
+ const date = new Date(value);
+ applyValue({ ...item, value: Number.isNaN(date.getTime()) ? undefined : date });
setIsApplying(false);
});
},
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 e1f098e05799..98e161d10f2c 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
@@ -1,40 +1,8 @@
-import * as React from 'react';
import { expect } from 'chai';
-import { createRenderer, ErrorBoundary } from '@mui-internal/test-utils';
-import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
-import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
-import { TreeItem } from '@mui/x-tree-view/TreeItem';
+import { describeTreeView } from 'test/utils/tree-view/describeTreeView';
-describe('useTreeViewItems', () => {
- const { render } = createRenderer();
-
- it('should throw an error when two items have the same ID (items prop approach)', 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();
- }
-
- expect(() =>
- render(
-
-
- ,
- ),
- ).toErrorDev([
- 'MUI X: The Tree View component requires all items to have a unique `id` property.',
- 'MUI X: The Tree View component requires all items to have a unique `id` property.',
- 'The above error occurred in the component:',
- ]);
- });
-
- it('should throw an error when two items have the same ID (JSX approach)', function test() {
+describeTreeView('useTreeViewItems plugin', ({ render, treeViewComponent }) => {
+ 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
@@ -42,19 +10,15 @@ describe('useTreeViewItems', () => {
this.skip();
}
- expect(() =>
- render(
-
-
-
-
-
- ,
- ),
- ).toErrorDev([
- 'MUI X: The Tree View component requires all items to have a unique `id` property.',
- 'MUI X: The Tree View component requires all items to have a unique `id` property.',
- 'The above error occurred in the component:',
- ]);
+ expect(() => render({ items: [{ id: '1' }, { id: '1' }], withErrorBoundary: true })).toErrorDev(
+ [
+ ...(treeViewComponent === 'SimpleTreeView'
+ ? ['Encountered two children with the same key']
+ : []),
+ 'MUI X: The Tree View component requires all items to have a unique `id` property.',
+ 'MUI X: The Tree View component requires all items to have a unique `id` property.',
+ `The above error occurred in the component`,
+ ],
+ );
});
});
diff --git a/test/e2e/index.test.ts b/test/e2e/index.test.ts
index 97357460b867..697265f3b71b 100644
--- a/test/e2e/index.test.ts
+++ b/test/e2e/index.test.ts
@@ -10,6 +10,7 @@ import {
devices,
BrowserContextOptions,
BrowserType,
+ WebError,
} from '@playwright/test';
import { pickersTextFieldClasses } from '@mui/x-date-pickers/PickersTextField';
import { pickersSectionListClasses } from '@mui/x-date-pickers/PickersSectionList';
@@ -514,6 +515,29 @@ async function initializeEnvironment(
await page.locator('[role="gridcell"][data-field="brand"] input').inputValue(),
).not.to.equal('v');
});
+
+ // https://github.com/mui/mui-x/issues/12705
+ it('should not crash if the date is invalid', async () => {
+ await renderFixture('DataGrid/KeyboardEditDate');
+
+ await page.hover('div[role="columnheader"][data-field="birthday"]');
+ await page.click(
+ 'div[role="columnheader"][data-field="birthday"] button[aria-label="Menu"]',
+ );
+ await page.click('"Filter"');
+ await page.keyboard.type('08/04/2024', { delay: 10 });
+
+ let thrownError: Error | null = null;
+ context.once('weberror', (webError: WebError) => {
+ thrownError = webError.error();
+ console.error(thrownError);
+ });
+
+ await page.keyboard.press('Backspace');
+
+ await sleep(200);
+ expect(thrownError).to.equal(null);
+ });
});
describe('', () => {
diff --git a/test/utils/tree-view/describeTreeView/describeTreeView.tsx b/test/utils/tree-view/describeTreeView/describeTreeView.tsx
index 5dc84c400887..6184bf83eefc 100644
--- a/test/utils/tree-view/describeTreeView/describeTreeView.tsx
+++ b/test/utils/tree-view/describeTreeView/describeTreeView.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
import createDescribe from '@mui-internal/test-utils/createDescribe';
-import { createRenderer } from '@mui-internal/test-utils';
+import { createRenderer, ErrorBoundary } from '@mui-internal/test-utils';
import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
import { RichTreeViewPro } from '@mui/x-tree-view-pro/RichTreeViewPro';
import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView';
@@ -55,233 +55,149 @@ const innerDescribeTreeView = (
};
};
- describe(message, () => {
- describe('RichTreeView + TreeItem', () => {
- const renderRichTreeView: DescribeTreeViewRenderer = ({
- items: rawItems,
- slotProps,
- ...other
- }) => {
- const items = rawItems as readonly DescribeTreeViewItem[];
- const apiRef = { current: undefined };
- const result = render(
-
- ({
- ...slotProps?.item,
- 'data-testid': ownerState.itemId,
- }) as any,
- }}
- getItemLabel={(item) => item.label ?? item.id}
- isItemDisabled={(item) => !!item.disabled}
- {...other}
- />,
- );
+ const createRendererForComponentWithItemsProp = (
+ TreeViewComponent: typeof RichTreeView,
+ TreeItemComponent: typeof TreeItem | typeof TreeItem2,
+ ) => {
+ const wrappedRenderer: DescribeTreeViewRenderer = ({
+ items: rawItems,
+ withErrorBoundary,
+ slotProps,
+ ...other
+ }) => {
+ const items = rawItems as readonly DescribeTreeViewItem[];
+ const apiRef = { current: undefined };
+
+ const jsx = (
+
+ ({
+ ...slotProps?.item,
+ 'data-testid': ownerState.itemId,
+ }) as any,
+ }}
+ getItemLabel={(item) => item.label ?? item.id}
+ isItemDisabled={(item) => !!item.disabled}
+ {...other}
+ />
+ );
+
+ const result = render(withErrorBoundary ? {jsx} : jsx);
+
+ return {
+ setProps: result.setProps,
+ apiRef: apiRef as unknown as { current: TreeViewPublicAPI },
+ ...getUtils(result),
+ };
+ };
- return {
- setProps: result.setProps,
- apiRef: apiRef as unknown as { current: TreeViewPublicAPI },
- ...getUtils(result),
- };
+ return wrappedRenderer;
+ };
+
+ const createRendererForComponentWithJSXItems = (
+ TreeViewComponent: typeof SimpleTreeView,
+ TreeItemComponent: typeof TreeItem | typeof TreeItem2,
+ ) => {
+ const wrappedRenderer: DescribeTreeViewRenderer = ({
+ items: rawItems,
+ withErrorBoundary,
+ slots,
+ slotProps,
+ ...other
+ }) => {
+ const items = rawItems as readonly DescribeTreeViewItem[];
+ const Item = slots?.item ?? TreeItemComponent;
+ const apiRef = { current: undefined };
+
+ const renderItem = (item: DescribeTreeViewItem) => (
+ -
+ {item.children?.map(renderItem)}
+
+ );
+
+ const jsx = (
+
+ {items.map(renderItem)}
+
+ );
+
+ const result = render(withErrorBoundary ? {jsx} : jsx);
+
+ return {
+ setProps: result.setProps,
+ apiRef: apiRef as unknown as { current: TreeViewPublicAPI },
+ ...getUtils(result),
};
+ };
- testRunner({ render: renderRichTreeView, setup: 'RichTreeView + TreeItem' });
+ return wrappedRenderer;
+ };
+
+ describe(message, () => {
+ describe('RichTreeView + TreeItem', () => {
+ testRunner({
+ render: createRendererForComponentWithItemsProp(RichTreeView, TreeItem),
+ setup: 'RichTreeView + TreeItem',
+ treeViewComponent: 'RichTreeView',
+ treeItemComponent: 'TreeItem',
+ });
});
describe('RichTreeView + TreeItem2', () => {
- const renderRichTreeView: DescribeTreeViewRenderer = ({
- items: rawItems,
- slots,
- slotProps,
- ...other
- }) => {
- const items = rawItems as readonly DescribeTreeViewItem[];
- const apiRef = { current: undefined };
- const result = render(
-
- ({
- ...slotProps?.item,
- 'data-testid': ownerState.itemId,
- }) as any,
- }}
- getItemLabel={(item) => item.label ?? item.id}
- isItemDisabled={(item) => !!item.disabled}
- {...other}
- />,
- );
-
- return {
- setProps: result.setProps,
- apiRef: apiRef as unknown as { current: TreeViewPublicAPI },
- ...getUtils(result),
- };
- };
-
- testRunner({ render: renderRichTreeView, setup: 'RichTreeView + TreeItem2' });
+ testRunner({
+ render: createRendererForComponentWithItemsProp(RichTreeView, TreeItem2),
+ setup: 'RichTreeView + TreeItem2',
+ treeViewComponent: 'RichTreeView',
+ treeItemComponent: 'TreeItem2',
+ });
});
describe('RichTreeViewPro + TreeItem', () => {
- const renderRichTreeViewPro: DescribeTreeViewRenderer = ({
- items: rawItems,
- slotProps,
- ...other
- }) => {
- const items = rawItems as readonly DescribeTreeViewItem[];
- const apiRef = { current: undefined };
- const result = render(
-
- ({
- ...slotProps?.item,
- 'data-testid': ownerState.itemId,
- }) as any,
- }}
- getItemLabel={(item) => item.label ?? item.id}
- isItemDisabled={(item) => !!item.disabled}
- {...other}
- />,
- );
-
- return {
- setProps: result.setProps,
- apiRef: apiRef as unknown as { current: TreeViewPublicAPI },
- ...getUtils(result),
- };
- };
-
- testRunner({ render: renderRichTreeViewPro, setup: 'RichTreeView + TreeItem' });
+ testRunner({
+ render: createRendererForComponentWithItemsProp(RichTreeViewPro, TreeItem),
+ setup: 'RichTreeViewPro + TreeItem',
+ treeViewComponent: 'RichTreeViewPro',
+ treeItemComponent: 'TreeItem',
+ });
});
describe('RichTreeViewPro + TreeItem2', () => {
- const renderRichTreeViewPro: DescribeTreeViewRenderer = ({
- items: rawItems,
- slots,
- slotProps,
- ...other
- }) => {
- const items = rawItems as readonly DescribeTreeViewItem[];
- const apiRef = { current: undefined };
- const result = render(
-
- ({
- ...slotProps?.item,
- 'data-testid': ownerState.itemId,
- }) as any,
- }}
- getItemLabel={(item) => item.label ?? item.id}
- isItemDisabled={(item) => !!item.disabled}
- {...other}
- />,
- );
-
- return {
- setProps: result.setProps,
- apiRef: apiRef as unknown as { current: TreeViewPublicAPI },
- ...getUtils(result),
- };
- };
-
- testRunner({ render: renderRichTreeViewPro, setup: 'RichTreeView + TreeItem2' });
+ testRunner({
+ render: createRendererForComponentWithItemsProp(RichTreeViewPro, TreeItem2),
+ setup: 'RichTreeViewPro + TreeItem2',
+ treeViewComponent: 'RichTreeViewPro',
+ treeItemComponent: 'TreeItem2',
+ });
});
describe('SimpleTreeView + TreeItem', () => {
- const renderSimpleTreeView: DescribeTreeViewRenderer = ({
- items: rawItems,
- slots,
- slotProps,
- ...other
- }) => {
- const items = rawItems as readonly DescribeTreeViewItem[];
- const Item = slots?.item ?? TreeItem;
- const apiRef = { current: undefined };
-
- const renderItem = (item: DescribeTreeViewItem) => (
- -
- {item.children?.map(renderItem)}
-
- );
-
- const result = render(
-
- {items.map(renderItem)}
- ,
- );
-
- return {
- setProps: result.setProps,
- apiRef: apiRef as unknown as { current: TreeViewPublicAPI },
- ...getUtils(result),
- };
- };
-
- testRunner({ render: renderSimpleTreeView, setup: 'SimpleTreeView + TreeItem' });
+ testRunner({
+ render: createRendererForComponentWithJSXItems(SimpleTreeView, TreeItem),
+ setup: 'SimpleTreeView + TreeItem',
+ treeViewComponent: 'SimpleTreeView',
+ treeItemComponent: 'TreeItem',
+ });
});
describe('SimpleTreeView + TreeItem2', () => {
- const renderSimpleTreeView: DescribeTreeViewRenderer = ({
- items: rawItems,
- slots,
- slotProps,
- ...other
- }) => {
- const items = rawItems as readonly DescribeTreeViewItem[];
- const Item = slots?.item ?? TreeItem2;
- const apiRef = { current: undefined };
-
- const renderItem = (item: DescribeTreeViewItem) => (
- -
- {item.children?.map(renderItem)}
-
- );
-
- const result = render(
-
- {items.map(renderItem)}
- ,
- );
-
- return {
- setProps: result.setProps,
- apiRef: apiRef as unknown as { current: TreeViewPublicAPI },
- ...getUtils(result),
- };
- };
-
- testRunner({ render: renderSimpleTreeView, setup: 'SimpleTreeView + TreeItem2' });
+ testRunner({
+ render: createRendererForComponentWithJSXItems(SimpleTreeView, TreeItem2),
+ setup: 'SimpleTreeView + TreeItem2',
+ treeViewComponent: 'SimpleTreeView',
+ treeItemComponent: 'TreeItem2',
+ });
});
});
};
@@ -301,6 +217,8 @@ type DescribeTreeView = {
* Describe tests for the Tree View that will be executed with the following setups:
* - RichTreeView + TreeItem
* - RichTreeView + TreeItem2
+ * - RichTreeViewPro + TreeItem
+ * - RichTreeViewPro + TreeItem2
* - SimpleTreeView + TreeItem
* - SimpleTreeView + TreeItem2
*
diff --git a/test/utils/tree-view/describeTreeView/describeTreeView.types.ts b/test/utils/tree-view/describeTreeView/describeTreeView.types.ts
index 017aa8906f99..e28e480022a8 100644
--- a/test/utils/tree-view/describeTreeView/describeTreeView.types.ts
+++ b/test/utils/tree-view/describeTreeView/describeTreeView.types.ts
@@ -78,6 +78,10 @@ export type DescribeTreeViewRenderer(
params: {
items: readonly R[];
+ /**
+ * If `true`, the Tree View will be wrapped with an error boundary.
+ */
+ withErrorBoundary?: boolean;
} & Omit, 'slots' | 'slotProps'> & {
slots?: MergePluginsProperty & {
item?: React.ElementType;
@@ -88,13 +92,14 @@ export type DescribeTreeViewRenderer DescribeTreeViewRendererReturnValue;
+type TreeViewComponent = 'RichTreeView' | 'RichTreeViewPro' | 'SimpleTreeView';
+type TreeItemComponent = 'TreeItem' | 'TreeItem2';
+
interface DescribeTreeViewTestRunnerParams {
render: DescribeTreeViewRenderer;
- setup:
- | 'SimpleTreeView + TreeItem'
- | 'SimpleTreeView + TreeItem2'
- | 'RichTreeView + TreeItem'
- | 'RichTreeView + TreeItem2';
+ setup: `${TreeViewComponent} + ${TreeItemComponent}`;
+ treeViewComponent: TreeViewComponent;
+ treeItemComponent: TreeItemComponent;
}
export interface DescribeTreeViewItem {