From b3fe5a8cf5f0bbd635ffd2f2369d583474775c9d Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Mon, 23 Dec 2024 18:07:16 +0500 Subject: [PATCH 1/7] [DataGrid] Improve test coverage of server side data source (#15942) --- .../src/hooks/features/dataSource/cache.ts | 2 +- .../src/tests/dataSource.DataGridPro.test.tsx | 282 ++++++++++++++++++ .../dataSourceTreeData.DataGridPro.test.tsx | 203 +++++++++++++ 3 files changed, 486 insertions(+), 1 deletion(-) create mode 100644 packages/x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx create mode 100644 packages/x-data-grid-pro/src/tests/dataSourceTreeData.DataGridPro.test.tsx diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts index 5645235abf019..ddb98723be693 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/cache.ts @@ -9,7 +9,7 @@ type GridDataSourceCacheDefaultConfig = { ttl?: number; }; -function getKey(params: GridGetRowsParams) { +export function getKey(params: GridGetRowsParams) { return JSON.stringify([ params.paginationModel, params.filterModel, 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 new file mode 100644 index 0000000000000..8c10d9924034d --- /dev/null +++ b/packages/x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx @@ -0,0 +1,282 @@ +import * as React from 'react'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import { act, createRenderer, waitFor, screen, within } from '@mui/internal-test-utils'; +import { expect } from 'chai'; +import { + DataGridPro, + DataGridProProps, + GridApi, + GridDataSource, + GridDataSourceCache, + GridGetRowsParams, + GridGetRowsResponse, + useGridApiRef, +} from '@mui/x-data-grid-pro'; +import { SinonSpy, spy } from 'sinon'; +import { getKey } from '../hooks/features/dataSource/cache'; + +const isJSDOM = /jsdom/.test(window.navigator.userAgent); +const cache = new Map(); + +const testCache: GridDataSourceCache = { + set: (key, value) => cache.set(getKey(key), value), + get: (key) => cache.get(getKey(key)), + clear: () => cache.clear(), +}; + +describe(' - Data source', () => { + const { render } = createRenderer(); + + let apiRef: React.MutableRefObject; + let fetchRowsSpy: SinonSpy; + let mockServer: ReturnType; + + function TestDataSource(props: Partial & { shouldRequestsFail?: boolean }) { + apiRef = useGridApiRef(); + const { shouldRequestsFail = false, ...rest } = props; + mockServer = useMockServer( + { rowLength: 100, maxColumns: 1 }, + { useCursorPagination: false, minDelay: 0, maxDelay: 0, verbose: false }, + shouldRequestsFail, + ); + fetchRowsSpy = spy(mockServer, 'fetchRows'); + const { fetchRows } = mockServer; + + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params: GridGetRowsParams) => { + const urlParams = new URLSearchParams({ + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + start: `${params.start}`, + end: `${params.end}`, + }); + + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + }), + [fetchRows], + ); + + const baselineProps = { + unstable_dataSource: dataSource, + columns: mockServer.columns, + initialState: { pagination: { paginationModel: { page: 0, pageSize: 10 } } }, + disableVirtualization: true, + }; + + return ( +
+ +
+ ); + } + + beforeEach(function beforeTest() { + if (isJSDOM) { + this.skip(); // Needs layout + } + + cache.clear(); + }); + + it('should fetch the data on initial render', async () => { + render(); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + }); + + it('should re-fetch the data on filter change', async () => { + const { setProps } = render(); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + setProps({ filterModel: { items: [{ field: 'name', value: 'John', operator: 'contains' }] } }); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); + }); + + it('should re-fetch the data on sort change', async () => { + const { setProps } = render(); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + setProps({ sortModel: [{ field: 'name', sort: 'asc' }] }); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); + }); + + it('should re-fetch the data on pagination change', async () => { + const { setProps } = render(); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + setProps({ paginationModel: { page: 1, pageSize: 10 } }); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); + }); + + describe('Cache', () => { + it('should cache the data using the default cache', async () => { + render(); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + + const dataRow1 = await screen.findByText( + (_, el) => el?.getAttribute('data-rowindex') === '0', + ); + + const cell1 = within(dataRow1).getByRole('gridcell'); + const cell1Content = cell1.innerText; + + act(() => { + apiRef.current.setPage(1); + }); + + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); + + const dataRow2 = await screen.findByText( + (_, el) => el?.getAttribute('data-rowindex') === '0' && el !== dataRow1, + ); + const cell2 = within(dataRow2).getByRole('gridcell'); + const cell2Content = cell2.innerText; + expect(cell2Content).not.to.equal(cell1Content); + + act(() => { + apiRef.current.setPage(0); + }); + + expect(fetchRowsSpy.callCount).to.equal(2); + + const dataRow3 = await screen.findByText( + (_, el) => el?.getAttribute('data-rowindex') === '0' && el !== dataRow1 && el !== dataRow2, + ); + const cell3 = within(dataRow3).getByRole('gridcell'); + const cell3Content = cell3.innerText; + expect(cell3Content).to.equal(cell1Content); + }); + + it('should cache the data using the custom cache', async () => { + render(); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + expect(cache.size).to.equal(1); + }); + + it('should use the cached data when the same query is made again', async () => { + render(); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + expect(cache.size).to.equal(1); + + const dataRow1 = await screen.findByText( + (_, el) => el?.getAttribute('data-rowindex') === '0', + ); + + const cell1 = within(dataRow1).getByRole('gridcell'); + + const cell1Content = cell1.innerText; + + act(() => { + apiRef.current.setPage(1); + }); + + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); + expect(cache.size).to.equal(2); + + const dataRow2 = await screen.findByText( + (_, el) => el?.getAttribute('data-rowindex') === '0' && el !== dataRow1, + ); + + const cell2 = within(dataRow2).getByRole('gridcell'); + + const cell2Content = cell2.innerText; + expect(cell2Content).not.to.equal(cell1Content); + + act(() => { + apiRef.current.setPage(0); + }); + + const dataRow3 = await screen.findByText( + (_, el) => el?.getAttribute('data-rowindex') === '0' && el !== dataRow1 && el !== dataRow2, + ); + + const cell3 = within(dataRow3).getByRole('gridcell'); + + const cell3Content = cell3.innerText; + expect(cell3Content).to.equal(cell1Content); + + expect(fetchRowsSpy.callCount).to.equal(2); + expect(cache.size).to.equal(2); + }); + + it('should allow to disable the default cache', async () => { + // only + render(); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + + const dataRow1 = await screen.findByText( + (_, el) => el?.getAttribute('data-rowindex') === '0', + ); + + const cell1 = within(dataRow1).getByRole('gridcell'); + + const cell1Content = cell1.innerText; + + act(() => { + apiRef.current.setPage(1); + }); + + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); + + const dataRow2 = await screen.findByText( + (_, el) => el?.getAttribute('data-rowindex') === '0' && el !== dataRow1, + ); + + const cell2 = within(dataRow2).getByRole('gridcell'); + + const cell2Content = cell2.innerText; + expect(cell2Content).not.to.equal(cell1Content); + + act(() => { + apiRef.current.setPage(0); + }); + + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(3); + }); + }); + }); + + describe('Error handling', () => { + it('should call `unstable_onDataSourceError` when the data source returns an error', async () => { + const onDataSourceError = spy(); + render(); + await waitFor(() => { + expect(onDataSourceError.callCount).to.equal(1); + }); + }); + }); +}); 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 new file mode 100644 index 0000000000000..8ad3581a199fc --- /dev/null +++ b/packages/x-data-grid-pro/src/tests/dataSourceTreeData.DataGridPro.test.tsx @@ -0,0 +1,203 @@ +import * as React from 'react'; +import { useMockServer } from '@mui/x-data-grid-generator'; +import { createRenderer, waitFor, fireEvent, within, act, screen } from '@mui/internal-test-utils'; +import { expect } from 'chai'; +import { + DataGridPro, + DataGridProProps, + GRID_ROOT_GROUP_ID, + GridApi, + GridDataSource, + GridGetRowsParams, + GridGroupNode, + useGridApiRef, +} from '@mui/x-data-grid-pro'; +import { SinonSpy, spy } from 'sinon'; +import { raf } from 'test/utils/helperFn'; + +const isJSDOM = /jsdom/.test(window.navigator.userAgent); + +const dataSetOptions = { + dataSet: 'Employee' as const, + rowLength: 100, + maxColumns: 3, + treeData: { maxDepth: 2, groupingField: 'name', averageChildren: 5 }, +}; +const pageSizeOptions = [5, 10, 50]; + +const serverOptions = { minDelay: 0, maxDelay: 0, verbose: false }; + +describe(' - Data source tree data', () => { + const { render } = createRenderer(); + + let apiRef: React.MutableRefObject; + let fetchRowsSpy: SinonSpy; + let mockServer: ReturnType; + + function TestDataSource(props: Partial & { shouldRequestsFail?: boolean }) { + apiRef = useGridApiRef(); + mockServer = useMockServer(dataSetOptions, serverOptions, props.shouldRequestsFail ?? false); + fetchRowsSpy = spy(mockServer, 'fetchRows'); + const { fetchRows, columns } = mockServer; + + const dataSource: GridDataSource = React.useMemo( + () => ({ + getRows: async (params: GridGetRowsParams) => { + const urlParams = new URLSearchParams({ + paginationModel: JSON.stringify(params.paginationModel), + filterModel: JSON.stringify(params.filterModel), + sortModel: JSON.stringify(params.sortModel), + groupKeys: JSON.stringify(params.groupKeys), + }); + + const getRowsResponse = await fetchRows( + `https://mui.com/x/api/data-grid?${urlParams.toString()}`, + ); + + return { + rows: getRowsResponse.rows, + rowCount: getRowsResponse.rowCount, + }; + }, + getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], + getChildrenCount: (row) => row.descendantCount, + }), + [fetchRows], + ); + + return ( +
+ +
+ ); + } + + beforeEach(function beforeTest() { + if (isJSDOM) { + this.skip(); // Needs layout + } + }); + + it('should fetch the data on initial render', async () => { + render(); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + }); + + it('should re-fetch the data on filter change', async () => { + const { setProps } = render(); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + setProps({ filterModel: { items: [{ field: 'name', value: 'John', operator: 'contains' }] } }); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); + }); + + it('should re-fetch the data on sort change', async () => { + const { setProps } = render(); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + setProps({ sortModel: [{ field: 'name', sort: 'asc' }] }); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); + }); + + it('should re-fetch the data on pagination change', async () => { + const { setProps } = render(); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + setProps({ paginationModel: { page: 1, pageSize: 10 } }); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); + }); + + it('should fetch nested data when clicking on a dropdown', async () => { + render(); + + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + await raf(); + expect(Object.keys(apiRef.current.state.rows.tree).length).to.equal(10 + 1); + const dataRow1 = await screen.findByText((_, el) => el?.getAttribute('data-rowindex') === '0'); + + const cell11 = within(dataRow1).getAllByRole('gridcell')[0]; + fireEvent.click(within(cell11).getByRole('button')); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); + + const cell11ChildrenCount = Number(cell11.innerText.split('(')[1].split(')')[0]); + expect(Object.keys(apiRef.current.state.rows.tree).length).to.equal( + 10 + 1 + cell11ChildrenCount, + ); + }); + + it('should fetch nested data when calling API method `unstable_dataSource.fetchRows`', async () => { + render(); + + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + await raf(); + + const tree = apiRef.current.state.rows.tree; + expect(Object.keys(tree).length).to.equal(10 + 1); + const dataRow1 = await screen.findByText((_, el) => el?.getAttribute('data-rowindex') === '0'); + + const cell11 = within(dataRow1).getAllByRole('gridcell')[0]; + const firstChildId = (tree[GRID_ROOT_GROUP_ID] as GridGroupNode).children[0]; + act(() => { + apiRef.current.unstable_dataSource.fetchRows(firstChildId); + }); + await raf(); + + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); + + const cell11ChildrenCount = Number(cell11.innerText.split('(')[1].split(')')[0]); + expect(Object.keys(apiRef.current.state.rows.tree).length).to.equal( + 10 + 1 + cell11ChildrenCount, + ); + }); + + it('should lazily fetch nested data when using `defaultGroupingExpansionDepth`', async () => { + render(); + + // Initial fetch + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + + const groupsToFetch = apiRef.current.state.rows.groupsToFetch; + expect(groupsToFetch?.length).to.be.greaterThan(0); + + const tree = apiRef.current.state.rows.tree; + + // All the group nodes belonging to the grid root group should be there for fetching + (tree[GRID_ROOT_GROUP_ID] as GridGroupNode).children.forEach((child) => { + const node = tree[child]; + if (node.type === 'group') { + expect(groupsToFetch).to.include(child); + } + }); + }); +}); From f9ecbc5ccec80e09409b274d9661ba77bcdec8af Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 24 Jan 2025 00:38:33 +0500 Subject: [PATCH 2/7] Updates --- .../src/tests/dataSource.DataGridPro.test.tsx | 21 ++++++++++++------- .../dataSourceTreeData.DataGridPro.test.tsx | 18 ++++++++++------ 2 files changed, 25 insertions(+), 14 deletions(-) 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..c321a04a63dd8 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 @@ -48,8 +48,7 @@ describe(' - Data source', () => { const urlParams = new URLSearchParams({ filterModel: JSON.stringify(params.filterModel), sortModel: JSON.stringify(params.sortModel), - start: `${params.start}`, - end: `${params.end}`, + paginationModel: JSON.stringify(params.paginationModel), }); const getRowsResponse = await fetchRows( @@ -95,33 +94,39 @@ describe(' - Data source', () => { }); it('should re-fetch the data on filter change', async () => { - const { setProps } = render(); + render(); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(1); }); - setProps({ filterModel: { items: [{ field: 'name', value: 'John', operator: 'contains' }] } }); + act(() => { + apiRef.current.setFilterModel({ items: [{ field: 'name', value: 'John', operator: 'contains' }] }); + }); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(2); }); }); it('should re-fetch the data on sort change', async () => { - const { setProps } = render(); + render(); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(1); }); - setProps({ sortModel: [{ field: 'name', sort: 'asc' }] }); + act(() => { + apiRef.current.setSortModel([{ field: 'name', sort: 'asc' }]); + }); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(2); }); }); it('should re-fetch the data on pagination change', async () => { - const { setProps } = render(); + render(); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(1); }); - setProps({ paginationModel: { page: 1, pageSize: 10 } }); + act(() => { + apiRef.current.setPage(1); + }); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(2); }); 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 8ad3581a199fc..1b7355733e9b4 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 @@ -96,33 +96,39 @@ describe(' - Data source tree data', () => { }); it('should re-fetch the data on filter change', async () => { - const { setProps } = render(); + render(); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(1); }); - setProps({ filterModel: { items: [{ field: 'name', value: 'John', operator: 'contains' }] } }); + act(() => { + apiRef.current.setFilterModel({ items: [{ field: 'name', value: 'John', operator: 'contains' }] }); + }); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(2); }); }); it('should re-fetch the data on sort change', async () => { - const { setProps } = render(); + render(); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(1); }); - setProps({ sortModel: [{ field: 'name', sort: 'asc' }] }); + act(() => { + apiRef.current.setSortModel([{ field: 'name', sort: 'asc' }]); + }); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(2); }); }); it('should re-fetch the data on pagination change', async () => { - const { setProps } = render(); + render(); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(1); }); - setProps({ paginationModel: { page: 1, pageSize: 10 } }); + act(() => { + apiRef.current.setPage(1); + }); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(2); }); From b87f529592857dd5960008d513307e684d8f416e Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 24 Jan 2025 00:38:56 +0500 Subject: [PATCH 3/7] Prettier --- .../x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx | 4 +++- .../src/tests/dataSourceTreeData.DataGridPro.test.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) 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 c321a04a63dd8..d4abc7e4c0078 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 @@ -99,7 +99,9 @@ describe(' - Data source', () => { expect(fetchRowsSpy.callCount).to.equal(1); }); act(() => { - apiRef.current.setFilterModel({ items: [{ field: 'name', value: 'John', operator: 'contains' }] }); + apiRef.current.setFilterModel({ + items: [{ field: 'name', value: 'John', operator: 'contains' }], + }); }); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(2); 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 1b7355733e9b4..073850339cfcc 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 @@ -101,7 +101,9 @@ describe(' - Data source tree data', () => { expect(fetchRowsSpy.callCount).to.equal(1); }); act(() => { - apiRef.current.setFilterModel({ items: [{ field: 'name', value: 'John', operator: 'contains' }] }); + apiRef.current.setFilterModel({ + items: [{ field: 'name', value: 'John', operator: 'contains' }], + }); }); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(2); From 83fd0f09616d7a38141aab6dfa558841908150ef Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 24 Jan 2025 01:10:51 +0500 Subject: [PATCH 4/7] Update --- packages/x-data-grid-pro/src/hooks/features/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-data-grid-pro/src/hooks/features/index.ts b/packages/x-data-grid-pro/src/hooks/features/index.ts index dd9209be6a533..456da75b157c1 100644 --- a/packages/x-data-grid-pro/src/hooks/features/index.ts +++ b/packages/x-data-grid-pro/src/hooks/features/index.ts @@ -6,4 +6,4 @@ export * from './treeData'; export * from './detailPanel'; export * from './rowPinning'; export * from './dataSource/interfaces'; -export * from './dataSource/cache'; +export { GridDataSourceCacheDefault } from './dataSource/cache'; From d844a1cbfef90bd41b7e5c27b5ee97c474e123c0 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 24 Jan 2025 15:21:24 +0500 Subject: [PATCH 5/7] Updates --- .../dataSourceTreeData.DataGridPro.test.tsx | 78 +++++++------------ test/utils/skipIf.ts | 27 +++++++ 2 files changed, 57 insertions(+), 48 deletions(-) create mode 100644 test/utils/skipIf.ts 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 073850339cfcc..51c09b61ee941 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 @@ -1,6 +1,6 @@ import * as React from 'react'; import { useMockServer } from '@mui/x-data-grid-generator'; -import { createRenderer, waitFor, fireEvent, within, act, screen } from '@mui/internal-test-utils'; +import { act, createRenderer, waitFor, within } from '@mui/internal-test-utils'; import { expect } from 'chai'; import { DataGridPro, @@ -13,9 +13,8 @@ import { useGridApiRef, } from '@mui/x-data-grid-pro'; import { SinonSpy, spy } from 'sinon'; -import { raf } from 'test/utils/helperFn'; - -const isJSDOM = /jsdom/.test(window.navigator.userAgent); +import { getCell } from 'test/utils/helperFn'; +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(() => { @@ -101,10 +95,8 @@ describe(' - Data source tree data', () => { expect(fetchRowsSpy.callCount).to.equal(1); }); act(() => { - apiRef.current.setFilterModel({ - items: [{ field: 'name', value: 'John', operator: 'contains' }], - }); - }); + apiRef.current.setFilterModel({ items: [{ field: 'name', value: 'John', operator: 'contains' }] }); + }) await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(2); }); @@ -129,7 +121,7 @@ describe(' - Data source tree data', () => { expect(fetchRowsSpy.callCount).to.equal(1); }); act(() => { - apiRef.current.setPage(1); + apiRef.current.setPaginationModel({ page: 1, pageSize: 10 }); }); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(2); @@ -137,17 +129,16 @@ describe(' - Data source tree data', () => { }); it('should fetch nested data when clicking on a dropdown', async () => { - render(); + const { user } = render(); + expect(fetchRowsSpy.callCount).to.equal(1); await waitFor(() => { - expect(fetchRowsSpy.callCount).to.equal(1); + expect(Object.keys(apiRef.current.state.rows.tree).length).to.equal(10 + 1); }); - await raf(); - expect(Object.keys(apiRef.current.state.rows.tree).length).to.equal(10 + 1); - const dataRow1 = await screen.findByText((_, el) => el?.getAttribute('data-rowindex') === '0'); - const cell11 = within(dataRow1).getAllByRole('gridcell')[0]; - fireEvent.click(within(cell11).getByRole('button')); + const cell11 = getCell(0, 0); + await user.click(within(cell11).getByRole('button')); + await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(2); }); @@ -160,27 +151,21 @@ describe(' - Data source tree data', () => { it('should fetch nested data when calling API method `unstable_dataSource.fetchRows`', async () => { render(); + expect(fetchRowsSpy.callCount).to.equal(1); await waitFor(() => { - expect(fetchRowsSpy.callCount).to.equal(1); + expect(Object.keys(apiRef.current.state.rows.tree).length).to.equal(10 + 1); }); - await raf(); - const tree = apiRef.current.state.rows.tree; - expect(Object.keys(tree).length).to.equal(10 + 1); - const dataRow1 = await screen.findByText((_, el) => el?.getAttribute('data-rowindex') === '0'); - - const cell11 = within(dataRow1).getAllByRole('gridcell')[0]; - const firstChildId = (tree[GRID_ROOT_GROUP_ID] as GridGroupNode).children[0]; - act(() => { - apiRef.current.unstable_dataSource.fetchRows(firstChildId); - }); - await raf(); + const firstChildId = (apiRef.current.state.rows.tree[GRID_ROOT_GROUP_ID] as GridGroupNode) + .children[0]; + apiRef.current.unstable_dataSource.fetchRows(firstChildId); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(2); }); + const cell11 = getCell(0, 0); const cell11ChildrenCount = Number(cell11.innerText.split('(')[1].split(')')[0]); expect(Object.keys(apiRef.current.state.rows.tree).length).to.equal( 10 + 1 + cell11ChildrenCount, @@ -190,22 +175,19 @@ describe(' - Data source tree data', () => { it('should lazily fetch nested data when using `defaultGroupingExpansionDepth`', async () => { render(); - // Initial fetch + expect(fetchRowsSpy.callCount).to.equal(1); await waitFor(() => { - expect(fetchRowsSpy.callCount).to.equal(1); + expect(apiRef.current.state.rows.groupsToFetch?.length).to.be.greaterThan(0); }); - const groupsToFetch = apiRef.current.state.rows.groupsToFetch; - expect(groupsToFetch?.length).to.be.greaterThan(0); - - const tree = apiRef.current.state.rows.tree; - // All the group nodes belonging to the grid root group should be there for fetching - (tree[GRID_ROOT_GROUP_ID] as GridGroupNode).children.forEach((child) => { - const node = tree[child]; - if (node.type === 'group') { - expect(groupsToFetch).to.include(child); - } - }); + (apiRef.current.state.rows.tree[GRID_ROOT_GROUP_ID] as GridGroupNode).children.forEach( + (child) => { + const node = apiRef.current.state.rows.tree[child]; + if (node.type === 'group') { + expect(apiRef.current.state.rows.groupsToFetch).to.include(child); + } + }, + ); }); }); diff --git a/test/utils/skipIf.ts b/test/utils/skipIf.ts new file mode 100644 index 0000000000000..69e484ea2420c --- /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'; \ No newline at end of file From d0c4214a557c8e225eac098ed9e5ab9ff94ee478 Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 24 Jan 2025 18:03:32 +0500 Subject: [PATCH 6/7] Updates --- .../x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx | 3 ++- .../src/tests/dataSourceTreeData.DataGridPro.test.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) 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 d4abc7e4c0078..8b0044c680ab0 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 @@ -1,6 +1,7 @@ import * as React from 'react'; import { useMockServer } from '@mui/x-data-grid-generator'; import { act, createRenderer, waitFor, screen, within } from '@mui/internal-test-utils'; +import { RefObject } from '@mui/x-internals/types'; import { expect } from 'chai'; import { DataGridPro, @@ -27,7 +28,7 @@ const testCache: GridDataSourceCache = { describe(' - Data source', () => { const { render } = createRenderer(); - let apiRef: React.MutableRefObject; + let apiRef: RefObject; let fetchRowsSpy: SinonSpy; let mockServer: ReturnType; 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 51c09b61ee941..4423c45aed68a 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 @@ -1,6 +1,7 @@ import * as React from 'react'; import { useMockServer } from '@mui/x-data-grid-generator'; import { act, createRenderer, waitFor, within } from '@mui/internal-test-utils'; +import { RefObject } from '@mui/x-internals/types'; import { expect } from 'chai'; import { DataGridPro, @@ -30,7 +31,7 @@ const serverOptions = { minDelay: 0, maxDelay: 0, verbose: false }; describeSkipIf(isJSDOM)(' - Data source tree data', () => { const { render } = createRenderer(); - let apiRef: React.MutableRefObject; + let apiRef: RefObject; let fetchRowsSpy: SinonSpy; let mockServer: ReturnType; From 1274a9343e7a0a900f4884a413bb6f9688600cdf Mon Sep 17 00:00:00 2001 From: Bilal Shafi Date: Fri, 24 Jan 2025 18:27:26 +0500 Subject: [PATCH 7/7] Updates --- .../src/tests/dataSource.DataGridPro.test.tsx | 6 +++--- .../tests/dataSourceTreeData.DataGridPro.test.tsx | 12 +++++++----- test/utils/skipIf.ts | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) 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 8b0044c680ab0..4f9323d2f7417 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 @@ -105,7 +105,7 @@ describe(' - Data source', () => { }); }); await waitFor(() => { - expect(fetchRowsSpy.callCount).to.equal(2); + expect(fetchRowsSpy.callCount).to.be.greaterThan(1); }); }); @@ -118,7 +118,7 @@ describe(' - Data source', () => { apiRef.current.setSortModel([{ field: 'name', sort: 'asc' }]); }); await waitFor(() => { - expect(fetchRowsSpy.callCount).to.equal(2); + expect(fetchRowsSpy.callCount).to.be.greaterThan(1); }); }); @@ -131,7 +131,7 @@ describe(' - Data source', () => { apiRef.current.setPage(1); }); await waitFor(() => { - expect(fetchRowsSpy.callCount).to.equal(2); + expect(fetchRowsSpy.callCount).to.be.greaterThan(1); }); }); 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 4423c45aed68a..e6afa62635be5 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 @@ -96,10 +96,12 @@ describeSkipIf(isJSDOM)(' - Data source tree data', () => { expect(fetchRowsSpy.callCount).to.equal(1); }); act(() => { - apiRef.current.setFilterModel({ items: [{ field: 'name', value: 'John', operator: 'contains' }] }); - }) + apiRef.current.setFilterModel({ + items: [{ field: 'name', value: 'John', operator: 'contains' }], + }); + }); await waitFor(() => { - expect(fetchRowsSpy.callCount).to.equal(2); + expect(fetchRowsSpy.callCount).to.be.greaterThan(1); }); }); @@ -112,7 +114,7 @@ describeSkipIf(isJSDOM)(' - Data source tree data', () => { apiRef.current.setSortModel([{ field: 'name', sort: 'asc' }]); }); await waitFor(() => { - expect(fetchRowsSpy.callCount).to.equal(2); + expect(fetchRowsSpy.callCount).to.be.greaterThan(1); }); }); @@ -125,7 +127,7 @@ describeSkipIf(isJSDOM)(' - Data source tree data', () => { apiRef.current.setPaginationModel({ page: 1, pageSize: 10 }); }); await waitFor(() => { - expect(fetchRowsSpy.callCount).to.equal(2); + expect(fetchRowsSpy.callCount).to.be.greaterThan(1); }); }); diff --git a/test/utils/skipIf.ts b/test/utils/skipIf.ts index 69e484ea2420c..1a5f1dba0dcf4 100644 --- a/test/utils/skipIf.ts +++ b/test/utils/skipIf.ts @@ -24,4 +24,4 @@ export const testSkipIf: (condition: boolean) => Mocha.PendingTestFunction = 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'; \ No newline at end of file + typeof window.Touch !== 'undefined' && typeof window.TouchEvent !== 'undefined';