Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DataGrid] Improve test coverage of server side data source #15942

Merged
merged 8 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type GridDataSourceCacheDefaultConfig = {
ttl?: number;
};

function getKey(params: GridGetRowsParams) {
export function getKey(params: GridGetRowsParams) {
return JSON.stringify([
params.filterModel,
params.sortModel,
Expand Down
282 changes: 282 additions & 0 deletions packages/x-data-grid-pro/src/tests/dataSource.DataGridPro.test.tsx
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @arminmeh for the test bed introduced in lazy loading PR, I reused it here.

Original file line number Diff line number Diff line change
@@ -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<string, GridGetRowsResponse>();

const testCache: GridDataSourceCache = {
set: (key, value) => cache.set(getKey(key), value),
get: (key) => cache.get(getKey(key)),
clear: () => cache.clear(),
};

describe('<DataGridPro /> - Data source', () => {
const { render } = createRenderer();

let apiRef: React.MutableRefObject<GridApi>;
let fetchRowsSpy: SinonSpy;
let mockServer: ReturnType<typeof useMockServer>;

function TestDataSource(props: Partial<DataGridProProps> & { 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 (
<div style={{ width: 300, height: 300 }}>
<DataGridPro apiRef={apiRef} {...baselineProps} {...rest} />
</div>
);
}

beforeEach(function beforeTest() {
if (isJSDOM) {
this.skip(); // Needs layout
}

cache.clear();
});

it('should fetch the data on initial render', async () => {
render(<TestDataSource />);
await waitFor(() => {
expect(fetchRowsSpy.callCount).to.equal(1);
});
});

it('should re-fetch the data on filter change', async () => {
const { setProps } = render(<TestDataSource />);
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(<TestDataSource />);
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(<TestDataSource />);
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(<TestDataSource />);
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(<TestDataSource unstable_dataSourceCache={testCache} />);
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(<TestDataSource unstable_dataSourceCache={testCache} />);
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(<TestDataSource unstable_dataSourceCache={null} />);
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(<TestDataSource unstable_onDataSourceError={onDataSourceError} shouldRequestsFail />);
await waitFor(() => {
expect(onDataSourceError.callCount).to.equal(1);
});
});
});
});
Loading
Loading