From f0fdd4c8a25f5a9dd6392fa14f8a66616d5adfc3 Mon Sep 17 00:00:00 2001 From: David Regla Date: Sun, 1 Feb 2026 04:43:27 +0000 Subject: [PATCH] Fix: Expose contexts in DataTable loading and empty states This change ensures that custom loading and empty components in DataTable have access to ResourceContext and other standard contexts. Also added comprehensive unit tests for DataTableBase to verify context availability and correct rendering behavior for empty and refreshing states. --- .../src/dataTable/DataTableBase.spec.tsx | 200 ++++++++++++++++++ .../ra-core/src/dataTable/DataTableBase.tsx | 70 +++--- 2 files changed, 235 insertions(+), 35 deletions(-) create mode 100644 packages/ra-core/src/dataTable/DataTableBase.spec.tsx diff --git a/packages/ra-core/src/dataTable/DataTableBase.spec.tsx b/packages/ra-core/src/dataTable/DataTableBase.spec.tsx new file mode 100644 index 00000000000..7ec6f2d25af --- /dev/null +++ b/packages/ra-core/src/dataTable/DataTableBase.spec.tsx @@ -0,0 +1,200 @@ +import * as React from 'react'; +import expect from 'expect'; +import { render, screen } from '@testing-library/react'; +import { DataTableBase } from './DataTableBase'; +import { ResourceContextProvider, useResourceContext } from '../core'; +import { ListContextProvider } from '../controller'; + +describe('', () => { + const defaultListContext = { + resource: 'posts', + data: [], + total: 0, + isPending: false, + isFetching: false, + sort: { field: 'id', order: 'ASC' }, + }; + + it('should render children when data is present', () => { + render( + + + Empty} + loading={
Loading
} + hasBulkActions={false} + > +
Child Content
+
+
+
+ ); + + expect(screen.queryByText('Child Content')).not.toBeNull(); + expect(screen.queryByText('Loading')).toBeNull(); + expect(screen.queryByText('Empty')).toBeNull(); + }); + + it('should render empty component when no data', () => { + render( + + + Empty Component} + loading={
Loading
} + hasBulkActions={false} + > +
Child
+
+
+
+ ); + + expect(screen.queryByText('Empty Component')).not.toBeNull(); + expect(screen.queryByText('Child')).toBeNull(); + }); + + it('should render loading component when pending', () => { + render( + + + Empty} + loading={
Loading Component
} + hasBulkActions={false} + > +
Child
+
+
+
+ ); + + expect(screen.queryByText('Loading Component')).not.toBeNull(); + expect(screen.queryByText('Child')).toBeNull(); + }); + + it('should display the empty component instead of the table header when loaded with no data', () => { + render( + + + Empty Component} + loading={
Loading
} + hasBulkActions={false} + > +
Table Header and Body
+
+
+
+ ); + + expect(screen.queryByText('Empty Component')).not.toBeNull(); + expect(screen.queryByText('Table Header and Body')).toBeNull(); + }); + + it('should display the current data when data is refreshing', () => { + render( + + + Empty} + loading={
Loading Component
} + hasBulkActions={false} + > +
Child Content
+
+
+
+ ); + + expect(screen.queryByText('Child Content')).not.toBeNull(); + expect(screen.queryByText('Loading Component')).toBeNull(); + }); + + it('should provide ResourceContext to the loading component', () => { + const Loading = () => { + const resource = useResourceContext(); + return
Loading resource: {resource}
; + }; + + render( + + + Empty} + loading={} + hasBulkActions={false} + > +
Child
+
+
+
+ ); + + expect(screen.queryByText('Loading resource: posts')).not.toBeNull(); + }); + + it('should provide ResourceContext to the empty component', () => { + const Empty = () => { + const resource = useResourceContext(); + return
Empty resource: {resource}
; + }; + + render( + + + } + loading={
Loading
} + hasBulkActions={false} + > +
Child
+
+
+
+ ); + + expect(screen.queryByText('Empty resource: posts')).not.toBeNull(); + }); +}); diff --git a/packages/ra-core/src/dataTable/DataTableBase.tsx b/packages/ra-core/src/dataTable/DataTableBase.tsx index 6db42fb568b..34024a5f655 100644 --- a/packages/ra-core/src/dataTable/DataTableBase.tsx +++ b/packages/ra-core/src/dataTable/DataTableBase.tsx @@ -145,43 +145,43 @@ export const DataTableBase = function DataTable< ] ); - if (isPending === true) { - return loading; - } - - /** - * Once loaded, the data for the list may be empty. Instead of - * displaying the table header with zero data rows, - * the DataTable displays the empty component. - */ - if (data == null || data.length === 0 || total === 0) { - return empty ?? null; - } - - /** - * After the initial load, if the data for the list isn't empty, - * and even if the data is refreshing (e.g. after a filter change), - * the DataTable displays the current data. - */ return ( - - - - - - - {children} - - - - - - + + + {isPending ? ( + loading + ) : data == null || data.length === 0 || total === 0 ? ( + /** + * Once loaded, the data for the list may be empty. + * Instead of displaying the table header + * with zero data rows, the DataTable + * displays the empty component. + */ + empty + ) : ( + /** + * After the initial load, if the data for the list + * isn't empty, and even if the data is refreshing + * (e.g. after a filter change), the DataTable + * displays the current data. + */ + + + + + {children} + + + + + )} + + ); };