diff --git a/packages/x-data-grid-premium/src/tests/dataSourceAggregation.DataGridPremium.test.tsx b/packages/x-data-grid-premium/src/tests/dataSourceAggregation.DataGridPremium.test.tsx index 8a67f0acf0957..8defca0540cd1 100644 --- a/packages/x-data-grid-premium/src/tests/dataSourceAggregation.DataGridPremium.test.tsx +++ b/packages/x-data-grid-premium/src/tests/dataSourceAggregation.DataGridPremium.test.tsx @@ -99,18 +99,12 @@ describe(' - Data source aggregation', () => { it('should show aggregation option in the column menu', async () => { const { user } = render(); - await waitFor(() => { - expect(getRowsSpy.callCount).to.be.greaterThan(0); - }); await user.click(within(getColumnHeaderCell(0)).getByLabelText('Menu')); expect(screen.queryByLabelText('Aggregation')).not.to.equal(null); }); it('should not show aggregation option in the column menu when no aggregation function is defined', async () => { const { user } = render(); - await waitFor(() => { - expect(getRowsSpy.callCount).to.be.greaterThan(0); - }); await user.click(within(getColumnHeaderCell(0)).getByLabelText('Menu')); expect(screen.queryByLabelText('Aggregation')).to.equal(null); }); @@ -123,9 +117,6 @@ describe(' - Data source aggregation', () => { }} />, ); - await waitFor(() => { - expect(getRowsSpy.callCount).to.be.greaterThan(0); - }); expect(getRowsSpy.args[0][0].aggregationModel).to.deep.equal({ id: 'size' }); }); diff --git a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceBase.ts b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceBase.ts index 9df4b1832b99e..ca83df7ca1238 100644 --- a/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceBase.ts +++ b/packages/x-data-grid-pro/src/hooks/features/dataSource/useGridDataSourceBase.ts @@ -17,6 +17,7 @@ import { GridDataSourceCache, runIf, } from '@mui/x-data-grid/internals'; +import { unstable_debounce as debounce } from '@mui/utils'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; import { gridGetRowsParamsSelector, gridDataSourceErrorsSelector } from './gridDataSourceSelector'; @@ -64,9 +65,6 @@ export const useGridDataSourceBase = ( | 'unstable_dataSource' | 'unstable_dataSourceCache' | 'unstable_onDataSourceError' - | 'sortingMode' - | 'filterMode' - | 'paginationMode' | 'pageSizeOptions' | 'treeData' | 'unstable_lazyLoading' @@ -324,6 +322,8 @@ export const useGridDataSourceBase = ( [apiRef], ); + const debouncedFetchRows = React.useMemo(() => debounce(fetchRows, 0), [fetchRows]); + const handleStrategyActivityChange = React.useCallback< GridEventListener<'strategyAvailabilityChange'> >(() => { @@ -422,9 +422,9 @@ export const useGridDataSourceBase = ( }, events: { strategyAvailabilityChange: handleStrategyActivityChange, - sortModelChange: runIf(defaultRowsUpdateStrategyActive, () => fetchRows()), - filterModelChange: runIf(defaultRowsUpdateStrategyActive, () => fetchRows()), - paginationModelChange: runIf(defaultRowsUpdateStrategyActive, () => fetchRows()), + sortModelChange: runIf(defaultRowsUpdateStrategyActive, () => debouncedFetchRows()), + filterModelChange: runIf(defaultRowsUpdateStrategyActive, () => debouncedFetchRows()), + paginationModelChange: runIf(defaultRowsUpdateStrategyActive, () => debouncedFetchRows()), }, }; }; diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts index 5e14c182e0b33..9bb5eae0ce7fd 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts @@ -1,6 +1,7 @@ import * as React from 'react'; import { RefObject } from '@mui/x-internals/types'; import { throttle } from '@mui/x-internals/throttle'; +import { unstable_debounce as debounce } from '@mui/utils'; import { useGridApiEventHandler, useGridSelector, @@ -79,6 +80,15 @@ export const useGridDataSourceLazyLoader = ( const loadingTrigger = React.useRef(null); const rowsStale = React.useRef(false); + const fetchRows = React.useCallback( + (params: Partial) => { + privateApiRef.current.unstable_dataSource.fetchRows(GRID_ROOT_GROUP_ID, params); + }, + [privateApiRef], + ); + + const debouncedFetchRows = React.useMemo(() => debounce(fetchRows, 0), [fetchRows]); + // Adjust the render context range to fit the pagination model's page size // First row index should be decreased to the start of the page, end row index should be increased to the end of the page const adjustRowParams = React.useCallback( @@ -108,8 +118,8 @@ export const useGridDataSourceLazyLoader = ( filterModel, }; - privateApiRef.current.unstable_dataSource.fetchRows(GRID_ROOT_GROUP_ID, getRowsParams); - }, [privateApiRef, sortModel, filterModel, paginationModel.pageSize]); + fetchRows(getRowsParams); + }, [privateApiRef, sortModel, filterModel, paginationModel.pageSize, fetchRows]); const ensureValidRowCount = React.useCallback( (previousLoadingTrigger: LoadingTrigger, newLoadingTrigger: LoadingTrigger) => { @@ -303,7 +313,7 @@ export const useGridDataSourceLazyLoader = ( ); const handleRowCountChange = React.useCallback(() => { - if (loadingTrigger.current === null) { + if (rowsStale.current || loadingTrigger.current === null) { return; } @@ -314,11 +324,12 @@ export const useGridDataSourceLazyLoader = ( const handleScrolling: GridEventListener<'scrollPositionChange'> = React.useCallback( (newScrollPosition) => { + if (rowsStale.current || loadingTrigger.current !== LoadingTrigger.SCROLL_END) { + return; + } + const renderContext = gridRenderContextSelector(privateApiRef); - if ( - loadingTrigger.current !== LoadingTrigger.SCROLL_END || - previousLastRowIndex.current >= renderContext.lastRowIndex - ) { + if (previousLastRowIndex.current >= renderContext.lastRowIndex) { return; } @@ -336,10 +347,7 @@ export const useGridDataSourceLazyLoader = ( }; privateApiRef.current.setLoading(true); - privateApiRef.current.unstable_dataSource.fetchRows( - GRID_ROOT_GROUP_ID, - adjustRowParams(getRowsParams), - ); + fetchRows(adjustRowParams(getRowsParams)); } }, [ @@ -350,6 +358,7 @@ export const useGridDataSourceLazyLoader = ( dimensions, paginationModel.pageSize, adjustRowParams, + fetchRows, ], ); @@ -357,7 +366,7 @@ export const useGridDataSourceLazyLoader = ( GridEventListener<'renderedRowsIntervalChange'> >( (params) => { - if (loadingTrigger.current !== LoadingTrigger.VIEWPORT) { + if (rowsStale.current || loadingTrigger.current !== LoadingTrigger.VIEWPORT) { return; } @@ -401,10 +410,7 @@ export const useGridDataSourceLazyLoader = ( getRowsParams.start = skeletonRowsSection.firstRowIndex; getRowsParams.end = skeletonRowsSection.lastRowIndex; - privateApiRef.current.unstable_dataSource.fetchRows( - GRID_ROOT_GROUP_ID, - adjustRowParams(getRowsParams), - ); + fetchRows(adjustRowParams(getRowsParams)); }, [ privateApiRef, @@ -413,6 +419,7 @@ export const useGridDataSourceLazyLoader = ( sortModel, filterModel, adjustRowParams, + fetchRows, ], ); @@ -444,12 +451,9 @@ export const useGridDataSourceLazyLoader = ( }; privateApiRef.current.setLoading(true); - privateApiRef.current.unstable_dataSource.fetchRows( - GRID_ROOT_GROUP_ID, - adjustRowParams(getRowsParams), - ); + debouncedFetchRows(adjustRowParams(getRowsParams)); }, - [privateApiRef, filterModel, paginationModel.pageSize, adjustRowParams], + [privateApiRef, filterModel, paginationModel.pageSize, adjustRowParams, debouncedFetchRows], ); const handleGridFilterModelChange = React.useCallback>( @@ -464,9 +468,9 @@ export const useGridDataSourceLazyLoader = ( }; privateApiRef.current.setLoading(true); - privateApiRef.current.unstable_dataSource.fetchRows(GRID_ROOT_GROUP_ID, getRowsParams); + debouncedFetchRows(getRowsParams); }, - [privateApiRef, sortModel, paginationModel.pageSize], + [privateApiRef, sortModel, paginationModel.pageSize, debouncedFetchRows], ); const handleStrategyActivityChange = React.useCallback< 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 b3746723d6f09..c5ef6d7cb2fcc 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,6 @@ 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 { act, createRenderer, waitFor } from '@mui/internal-test-utils'; import { expect } from 'chai'; import { RefObject } from '@mui/x-internals/types'; import { @@ -8,44 +8,62 @@ import { DataGridProProps, GridApi, GridDataSource, - GridDataSourceCache, GridGetRowsParams, GridGetRowsResponse, useGridApiRef, } from '@mui/x-data-grid-pro'; -import { SinonSpy, spy } from 'sinon'; +import { spy } from 'sinon'; import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; import { getKeyDefault } from '../hooks/features/dataSource/cache'; -const cache = new Map(); +class TestCache { + private cache: Map; -const testCache: GridDataSourceCache = { - set: (key, value) => cache.set(getKeyDefault(key), value), - get: (key) => cache.get(getKeyDefault(key)), - clear: () => cache.clear(), -}; + constructor() { + this.cache = new Map(); + } + + set(key: GridGetRowsParams, value: GridGetRowsResponse) { + this.cache.set(getKeyDefault(key), value); + } + + get(key: GridGetRowsParams) { + return this.cache.get(getKeyDefault(key)); + } + + size() { + return this.cache.size; + } + + clear() { + this.cache.clear(); + } +} + +const pageSizeOptions = [10, 20]; +const serverOptions = { useCursorPagination: false, minDelay: 0, maxDelay: 0, verbose: false }; // Needs layout describeSkipIf(isJSDOM)(' - Data source', () => { const { render } = createRenderer(); + const fetchRowsSpy = spy(); let apiRef: RefObject; - 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, + serverOptions, + props.shouldRequestsFail ?? false, ); - fetchRowsSpy = spy(mockServer, 'fetchRows'); + const { fetchRows } = mockServer; - const dataSource: GridDataSource = React.useMemo( - () => ({ + const dataSource: GridDataSource = React.useMemo(() => { + fetchRowsSpy.resetHistory(); + return { getRows: async (params: GridGetRowsParams) => { const urlParams = new URLSearchParams({ filterModel: JSON.stringify(params.filterModel), @@ -54,40 +72,34 @@ describeSkipIf(isJSDOM)(' - Data source', () => { end: `${params.end}`, }); - const getRowsResponse = await fetchRows( - `https://mui.com/x/api/data-grid?${urlParams.toString()}`, - ); + const url = `https://mui.com/x/api/data-grid?${urlParams.toString()}`; + fetchRowsSpy(url); + const getRowsResponse = await fetchRows(url); 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, - pagination: true, - pageSizeOptions: [10], - }; + }; + }, [fetchRows]); return (
- +
); } - // eslint-disable-next-line mocha/no-top-level-hooks - beforeEach(() => { - cache.clear(); - }); - it('should fetch the data on initial render', async () => { render(); await waitFor(() => { @@ -128,19 +140,32 @@ describeSkipIf(isJSDOM)(' - Data source', () => { }); }); + it('should re-fetch the data once if multiple models have changed', async () => { + const { setProps } = render(); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + + setProps({ + paginationModel: { page: 1, pageSize: 10 }, + sortModel: [{ field: 'name', sort: 'asc' }], + filterModel: { items: [{ field: 'name', value: 'John', operator: 'contains' }] }, + }); + + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); + }); + describe('Cache', () => { it('should cache the data using the default cache', async () => { - render(); + const pageChangeSpy = spy(); + 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; + expect(pageChangeSpy.callCount).to.equal(0); act(() => { apiRef.current?.setPage(1); @@ -149,50 +174,55 @@ describeSkipIf(isJSDOM)(' - Data source', () => { 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); + expect(pageChangeSpy.callCount).to.equal(1); 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); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); + expect(pageChangeSpy.callCount).to.equal(2); }); it('should cache the data using the custom cache', async () => { + const testCache = new TestCache(); render(); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(1); }); - expect(cache.size).to.equal(1); + expect(testCache.size()).to.equal(1); }); - it('should use the cached data when the same query is made again', async () => { - render(); + it('should cache the data in the chunks defined by the minimum page size', async () => { + const testCache = new TestCache(); + render( + , + ); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(1); }); - expect(cache.size).to.equal(1); + expect(testCache.size()).to.equal(2); // 2 chunks of 10 rows + }); - const dataRow1 = await screen.findByText( - (_, el) => el?.getAttribute('data-rowindex') === '0', + it('should use the cached data when the same query is made again', async () => { + const testCache = new TestCache(); + const pageChangeSpy = spy(); + render( + , ); - - const cell1 = within(dataRow1).getByRole('gridcell'); - - const cell1Content = cell1.innerText; + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + expect(testCache.size()).to.equal(1); + expect(pageChangeSpy.callCount).to.equal(0); act(() => { apiRef.current?.setPage(1); @@ -201,48 +231,31 @@ describeSkipIf(isJSDOM)(' - Data source', () => { 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); + await waitFor(() => { + expect(testCache.size()).to.equal(2); + }); + expect(pageChangeSpy.callCount).to.equal(1); 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); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); + expect(testCache.size()).to.equal(2); + expect(pageChangeSpy.callCount).to.equal(2); }); it('should allow to disable the default cache', async () => { - // only - render(); + const pageChangeSpy = spy(); + 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; + expect(pageChangeSpy.callCount).to.equal(0); act(() => { apiRef.current?.setPage(1); @@ -251,15 +264,7 @@ describeSkipIf(isJSDOM)(' - Data source', () => { 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); + expect(pageChangeSpy.callCount).to.equal(1); act(() => { apiRef.current?.setPage(0); @@ -268,6 +273,7 @@ describeSkipIf(isJSDOM)(' - Data source', () => { await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(3); }); + expect(pageChangeSpy.callCount).to.equal(2); }); }); 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 26fae9e21c748..4f0864c7fb28d 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 @@ -1,6 +1,6 @@ import * as React from 'react'; import { useMockServer } from '@mui/x-data-grid-generator'; -import { createRenderer, waitFor } from '@mui/internal-test-utils'; +import { act, createRenderer, waitFor } from '@mui/internal-test-utils'; import { getRow } from 'test/utils/helperFn'; import { expect } from 'chai'; import { RefObject } from '@mui/x-internals/types'; @@ -13,17 +13,17 @@ import { GridGetRowsResponse, useGridApiRef, } from '@mui/x-data-grid-pro'; -import { SinonSpy, spy } from 'sinon'; +import { spy } from 'sinon'; import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; // Needs layout describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { const { render } = createRenderer(); const defaultTransformGetRowsResponse = (response: GridGetRowsResponse) => response; + const fetchRowsSpy = spy(); let transformGetRowsResponse: (response: GridGetRowsResponse) => GridGetRowsResponse; let apiRef: RefObject; - let fetchRowsSpy: SinonSpy; let mockServer: ReturnType; function TestDataSourceLazyLoader(props: Partial) { @@ -32,11 +32,12 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { { rowLength: 100, maxColumns: 1 }, { useCursorPagination: false, minDelay: 0, maxDelay: 0, verbose: false }, ); - fetchRowsSpy = spy(mockServer, 'fetchRows'); + const { fetchRows } = mockServer; - const dataSource: GridDataSource = React.useMemo( - () => ({ + const dataSource: GridDataSource = React.useMemo(() => { + fetchRowsSpy.resetHistory(); + return { getRows: async (params: GridGetRowsParams) => { const urlParams = new URLSearchParams({ filterModel: JSON.stringify(params.filterModel), @@ -45,9 +46,9 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { end: `${params.end}`, }); - const getRowsResponse = await fetchRows( - `https://mui.com/x/api/data-grid?${urlParams.toString()}`, - ); + const url = `https://mui.com/x/api/data-grid?${urlParams.toString()}`; + fetchRowsSpy(url); + const getRowsResponse = await fetchRows(url); const response = transformGetRowsResponse(getRowsResponse); return { @@ -55,21 +56,20 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { rowCount: response.rowCount, }; }, - }), - [fetchRows], - ); - - const baselineProps = { - unstable_dataSource: dataSource, - columns: mockServer.columns, - unstable_lazyLoading: true, - paginationModel: { page: 0, pageSize: 10 }, - disableVirtualization: true, - }; + }; + }, [fetchRows]); return (
- +
); } @@ -86,6 +86,22 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { }); }); + it('should re-fetch the data once if multiple models have changed', async () => { + const { setProps } = render(); + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(1); + }); + + setProps({ + sortModel: [{ field: 'name', sort: 'asc' }], + filterModel: { items: [{ field: 'name', value: 'John', operator: 'contains' }] }, + }); + + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); + }); + describe('Viewport loading', () => { it('should render skeleton rows if rowCount is bigger than the number of rows', async () => { render(); @@ -104,7 +120,9 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { // reset the spy call count fetchRowsSpy.resetHistory(); - apiRef.current?.scrollToIndexes({ rowIndex: 10 }); + act(() => { + apiRef.current?.scrollToIndexes({ rowIndex: 50 }); + }); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(1); @@ -119,7 +137,9 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { const initialSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; expect(initialSearchParams.get('end')).to.equal('9'); - apiRef.current?.scrollToIndexes({ rowIndex: 10 }); + act(() => { + apiRef.current?.scrollToIndexes({ rowIndex: 10 }); + }); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(2); @@ -128,7 +148,9 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { const beforeSortSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; expect(beforeSortSearchParams.get('end')).to.not.equal('9'); - apiRef.current?.sortColumn(mockServer.columns[0].field, 'asc'); + act(() => { + apiRef.current?.sortColumn(mockServer.columns[0].field, 'asc'); + }); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(3); @@ -143,7 +165,9 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { // wait until the rows are rendered await waitFor(() => expect(getRow(0)).not.to.be.undefined); - apiRef.current?.scrollToIndexes({ rowIndex: 10 }); + act(() => { + apiRef.current?.scrollToIndexes({ rowIndex: 10 }); + }); await waitFor(() => { expect(fetchRowsSpy.callCount).to.equal(2); @@ -153,14 +177,16 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { // first row is not the first page anymore expect(beforeFilteringSearchParams.get('start')).to.not.equal('0'); - apiRef.current?.setFilterModel({ - items: [ - { - field: mockServer.columns[0].field, - value: '0', - operator: 'contains', - }, - ], + act(() => { + apiRef.current?.setFilterModel({ + items: [ + { + field: mockServer.columns[0].field, + value: '0', + operator: 'contains', + }, + ], + }); }); await waitFor(() => { @@ -197,8 +223,12 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { fetchRowsSpy.resetHistory(); // make one small and one big scroll that makes sure that the bottom of the grid window is reached - apiRef.current?.scrollToIndexes({ rowIndex: 1 }); - apiRef.current?.scrollToIndexes({ rowIndex: 9 }); + act(() => { + apiRef.current?.scrollToIndexes({ rowIndex: 1 }); + }); + act(() => { + apiRef.current?.scrollToIndexes({ rowIndex: 9 }); + }); // Only one additional fetch should have been made await waitFor(() => { @@ -211,7 +241,14 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { // wait until the rows are rendered await waitFor(() => expect(getRow(0)).not.to.be.undefined); - apiRef.current?.scrollToIndexes({ rowIndex: 9 }); + act(() => { + apiRef.current?.scrollToIndexes({ rowIndex: 9 }); + }); + + // wait until the debounced fetch + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); // wait until the rows are rendered await waitFor(() => expect(getRow(10)).not.to.be.undefined); @@ -220,7 +257,13 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { // last row is not the first page anymore expect(beforeSortingSearchParams.get('end')).to.not.equal('9'); - apiRef.current?.sortColumn(mockServer.columns[0].field, 'asc'); + act(() => { + apiRef.current?.sortColumn(mockServer.columns[0].field, 'asc'); + }); + + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(3); + }); const afterSortingSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; // last row is the end of the first page @@ -232,7 +275,14 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { // wait until the rows are rendered await waitFor(() => expect(getRow(0)).not.to.be.undefined); - apiRef.current?.scrollToIndexes({ rowIndex: 9 }); + act(() => { + apiRef.current?.scrollToIndexes({ rowIndex: 9 }); + }); + + // wait until the debounced fetch + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(2); + }); // wait until the rows are rendered await waitFor(() => expect(getRow(10)).not.to.be.undefined); @@ -241,14 +291,20 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { // last row is not the first page anymore expect(beforeFilteringSearchParams.get('end')).to.not.equal('9'); - apiRef.current?.setFilterModel({ - items: [ - { - field: mockServer.columns[0].field, - value: '0', - operator: 'contains', - }, - ], + act(() => { + apiRef.current?.setFilterModel({ + items: [ + { + field: mockServer.columns[0].field, + value: '0', + operator: 'contains', + }, + ], + }); + }); + + await waitFor(() => { + expect(fetchRowsSpy.callCount).to.equal(3); }); const afterFilteringSearchParams = new URL(fetchRowsSpy.lastCall.args[0]).searchParams; @@ -272,7 +328,9 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { setProps({ rowCount: 100 }); // The 11th row should be a skeleton - expect(getRow(10).dataset.id).to.equal('auto-generated-skeleton-row-root-10'); + await waitFor(() => + expect(getRow(10).dataset.id).to.equal('auto-generated-skeleton-row-root-10'), + ); }); it('should reset the grid if the rowCount becomes unknown', async () => { @@ -305,11 +363,15 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { fetchRowsSpy.resetHistory(); // reduce the rowCount to be more than the number of rows - apiRef.current?.setRowCount(80); + act(() => { + apiRef.current?.setRowCount(80); + }); expect(fetchRowsSpy.callCount).to.equal(0); // reduce the rowCount once more, but now to be less than the number of rows - apiRef.current?.setRowCount(20); + act(() => { + apiRef.current?.setRowCount(20); + }); await waitFor(() => expect(fetchRowsSpy.callCount).to.equal(1)); }); @@ -324,7 +386,9 @@ describeSkipIf(isJSDOM)(' - Data source lazy loader', () => { expect(() => getRow(10)).to.throw(); // set the rowCount via API - apiRef.current?.setRowCount(100); + act(() => { + apiRef.current?.setRowCount(100); + }); // wait until the rows are added await waitFor(() => expect(getRow(10)).not.to.be.undefined); 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 556567465f326..4bcb6c8de77a1 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 @@ -13,7 +13,7 @@ import { GridGroupNode, useGridApiRef, } from '@mui/x-data-grid-pro'; -import { SinonSpy, spy } from 'sinon'; +import { spy } from 'sinon'; import { getCell } from 'test/utils/helperFn'; import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; @@ -30,19 +30,21 @@ const serverOptions = { minDelay: 0, maxDelay: 0, verbose: false }; // Needs layout describeSkipIf(isJSDOM)(' - Data source tree data', () => { const { render } = createRenderer(); + const fetchRowsSpy = spy(); let apiRef: RefObject; - 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 { columns } = mockServer; - const dataSource: GridDataSource = React.useMemo( - () => ({ + const { fetchRows } = mockServer; + + const dataSource: GridDataSource = React.useMemo(() => { + fetchRowsSpy.resetHistory(); + return { getRows: async (params: GridGetRowsParams) => { const urlParams = new URLSearchParams({ paginationModel: JSON.stringify(params.paginationModel), @@ -51,9 +53,9 @@ describeSkipIf(isJSDOM)(' - Data source tree data', () => { groupKeys: JSON.stringify(params.groupKeys), }); - const getRowsResponse = await fetchRows( - `https://mui.com/x/api/data-grid?${urlParams.toString()}`, - ); + const url = `https://mui.com/x/api/data-grid?${urlParams.toString()}`; + fetchRowsSpy(url); + const getRowsResponse = await fetchRows(url); return { rows: getRowsResponse.rows, @@ -62,9 +64,8 @@ describeSkipIf(isJSDOM)(' - Data source tree data', () => { }, getGroupKey: (row) => row[dataSetOptions.treeData.groupingField], getChildrenCount: (row) => row.descendantCount, - }), - [fetchRows], - ); + }; + }, [fetchRows]); return (