From 972e9dddfbdb918293898c1ff2c79d703fd278ca Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Wed, 20 Nov 2024 21:57:26 +0100 Subject: [PATCH 001/146] Add a "Select all" button + make it customizable + document jsDoc --- .../src/controller/list/ListContext.tsx | 2 + .../list/useListContextWithProps.ts | 6 +++ .../src/controller/list/useListController.ts | 50 +++++++++++++++++++ packages/ra-language-english/src/index.ts | 13 ++--- .../src/list/BulkActionsToolbar.tsx | 20 +++++++- packages/ra-ui-materialui/src/list/List.tsx | 3 ++ 6 files changed, 87 insertions(+), 7 deletions(-) diff --git a/packages/ra-core/src/controller/list/ListContext.tsx b/packages/ra-core/src/controller/list/ListContext.tsx index f4af11d4495..10d0d19fc2c 100644 --- a/packages/ra-core/src/controller/list/ListContext.tsx +++ b/packages/ra-core/src/controller/list/ListContext.tsx @@ -24,7 +24,9 @@ import { ListControllerResult } from './useListController'; * @prop {Function} showFilter a callback to show one of the filters, e.g. showFilter('title', defaultValue) * @prop {Function} hideFilter a callback to hide one of the filters, e.g. hideFilter('title') * @prop {Array} selectedIds an array listing the ids of the selected rows, e.g. [123, 456] + * @prop {boolean} areAllSelected boolean to indicate if the list is already fully selected * @prop {Function} onSelect callback to change the list of selected rows, e.g. onSelect([456, 789]) + * @prop {Function} onSelectAll callback to select all the records, e.g. onSelectAll() * @prop {Function} onToggleItem callback to toggle the selection of a given record based on its id, e.g. onToggleItem(456) * @prop {Function} onUnselectItems callback to clear the selection, e.g. onUnselectItems(); * @prop {string} defaultTitle the translated title based on the resource, e.g. 'Posts' diff --git a/packages/ra-core/src/controller/list/useListContextWithProps.ts b/packages/ra-core/src/controller/list/useListContextWithProps.ts index 206be897b56..d4bcef919b2 100644 --- a/packages/ra-core/src/controller/list/useListContextWithProps.ts +++ b/packages/ra-core/src/controller/list/useListContextWithProps.ts @@ -33,7 +33,9 @@ import { RaRecord } from '../../types'; * @prop {Function} showFilter a callback to show one of the filters, e.g. showFilter('title', defaultValue) * @prop {Function} hideFilter a callback to hide one of the filters, e.g. hideFilter('title') * @prop {Array} selectedIds an array listing the ids of the selected rows, e.g. [123, 456] + * @prop {boolean} areAllSelected boolean to indicate if the list is already fully selected * @prop {Function} onSelect callback to change the list of selected rows, e.g. onSelect([456, 789]) + * @prop {Function} onSelectAll callback to select all the records, e.g. onSelectAll() * @prop {Function} onToggleItem callback to toggle the selection of a given record based on its id, e.g. onToggleItem(456) * @prop {Function} onUnselectItems callback to clear the selection, e.g. onUnselectItems(); * @prop {string} defaultTitle the translated title based on the resource, e.g. 'Posts' @@ -81,6 +83,7 @@ const extractListContextProps = ({ isLoading, isPending, onSelect, + onSelectAll, onToggleItem, onUnselectItems, page, @@ -88,6 +91,7 @@ const extractListContextProps = ({ refetch, resource, selectedIds, + areAllSelected, setFilters, setPage, setPerPage, @@ -107,6 +111,7 @@ const extractListContextProps = ({ isLoading, isPending, onSelect, + onSelectAll, onToggleItem, onUnselectItems, page, @@ -114,6 +119,7 @@ const extractListContextProps = ({ refetch, resource, selectedIds, + areAllSelected, setFilters, setPage, setPerPage, diff --git a/packages/ra-core/src/controller/list/useListController.ts b/packages/ra-core/src/controller/list/useListController.ts index 0e69f46e133..d6fe16e515f 100644 --- a/packages/ra-core/src/controller/list/useListController.ts +++ b/packages/ra-core/src/controller/list/useListController.ts @@ -46,6 +46,7 @@ export const useListController = ( queryOptions = {}, sort = defaultSort, storeKey, + selectAllLimit = 250, } = props; const resource = useResourceContext(props); const { meta, ...otherQueryOptions } = queryOptions; @@ -168,6 +169,38 @@ export const useListController = ( name: getResourceLabel(resource, 2), }); + const { data: allData } = useGetList( + resource, + { + pagination: { + page: 1, + perPage: selectAllLimit, + }, + sort: { field: query.sort, order: query.order }, + filter: { ...query.filter, ...filter }, + meta, + }, + { + enabled: + (!isPendingAuthenticated && !isPendingCanAccess) || + disableAuthentication, + retry: false, + ...otherQueryOptions, + } + ); + + const onSelectAll = () => { + console.log('onSelectAll', allData); + const allIds = allData?.map(({ id }) => id) || []; + selectionModifiers.select(allIds); + if (allIds.length === selectAllLimit) { + notify('ra.message.too_many_elements', { + messageArgs: { max: selectAllLimit }, + type: 'warning', + }); + } + }; + return { sort: currentSort, data, @@ -183,6 +216,7 @@ export const useListController = ( isLoading, isPending, onSelect: selectionModifiers.select, + onSelectAll, onToggleItem: selectionModifiers.toggle, onUnselectItems: selectionModifiers.clearSelection, page: query.page, @@ -190,6 +224,7 @@ export const useListController = ( refetch, resource, selectedIds, + areAllSelected: allData?.length !== selectedIds.length, setFilters: queryModifiers.setFilters, setPage: queryModifiers.setPage, setPerPage: queryModifiers.setPerPage, @@ -409,6 +444,19 @@ export interface ListControllerProps { * ); */ storeKey?: string | false; + + /** + * The number of items selected by the "SELECT ALL" button of the bulk actions toolbar. + * + * @see https://marmelab.com/react-admin/List.html#selectalllimit + * @example + * export const PostList = () => ( + * + * ... + * + * ); + */ + selectAllLimit?: number; } const defaultSort = { @@ -475,6 +523,7 @@ export interface ListControllerBaseResult { filterValues: any; hideFilter: (filterName: string) => void; onSelect: (ids: RecordType['id'][]) => void; + onSelectAll: () => void; onToggleItem: (id: RecordType['id']) => void; onUnselectItems: () => void; page: number; @@ -482,6 +531,7 @@ export interface ListControllerBaseResult { refetch: (() => void) | UseGetListHookValue['refetch']; resource: string; selectedIds: RecordType['id'][]; + areAllSelected: boolean; setFilters: ( filters: any, displayedFilters?: any, diff --git a/packages/ra-language-english/src/index.ts b/packages/ra-language-english/src/index.ts index 704b30e6919..81422c1a7da 100644 --- a/packages/ra-language-english/src/index.ts +++ b/packages/ra-language-english/src/index.ts @@ -87,7 +87,11 @@ const englishMessages: TranslationMessages = { }, message: { about: 'About', + access_denied: + "You don't have the right permissions to access this page", are_you_sure: 'Are you sure?', + authentication_error: + 'The authentication server returned an error and your credentials could not be checked.', auth_error: 'An error occurred while validating the authentication token.', bulk_delete_content: @@ -103,19 +107,16 @@ const englishMessages: TranslationMessages = { delete_title: 'Delete %{name} #%{id}', details: 'Details', error: "A client error occurred and your request couldn't be completed.", - invalid_form: 'The form is not valid. Please check for errors', loading: 'Please wait', no: 'No', not_found: 'Either you typed a wrong URL, or you followed a bad link.', - yes: 'Yes', + too_many_elements: + 'Warning: There are too many elements to select them all. Only the first %{max} elements were selected.', unsaved_changes: "Some of your changes weren't saved. Are you sure you want to ignore them?", - access_denied: - "You don't have the right permissions to access this page", - authentication_error: - 'The authentication server returned an error and your credentials could not be checked.', + yes: 'Yes', }, navigation: { clear_filters: 'Clear filters', diff --git a/packages/ra-ui-materialui/src/list/BulkActionsToolbar.tsx b/packages/ra-ui-materialui/src/list/BulkActionsToolbar.tsx index 1d42530c1f6..b39eaf69ceb 100644 --- a/packages/ra-ui-materialui/src/list/BulkActionsToolbar.tsx +++ b/packages/ra-ui-materialui/src/list/BulkActionsToolbar.tsx @@ -10,6 +10,7 @@ import CloseIcon from '@mui/icons-material/Close'; import { useTranslate, sanitizeListRestProps, useListContext } from 'ra-core'; import TopToolbar from '../layout/TopToolbar'; +import { Button } from '../button'; export const BulkActionsToolbar = (props: BulkActionsToolbarProps) => { const { @@ -18,7 +19,12 @@ export const BulkActionsToolbar = (props: BulkActionsToolbarProps) => { className, ...rest } = props; - const { selectedIds = [], onUnselectItems } = useListContext(); + const { + selectedIds = [], + onUnselectItems, + onSelectAll, + areAllSelected, + } = useListContext(); const translate = useTranslate(); @@ -26,6 +32,10 @@ export const BulkActionsToolbar = (props: BulkActionsToolbarProps) => { onUnselectItems(); }, [onUnselectItems]); + const handleSelectAll = useCallback(() => { + onSelectAll(); + }, [onSelectAll]); + return ( { smart_count: selectedIds.length, })} + {areAllSelected && ( + + ); + }; + afterEach(() => { + fireEvent.click(screen.getByRole('button', { name: 'reset' })); + }); it('should be displayed if an item is selected', async () => { - render(); + render( + + + + ); await waitFor(() => { expect(screen.queryAllByRole('checkbox')).toHaveLength(11); }); @@ -377,7 +395,9 @@ describe('', () => { total: 2, }), })} - /> + > + + ); await waitFor(() => { expect(screen.queryAllByRole('checkbox')).toHaveLength(3); @@ -389,7 +409,11 @@ describe('', () => { ).toBeNull(); }); it('should not be displayed if all item are selected with the "Select all" button', async () => { - render(); + render( + + + + ); await waitFor(() => { expect(screen.queryAllByRole('checkbox')).toHaveLength(11); }); @@ -431,7 +455,9 @@ describe('', () => { total: 3, }), })} - /> + > + + ); await waitFor(() => { expect(screen.queryAllByRole('checkbox')).toHaveLength(4); @@ -444,7 +470,11 @@ describe('', () => { ).toBeNull(); }); it('should not be displayed if we reached de selectAllLimit by a click on the "Select all" button', async () => { - render(); + render( + + + + ); await waitFor(() => { expect(screen.queryAllByRole('checkbox')).toHaveLength(11); }); @@ -460,7 +490,11 @@ describe('', () => { ).toBeNull(); }); it('should select all items', async () => { - render(); + render( + + + + ); await waitFor(() => { expect(screen.queryAllByRole('checkbox')).toHaveLength(11); }); @@ -470,7 +504,11 @@ describe('', () => { await screen.findByText('13 items selected'); }); it('should select the maximum items possible until we reached the selectAllLimit', async () => { - render(); + render( + + + + ); await waitFor(() => { expect(screen.queryAllByRole('checkbox')).toHaveLength(11); }); diff --git a/packages/ra-ui-materialui/src/list/List.stories.tsx b/packages/ra-ui-materialui/src/list/List.stories.tsx index 3c315ae0eb8..4583c30033e 100644 --- a/packages/ra-ui-materialui/src/list/List.stories.tsx +++ b/packages/ra-ui-materialui/src/list/List.stories.tsx @@ -299,6 +299,7 @@ export const HasCreate = () => ( export const SelectAllLimit = ({ dataProvider = defaultDataProvider, selectAllLimit = 11, + children, }) => ( @@ -312,6 +313,7 @@ export const SelectAllLimit = ({ + {children} )} /> @@ -489,7 +491,7 @@ export const Meta = () => ( ); -export const Default = ({ dataProvider = defaultDataProvider }) => ( +export const Default = ({ dataProvider = defaultDataProvider, children }) => ( ( + {children} )} /> From 91627613437d45409c45b7e1e842506aeda94190 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Tue, 26 Nov 2024 15:52:44 +0100 Subject: [PATCH 033/146] fix typos --- docs/List.md | 2 +- docs/ReferenceManyField.md | 2 +- .../field/useReferenceArrayFieldController.spec.tsx | 8 ++++---- packages/ra-ui-materialui/src/list/List.spec.tsx | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/List.md b/docs/List.md index a197a6c34f3..a99256788ea 100644 --- a/docs/List.md +++ b/docs/List.md @@ -71,7 +71,7 @@ You can find more advanced examples of `` usage in the [demos](./Demos.md) | `perPage` | Optional | `number` | `10` | The number of records to fetch per page. | | `queryOptions` | Optional | `object` | - | The options to pass to the `useQuery` hook. | | `resource` | Optional | `string` | - | The resource name, e.g. `posts`. | -| `selectAllLimit` | Optional | `number` | - | The number of items selected by the "SELECT ALL" button of the bulk actions toolbar. | +| `selectAllLimit` | Optional | `number` | - | The maximum number of items selected by the "SELECT ALL" button of the bulk actions toolbar. | | `sort` | Optional | `object` | - | The initial sort parameters. | | `storeKey` | Optional | `string | false` | - | The key to use to store the current filter & sort. Pass `false` to disable store synchronization | | `title` | Optional | `string | ReactElement | false` | - | The title to display in the App Bar. | diff --git a/docs/ReferenceManyField.md b/docs/ReferenceManyField.md index ea94b646cb6..d6c0aadae9e 100644 --- a/docs/ReferenceManyField.md +++ b/docs/ReferenceManyField.md @@ -91,7 +91,7 @@ This example leverages [``](./SingleFieldList.md) to display an | `perPage` | Optional | `number` | 25 | Maximum number of referenced records to fetch | | `queryOptions` | Optional | [`UseQuery Options`](https://tanstack.com/query/v3/docs/react/reference/useQuery) | `{}` | `react-query` options for the `getMany` query | | `reference` | Required | `string` | - | The name of the resource for the referenced records, e.g. 'books' | -| `selectAllLimit` | Optional | `number` | - | The number of items selected by the "SELECT ALL" button of the bulk actions toolbar. | +| `selectAllLimit` | Optional | `number` | - | The maximum number of items selected by the "SELECT ALL" button of the bulk actions toolbar. | | `sort` | Optional | `{ field, order }` | `{ field: 'id', order: 'DESC' }` | Sort order to use when fetching the related records, passed to `getManyReference()` | | `source` | Optional | `string` | `id` | Target field carrying the relationship on the source record (usually 'id') | | `storeKey` | Optional | `string` | - | The key to use to store the records selection state | diff --git a/packages/ra-core/src/controller/field/useReferenceArrayFieldController.spec.tsx b/packages/ra-core/src/controller/field/useReferenceArrayFieldController.spec.tsx index d5562131b7c..0c57813eea7 100644 --- a/packages/ra-core/src/controller/field/useReferenceArrayFieldController.spec.tsx +++ b/packages/ra-core/src/controller/field/useReferenceArrayFieldController.spec.tsx @@ -168,7 +168,7 @@ describe('', () => { }); describe('displaySelectAllButton', () => { - it('should return true if no items are selected', async () => { + it('should be true if no items are selected', async () => { const children = jest.fn().mockReturnValue('child'); render( @@ -216,7 +216,7 @@ describe('', () => { ); }); }); - it('should return true if some items are selected', async () => { + it('should be true if some items are selected', async () => { const children = jest.fn().mockReturnValue('child'); render( @@ -285,7 +285,7 @@ describe('', () => { ); }); }); - it('should return false if all items are manually selected', async () => { + it('should be false if all items are manually selected', async () => { const children = jest.fn().mockReturnValue('child'); render( @@ -354,7 +354,7 @@ describe('', () => { ); }); }); - it('should return false if all items are selected with onSelectAll', async () => { + it('should be false if all items are selected with onSelectAll', async () => { const children = jest.fn().mockReturnValue('child'); render( diff --git a/packages/ra-ui-materialui/src/list/List.spec.tsx b/packages/ra-ui-materialui/src/list/List.spec.tsx index 8c6aad6e3b8..edaa03b1a29 100644 --- a/packages/ra-ui-materialui/src/list/List.spec.tsx +++ b/packages/ra-ui-materialui/src/list/List.spec.tsx @@ -372,7 +372,7 @@ describe('', () => { await screen.findByRole('button', { name: 'Select all' }) ).toBeDefined(); }); - it('should not be displayed if all item are manyally selected', async () => { + it('should not be displayed if all item are manually selected', async () => { render( ', () => { screen.queryByRole('button', { name: 'Select all' }) ).toBeNull(); }); - it('should not be displayed if we reached de selectAllLimit by a manyally selection', async () => { + it('should not be displayed if we reached the selectAllLimit by a manual selection', async () => { render( ', () => { screen.queryByRole('button', { name: 'Select all' }) ).toBeNull(); }); - it('should not be displayed if we reached de selectAllLimit by a click on the "Select all" button', async () => { + it('should not be displayed if we reached the selectAllLimit by a click on the "Select all" button', async () => { render( From cadc723d3a325ce5dcde7d753224a32e2cb7ab7d Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Tue, 26 Nov 2024 17:31:47 +0100 Subject: [PATCH 034/146] Simplify tests --- .../useReferenceArrayFieldController.spec.tsx | 200 ++---------- .../useReferenceManyFieldController.spec.tsx | 257 ++-------------- .../list/useInfiniteListController.spec.tsx | 289 ++---------------- .../src/controller/list/useList.spec.tsx | 140 +-------- .../list/useListController.spec.tsx | 284 ++--------------- 5 files changed, 80 insertions(+), 1090 deletions(-) diff --git a/packages/ra-core/src/controller/field/useReferenceArrayFieldController.spec.tsx b/packages/ra-core/src/controller/field/useReferenceArrayFieldController.spec.tsx index 0c57813eea7..7f902ee629c 100644 --- a/packages/ra-core/src/controller/field/useReferenceArrayFieldController.spec.tsx +++ b/packages/ra-core/src/controller/field/useReferenceArrayFieldController.spec.tsx @@ -183,28 +183,7 @@ describe('', () => { ); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: true, data: [ @@ -230,49 +209,11 @@ describe('', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({ - displaySelectAllButton: true, - data: [ - { id: 1, title: 'bar1' }, - { id: 2, title: 'bar2' }, - ], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([1]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: true, data: [ @@ -299,49 +240,11 @@ describe('', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({ - displaySelectAllButton: true, - data: [ - { id: 1, title: 'bar1' }, - { id: 2, title: 'bar2' }, - ], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([1, 2]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: false, data: [ @@ -369,30 +272,7 @@ describe('', () => { ); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: true, data: [ @@ -405,12 +285,10 @@ describe('', () => { ); }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: false, data: [ @@ -441,36 +319,20 @@ describe('', () => { ); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ - selectedIds: [], + data: [ + { id: 1, title: 'bar1' }, + { id: 2, title: 'bar2' }, + ], }) ); }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [1, 2], }) @@ -492,48 +354,20 @@ describe('', () => { ); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ - selectedIds: [], - }) - ); - }); - act(() => { - // @ts-ignore - children.mock.calls.at(-1)[0].onSelect([1]); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, - expect.objectContaining({ - selectedIds: [1], + data: [ + { id: 1, title: 'bar1' }, + { id: 2, title: 'bar2' }, + ], }) ); }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 5, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [1, 2], }) diff --git a/packages/ra-core/src/controller/field/useReferenceManyFieldController.spec.tsx b/packages/ra-core/src/controller/field/useReferenceManyFieldController.spec.tsx index 5f923f3b783..20c84a8de51 100644 --- a/packages/ra-core/src/controller/field/useReferenceManyFieldController.spec.tsx +++ b/packages/ra-core/src/controller/field/useReferenceManyFieldController.spec.tsx @@ -420,7 +420,7 @@ describe('useReferenceManyFieldController', () => { }); describe('displaySelectAllButton', () => { - it('should return true if no items are selected', async () => { + it('should be true if no items are selected', async () => { const children = jest.fn().mockReturnValue('child'); const dataProvider = testDataProvider({ getManyReference: jest.fn().mockResolvedValue({ @@ -442,27 +442,14 @@ describe('useReferenceManyFieldController', () => { ); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: true, - data: undefined, - total: undefined, - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, }) ); }); }); - it('should return true if some items are selected', async () => { + it('should be true if some items are selected', async () => { const children = jest.fn().mockReturnValue('child'); const dataProvider = testDataProvider({ getManyReference: jest.fn().mockResolvedValue({ @@ -483,45 +470,19 @@ describe('useReferenceManyFieldController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([1]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, selectedIds: [1], }) ); }); }); - it('should return false if all items are manually selected', async () => { + it('should be false if all items are manually selected', async () => { const children = jest.fn().mockReturnValue('child'); const dataProvider = testDataProvider({ getManyReference: jest.fn().mockResolvedValue({ @@ -542,45 +503,19 @@ describe('useReferenceManyFieldController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([0, 1]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: false, - data: [{ id: 0 }, { id: 1 }], - total: 2, selectedIds: [0, 1], }) ); }); }); - it('should return false if all items are selected with onSelectAll', async () => { + it('should be false if all items are selected with onSelectAll', async () => { const children = jest.fn().mockReturnValue('child'); const dataProvider = testDataProvider({ getManyReference: jest.fn().mockResolvedValue({ @@ -601,56 +536,19 @@ describe('useReferenceManyFieldController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: false, - data: [{ id: 0 }, { id: 1 }], - total: 2, selectedIds: [0, 1], }) ); }); }); - it('should return false if all we manually reached the selectAllLimit', async () => { + it('should be false if all we manually reached the selectAllLimit', async () => { const children = jest.fn().mockReturnValue('child'); const dataProvider = testDataProvider({ getManyReference: jest.fn().mockResolvedValue({ @@ -672,45 +570,19 @@ describe('useReferenceManyFieldController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([0]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: false, - data: [{ id: 0 }, { id: 1 }], - total: 2, selectedIds: [0], }) ); }); }); - it('should return false if all we reached the selectAllLimit with onSelectAll', async () => { + it('should be false if all we reached the selectAllLimit with onSelectAll', async () => { const children = jest.fn().mockReturnValue('child'); const dataProvider = testDataProvider({ getManyReference: jest @@ -739,50 +611,13 @@ describe('useReferenceManyFieldController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: false, - data: [{ id: 0 }, { id: 1 }], - total: 2, selectedIds: [0], }) ); @@ -813,29 +648,11 @@ describe('useReferenceManyFieldController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [0, 1], }) @@ -864,41 +681,21 @@ describe('useReferenceManyFieldController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([1]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [1], }) ); }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({ - selectedIds: [1], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [0, 1], }) @@ -941,38 +738,18 @@ describe('useReferenceManyFieldController', () => { }) ); }); - await waitFor(() => { - expect(getManyReference).toHaveBeenNthCalledWith( - 1, - 'books', - expect.objectContaining({ - pagination: { page: 1, perPage: 25 }, - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [0], }) ); }); await waitFor(() => { - expect(getManyReference).toHaveBeenNthCalledWith( - 2, + expect(getManyReference).toHaveBeenCalledWith( 'books', expect.objectContaining({ pagination: { page: 1, perPage: 1 }, diff --git a/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx b/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx index d878a11c29e..2e1d357ea3b 100644 --- a/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx +++ b/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx @@ -46,7 +46,7 @@ describe('useInfiniteListController', () => { }; describe('displaySelectAllButton', () => { - it('should return true if no items are selected', async () => { + it('should be true if no items are selected', async () => { const getList = jest .fn() .mockImplementation(() => @@ -64,27 +64,15 @@ describe('useInfiniteListController', () => { ); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: true, data: [{ id: 0 }, { id: 1 }], - total: 2, }) ); }); }); - it('should return true if some items are selected', async () => { + it('should be true if some items are selected', async () => { const getList = jest .fn() .mockImplementation(() => @@ -101,45 +89,19 @@ describe('useInfiniteListController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([0]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, selectedIds: [0], }) ); }); }); - it('should return false if all items are manually selected', async () => { + it('should be false if all items are manually selected', async () => { const getList = jest .fn() .mockImplementation(() => @@ -156,45 +118,19 @@ describe('useInfiniteListController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([0, 1]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: false, - data: [{ id: 0 }, { id: 1 }], - total: 2, selectedIds: [0, 1], }) ); }); }); - it('should return false if all items are selected with onSelectAll', async () => { + it('should be false if all items are selected with onSelectAll', async () => { const getList = jest .fn() .mockImplementation(() => @@ -211,56 +147,19 @@ describe('useInfiniteListController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: false, - data: [{ id: 0 }, { id: 1 }], - total: 2, selectedIds: [0, 1], }) ); }); }); - it('should return false if all we manually reached the selectAllLimit', async () => { + it('should be false if all we manually reached the selectAllLimit', async () => { const getList = jest.fn().mockImplementation(() => Promise.resolve({ data: [{ id: 0 }, { id: 1 }], @@ -279,45 +178,19 @@ describe('useInfiniteListController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([0]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: false, - data: [{ id: 0 }, { id: 1 }], - total: 2, selectedIds: [0], }) ); }); }); - it('should return false if all we reached the selectAllLimit with onSelectAll', async () => { + it('should be false if all we reached the selectAllLimit with onSelectAll', async () => { const getList = jest.fn().mockImplementation((_resource, params) => Promise.resolve({ data: [{ id: 0 }, { id: 1 }].slice( @@ -340,50 +213,13 @@ describe('useInfiniteListController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: false, - data: [{ id: 0 }, { id: 1 }], - total: 2, selectedIds: [0], }) ); @@ -410,37 +246,11 @@ describe('useInfiniteListController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [0, 1], }) @@ -465,49 +275,21 @@ describe('useInfiniteListController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([0]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [0], }) ); }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, - expect.objectContaining({ - selectedIds: [0], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 5, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [0, 1], }) @@ -536,54 +318,18 @@ describe('useInfiniteListController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(getList).toHaveBeenNthCalledWith( - 1, - 'posts', - expect.objectContaining({ - pagination: { page: 1, perPage: 10 }, - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [0], }) ); }); await waitFor(() => { - expect(getList).toHaveBeenNthCalledWith( - 2, + expect(getList).toHaveBeenCalledWith( 'posts', expect.objectContaining({ pagination: { page: 1, perPage: 1 }, @@ -592,6 +338,7 @@ describe('useInfiniteListController', () => { }); }); }); + describe('queryOptions', () => { it('should accept custom client query options', async () => { jest.spyOn(console, 'error').mockImplementationOnce(() => {}); diff --git a/packages/ra-core/src/controller/list/useList.spec.tsx b/packages/ra-core/src/controller/list/useList.spec.tsx index 40f8d5a36af..af78b755399 100644 --- a/packages/ra-core/src/controller/list/useList.spec.tsx +++ b/packages/ra-core/src/controller/list/useList.spec.tsx @@ -317,142 +317,62 @@ describe('', () => { }); describe('displaySelectAllButton', () => { - it('should return true if no items are selected', async () => { + it('should be true if no items are selected', async () => { const children = jest.fn(); const data = [{ id: 0 }, { id: 1 }]; render(); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: true, data: [{ id: 0 }, { id: 1 }], - total: 2, }) ); }); }); - it('should return true if some items are selected', async () => { + it('should be true if some items are selected', async () => { const children = jest.fn(); const data = [{ id: 0 }, { id: 1 }]; render(); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([1]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, selectedIds: [1], }) ); }); }); - it('should return false if all items are manually selected', async () => { + it('should be false if all items are manually selected', async () => { const children = jest.fn(); const data = [{ id: 0 }, { id: 1 }]; render(); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([0, 1]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: false, - data: [{ id: 0 }, { id: 1 }], - total: 2, selectedIds: [0, 1], }) ); }); }); - it('should return false if all items are selected with onSelectAll', async () => { + it('should be false if all items are selected with onSelectAll', async () => { const children = jest.fn(); const data = [{ id: 0 }, { id: 1 }]; render(); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: false, - data: [{ id: 0 }, { id: 1 }], - total: 2, selectedIds: [0, 1], }) ); @@ -465,29 +385,11 @@ describe('', () => { const children = jest.fn(); const data = [{ id: 0 }, { id: 1 }]; render(); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [0, 1], }) @@ -498,41 +400,21 @@ describe('', () => { const children = jest.fn(); const data = [{ id: 0 }, { id: 1 }]; render(); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([1]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [1], }) ); }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [0, 1], }) diff --git a/packages/ra-core/src/controller/list/useListController.spec.tsx b/packages/ra-core/src/controller/list/useListController.spec.tsx index 79b6d794681..e6022edd0e5 100644 --- a/packages/ra-core/src/controller/list/useListController.spec.tsx +++ b/packages/ra-core/src/controller/list/useListController.spec.tsx @@ -587,7 +587,7 @@ describe('useListController', () => { }); describe('displaySelectAllButton', () => { - it('should return true if no items are selected', async () => { + it('should be true if no items are selected', async () => { const getList = jest .fn() .mockImplementation(() => @@ -605,27 +605,15 @@ describe('useListController', () => { ); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: true, data: [{ id: 0 }, { id: 1 }], - total: 2, }) ); }); }); - it('should return true if some items are selected', async () => { + it('should be true if some items are selected', async () => { const getList = jest .fn() .mockImplementation(() => @@ -642,45 +630,19 @@ describe('useListController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([0]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, selectedIds: [0], }) ); }); }); - it('should return false if all items are manually selected', async () => { + it('should be false if all items are manually selected', async () => { const getList = jest .fn() .mockImplementation(() => @@ -697,45 +659,19 @@ describe('useListController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([0, 1]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: false, - data: [{ id: 0 }, { id: 1 }], - total: 2, selectedIds: [0, 1], }) ); }); }); - it('should return false if all items are selected with onSelectAll', async () => { + it('should be false if all items are selected with onSelectAll', async () => { const getList = jest .fn() .mockImplementation(() => @@ -752,46 +688,11 @@ describe('useListController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: false, data: [{ id: 0 }, { id: 1 }], @@ -801,7 +702,7 @@ describe('useListController', () => { ); }); }); - it('should return false if all we manually reached the selectAllLimit', async () => { + it('should be false if all we manually reached the selectAllLimit', async () => { const getList = jest.fn().mockImplementation(() => Promise.resolve({ data: [{ id: 0 }, { id: 1 }], @@ -820,45 +721,19 @@ describe('useListController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([0]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: false, - data: [{ id: 0 }, { id: 1 }], - total: 2, selectedIds: [0], }) ); }); }); - it('should return false if all we reached the selectAllLimit with onSelectAll', async () => { + it('should be false if all we reached the selectAllLimit with onSelectAll', async () => { const getList = jest.fn().mockImplementation((_resource, params) => Promise.resolve({ data: [{ id: 0 }, { id: 1 }].slice( @@ -881,46 +756,11 @@ describe('useListController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - displaySelectAllButton: true, - data: undefined, - total: undefined, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({ - displaySelectAllButton: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ displaySelectAllButton: false, data: [{ id: 0 }, { id: 1 }], @@ -951,37 +791,11 @@ describe('useListController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [0, 1], }) @@ -1006,49 +820,21 @@ describe('useListController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelect([0]); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [0], }) ); }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, - expect.objectContaining({ - selectedIds: [0], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 5, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [0, 1], }) @@ -1077,54 +863,18 @@ describe('useListController', () => { ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(getList).toHaveBeenNthCalledWith( - 1, - 'posts', - expect.objectContaining({ - pagination: { page: 1, perPage: 10 }, - }) - ); - }); act(() => { - // @ts-ignore children.mock.calls.at(-1)[0].onSelectAll(); }); await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 3, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 4, + expect(children).toHaveBeenCalledWith( expect.objectContaining({ selectedIds: [0], }) ); }); await waitFor(() => { - expect(getList).toHaveBeenNthCalledWith( - 2, + expect(getList).toHaveBeenCalledWith( 'posts', expect.objectContaining({ pagination: { page: 1, perPage: 1 }, From 538076c243bc79d451c258458e156daf870d3312 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Tue, 26 Nov 2024 17:50:46 +0100 Subject: [PATCH 035/146] simplify spacings in bulkActionToolbar --- packages/ra-ui-materialui/src/list/BulkActionsToolbar.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/ra-ui-materialui/src/list/BulkActionsToolbar.tsx b/packages/ra-ui-materialui/src/list/BulkActionsToolbar.tsx index 674dd6a0e4d..786e1477a02 100644 --- a/packages/ra-ui-materialui/src/list/BulkActionsToolbar.tsx +++ b/packages/ra-ui-materialui/src/list/BulkActionsToolbar.tsx @@ -66,7 +66,6 @@ export const BulkActionsToolbar = (props: BulkActionsToolbarProps) => { ; + */ +export const useSelectAll = ({ + query, + resource, + selectAllLimit = 250, + filter = {}, + meta = {}, +}: useSelectAllProps): (() => void) => { + const notify = useNotify(); + const dataProvider = useDataProvider(); + const [_, selectionModifiers] = useRecordSelection({ resource }); + const { mutate: onSelectAll } = useMutation({ + mutationFn: () => + dataProvider.getList(resource, { + pagination: { + page: 1, + perPage: selectAllLimit, + }, + sort: { field: query.sort, order: query.order }, + filter: { ...query.filter, ...filter }, + meta, + }), + onSuccess: ({ data }) => { + const allIds = data?.map(({ id }) => id) || []; + selectionModifiers.select(allIds); + if (allIds.length === selectAllLimit) { + notify('ra.message.too_many_elements', { + messageArgs: { max: selectAllLimit }, + type: 'warning', + }); + } + }, + onError: e => { + console.error('Mutation Error: ', e); + notify('An error occurred. Please try again.'); + }, + }); + + return onSelectAll; +}; + +export interface useSelectAllProps { + resource: string; + query: ListParams; + selectAllLimit?: number; + filter?: FilterPayload; + meta?: any; +} From 59024b4ef566ef57075c723f2993b9c07275198c Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Wed, 27 Nov 2024 11:55:45 +0100 Subject: [PATCH 038/146] fix ReferenceArrayInput --- .../ra-ui-materialui/src/input/ReferenceArrayInput.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.tsx b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.tsx index 7815bdec371..34311c16a98 100644 --- a/packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.tsx +++ b/packages/ra-ui-materialui/src/input/ReferenceArrayInput.spec.tsx @@ -192,7 +192,7 @@ describe('', () => { .getByLabelText('ra.action.select_row') .querySelector('input'); const getCheckboxAll = () => - screen.getByLabelText('ra.action.select_all'); + screen.getByRole('checkbox', { name: 'ra.action.select_all' }); await waitFor(() => { expect(getCheckbox1()?.checked).toEqual(true); expect(getCheckbox2()?.checked).toEqual(false); From 0846132b0a1a1a4ff1c3d45bd7aff787cd197567 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Wed, 27 Nov 2024 11:57:05 +0100 Subject: [PATCH 039/146] change `useMutation` to `queryClient.fetchQuery` --- .../src/controller/list/useSelectAll.tsx | 54 ++++++++++++------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/packages/ra-core/src/controller/list/useSelectAll.tsx b/packages/ra-core/src/controller/list/useSelectAll.tsx index 5036e6ad8b7..1eccd5f7105 100644 --- a/packages/ra-core/src/controller/list/useSelectAll.tsx +++ b/packages/ra-core/src/controller/list/useSelectAll.tsx @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useQueryClient } from '@tanstack/react-query'; import { useNotify } from '../../notification'; import { useDataProvider } from '../../dataProvider'; import { useRecordSelection } from './useRecordSelection'; @@ -33,19 +33,35 @@ export const useSelectAll = ({ }: useSelectAllProps): (() => void) => { const notify = useNotify(); const dataProvider = useDataProvider(); + const queryClient = useQueryClient(); const [_, selectionModifiers] = useRecordSelection({ resource }); - const { mutate: onSelectAll } = useMutation({ - mutationFn: () => - dataProvider.getList(resource, { - pagination: { - page: 1, - perPage: selectAllLimit, - }, - sort: { field: query.sort, order: query.order }, - filter: { ...query.filter, ...filter }, - meta, - }), - onSuccess: ({ data }) => { + + const onSelectAll = async () => { + try { + const { data } = await queryClient.fetchQuery({ + queryKey: [ + resource, + 'getList', + { + resource, + pagination: { page: 1, perPage: selectAllLimit }, + sort: { field: query.sort, order: query.order }, + filter: { ...query.filter, ...filter }, + meta, + }, + ], + queryFn: () => + dataProvider.getList(resource, { + pagination: { + page: 1, + perPage: selectAllLimit, + }, + sort: { field: query.sort, order: query.order }, + filter: { ...query.filter, ...filter }, + meta, + }), + }); + const allIds = data?.map(({ id }) => id) || []; selectionModifiers.select(allIds); if (allIds.length === selectAllLimit) { @@ -54,13 +70,13 @@ export const useSelectAll = ({ type: 'warning', }); } - }, - onError: e => { - console.error('Mutation Error: ', e); - notify('An error occurred. Please try again.'); - }, - }); + return data; + } catch (error) { + console.error('Mutation Error: ', error); + notify('An error occurred. Please try again.'); + } + }; return onSelectAll; }; From a3a9b8b05f2fcf360f0764e0736a7e80f5cc6b71 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Mon, 2 Dec 2024 11:50:06 +0100 Subject: [PATCH 040/146] Isolate test components in stories-> `useReferenceArrayField` --- .../field/ReferenceArrayField.stories.tsx | 95 +++++++++++++++++++ .../useReferenceArrayFieldController.spec.tsx | 79 ++------------- 2 files changed, 102 insertions(+), 72 deletions(-) create mode 100644 packages/ra-core/src/controller/field/ReferenceArrayField.stories.tsx diff --git a/packages/ra-core/src/controller/field/ReferenceArrayField.stories.tsx b/packages/ra-core/src/controller/field/ReferenceArrayField.stories.tsx new file mode 100644 index 00000000000..ff12a50da4b --- /dev/null +++ b/packages/ra-core/src/controller/field/ReferenceArrayField.stories.tsx @@ -0,0 +1,95 @@ +import * as React from 'react'; +import { + CoreAdminContext, + type GetManyResult, + type ListControllerResult, + testDataProvider, + useReferenceArrayFieldController, +} from '../..'; + +const dataProvider = testDataProvider({ + getMany: (_resource, _params): Promise => + Promise.resolve({ + data: [ + { id: 1, title: 'bar1' }, + { id: 2, title: 'bar2' }, + ], + }), +}); + +const ReferenceArrayFieldController = props => { + const { children, ...rest } = props; + const controllerProps = useReferenceArrayFieldController({ + sort: { + field: 'id', + order: 'ASC', + }, + ...rest, + }); + return children(controllerProps); +}; + +const ReferenceArrayFieldComponent = (props: ListControllerResult) => ( +
+
+ + +

Selected ids: {JSON.stringify(props.selectedIds)}

+
+
    + {props.data?.map(record => ( +
  • + props.onToggleItem(record.id)} + style={{ + cursor: 'pointer', + marginRight: '10px', + }} + /> + {record.id} - {record.title} +
  • + ))} +
+
+); + +export const ReferenceArrayField = ({ + children = props => , +}) => ( + + + {children} + + +); + +export default { + title: 'ra-core/fields/ReferenceArrayField', +}; diff --git a/packages/ra-core/src/controller/field/useReferenceArrayFieldController.spec.tsx b/packages/ra-core/src/controller/field/useReferenceArrayFieldController.spec.tsx index 3428dab671e..610ac73983c 100644 --- a/packages/ra-core/src/controller/field/useReferenceArrayFieldController.spec.tsx +++ b/packages/ra-core/src/controller/field/useReferenceArrayFieldController.spec.tsx @@ -5,6 +5,7 @@ import { act, render, waitFor } from '@testing-library/react'; import { useReferenceArrayFieldController } from './useReferenceArrayFieldController'; import { testDataProvider } from '../../dataProvider'; import { CoreAdminContext } from '../../core'; +import { ReferenceArrayField } from './ReferenceArrayField.stories'; const ReferenceArrayFieldController = props => { const { children, ...rest } = props; @@ -170,18 +171,7 @@ describe('', () => { describe('areAllItemsSelected', () => { it('should be false if no items are selected', async () => { const children = jest.fn().mockReturnValue('child'); - render( - - - {children} - - - ); + render({children}); await waitFor(() => { expect(children).toHaveBeenCalledWith( expect.objectContaining({ @@ -197,18 +187,7 @@ describe('', () => { }); it('should be false if some items are selected', async () => { const children = jest.fn().mockReturnValue('child'); - render( - - - {children} - - - ); + render({children}); act(() => { children.mock.calls.at(-1)[0].onSelect([1]); }); @@ -228,18 +207,7 @@ describe('', () => { }); it('should be true if all items are manually selected', async () => { const children = jest.fn().mockReturnValue('child'); - render( - - - {children} - - - ); + render({children}); act(() => { children.mock.calls.at(-1)[0].onSelect([1, 2]); }); @@ -259,18 +227,7 @@ describe('', () => { }); it('should be true if all items are selected with onSelectAll', async () => { const children = jest.fn().mockReturnValue('child'); - render( - - - {children} - - - ); + render({children}); await waitFor(() => { expect(children).toHaveBeenCalledWith( expect.objectContaining({ @@ -306,18 +263,7 @@ describe('', () => { describe('onSelectAll', () => { it('should select all items if no items are selected', async () => { const children = jest.fn().mockReturnValue('child'); - render( - - - {children} - - - ); + render({children}); await waitFor(() => { expect(children).toHaveBeenCalledWith( expect.objectContaining({ @@ -341,18 +287,7 @@ describe('', () => { }); it('should select all items if some items are selected', async () => { const children = jest.fn().mockReturnValue('child'); - render( - - - {children} - - - ); + render({children}); await waitFor(() => { expect(children).toHaveBeenCalledWith( expect.objectContaining({ From 30c1d2851a66fcb3ec9ea8c7009f17997ca9c298 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Mon, 2 Dec 2024 14:33:34 +0100 Subject: [PATCH 041/146] Isolate test components in stories-> `useReferenceManyField` --- .../field/ReferenceArrayField.stories.tsx | 2 +- .../field/ReferenceManyField.stories.tsx | 118 +++++++++++ .../useReferenceManyFieldController.spec.tsx | 191 ++---------------- 3 files changed, 134 insertions(+), 177 deletions(-) create mode 100644 packages/ra-core/src/controller/field/ReferenceManyField.stories.tsx diff --git a/packages/ra-core/src/controller/field/ReferenceArrayField.stories.tsx b/packages/ra-core/src/controller/field/ReferenceArrayField.stories.tsx index ff12a50da4b..393fba8146a 100644 --- a/packages/ra-core/src/controller/field/ReferenceArrayField.stories.tsx +++ b/packages/ra-core/src/controller/field/ReferenceArrayField.stories.tsx @@ -76,7 +76,7 @@ const ReferenceArrayFieldComponent = (props: ListControllerResult) => ( ); export const ReferenceArrayField = ({ - children = props => , + children = ReferenceArrayFieldComponent, }) => ( => + Promise.resolve({ + data: [ + { id: 0, title: 'bar0' }, + { id: 1, title: 'bar1' }, + ].slice(0, params.pagination.perPage), + total: params.pagination.perPage || 2, + }), +}); + +const ReferenceManyFieldController = props => { + const { children, ...rest } = props; + const controllerProps = useReferenceManyFieldController({ + sort: { + field: 'id', + order: 'ASC', + }, + ...rest, + }); + return children(controllerProps); +}; + +const ReferenceManyField = ( + props: ListControllerResult & { selectAllLimit?: number } +) => ( +
+
+ + +

Selected ids: {JSON.stringify(props.selectedIds)}

+ {props.selectAllLimit && ( +

selectAllLimit : {props.selectAllLimit}

+ )} +
+
    + {props.data?.map(record => ( +
  • + props.onToggleItem(record.id)} + style={{ + cursor: 'pointer', + marginRight: '10px', + }} + /> + {record.id} - {record.title} +
  • + ))} +
+
+); + +export const Basic = ({ children = ReferenceManyField }) => ( + + + {children} + + +); + +export const SelectAllLimit = ({ + dataProvider = defaultDataProvider, + children = props => , +}) => ( + + + {children} + + +); + +export default { + title: 'ra-core/fields/ReferenceManyField', +}; diff --git a/packages/ra-core/src/controller/field/useReferenceManyFieldController.spec.tsx b/packages/ra-core/src/controller/field/useReferenceManyFieldController.spec.tsx index c9722926f03..9d77807b7ea 100644 --- a/packages/ra-core/src/controller/field/useReferenceManyFieldController.spec.tsx +++ b/packages/ra-core/src/controller/field/useReferenceManyFieldController.spec.tsx @@ -12,6 +12,7 @@ import { testDataProvider } from '../../dataProvider/testDataProvider'; import { CoreAdminContext } from '../../core'; import { useReferenceManyFieldController } from './useReferenceManyFieldController'; import { memoryStore } from '../../store'; +import { Basic, SelectAllLimit } from './ReferenceManyField.stories'; const ReferenceManyFieldController = props => { const { children, page = 1, perPage = 25, ...rest } = props; @@ -422,25 +423,7 @@ describe('useReferenceManyFieldController', () => { describe('areAllItemsSelected', () => { it('should be false if no items are selected', async () => { const children = jest.fn().mockReturnValue('child'); - const dataProvider = testDataProvider({ - getManyReference: jest.fn().mockResolvedValue({ - data: [{ id: 0 }, { id: 1 }], - total: 2, - }), - }); - render( - - - {children} - - - ); + render({children}); await waitFor(() => { expect(children).toHaveBeenCalledWith( expect.objectContaining({ @@ -451,25 +434,7 @@ describe('useReferenceManyFieldController', () => { }); it('should be false if some items are selected', async () => { const children = jest.fn().mockReturnValue('child'); - const dataProvider = testDataProvider({ - getManyReference: jest.fn().mockResolvedValue({ - data: [{ id: 0 }, { id: 1 }], - total: 2, - }), - }); - render( - - - {children} - - - ); + render({children}); act(() => { children.mock.calls.at(-1)[0].onSelect([1]); }); @@ -484,25 +449,7 @@ describe('useReferenceManyFieldController', () => { }); it('should be true if all items are manually selected', async () => { const children = jest.fn().mockReturnValue('child'); - const dataProvider = testDataProvider({ - getManyReference: jest.fn().mockResolvedValue({ - data: [{ id: 0 }, { id: 1 }], - total: 2, - }), - }); - render( - - - {children} - - - ); + render({children}); act(() => { children.mock.calls.at(-1)[0].onSelect([0, 1]); }); @@ -517,25 +464,7 @@ describe('useReferenceManyFieldController', () => { }); it('should be true if all items are selected with onSelectAll', async () => { const children = jest.fn().mockReturnValue('child'); - const dataProvider = testDataProvider({ - getManyReference: jest.fn().mockResolvedValue({ - data: [{ id: 0 }, { id: 1 }], - total: 2, - }), - }); - render( - - - {children} - - - ); + render({children}); act(() => { children.mock.calls.at(-1)[0].onSelectAll(); }); @@ -550,26 +479,7 @@ describe('useReferenceManyFieldController', () => { }); it('should be true if all we manually reached the selectAllLimit', async () => { const children = jest.fn().mockReturnValue('child'); - const dataProvider = testDataProvider({ - getManyReference: jest.fn().mockResolvedValue({ - data: [{ id: 0 }, { id: 1 }], - total: 2, - }), - }); - render( - - - {children} - - - ); + render({children}); act(() => { children.mock.calls.at(-1)[0].onSelect([0]); }); @@ -584,33 +494,7 @@ describe('useReferenceManyFieldController', () => { }); it('should be true if all we reached the selectAllLimit with onSelectAll', async () => { const children = jest.fn().mockReturnValue('child'); - const dataProvider = testDataProvider({ - getManyReference: jest - .fn() - .mockImplementation((_resource, params) => - Promise.resolve({ - data: [{ id: 0 }, { id: 1 }].slice( - 0, - params.pagination.perPage - ), - total: 2, - }) - ), - }); - render( - - - {children} - - - ); + render({children}); act(() => { children.mock.calls.at(-1)[0].onSelectAll(); }); @@ -628,26 +512,7 @@ describe('useReferenceManyFieldController', () => { describe('onSelectAll', () => { it('should select all items if no items are selected', async () => { const children = jest.fn().mockReturnValue('child'); - const dataProvider = testDataProvider({ - getManyReference: jest.fn().mockResolvedValue({ - data: [{ id: 0 }, { id: 1 }], - total: 2, - }), - }); - render( - - - {children} - - - ); + render({children}); act(() => { children.mock.calls.at(-1)[0].onSelectAll(); }); @@ -661,26 +526,7 @@ describe('useReferenceManyFieldController', () => { }); it('should select all items if some items are selected', async () => { const children = jest.fn().mockReturnValue('child'); - const dataProvider = testDataProvider({ - getManyReference: jest.fn().mockResolvedValue({ - data: [{ id: 0 }, { id: 1 }], - total: 2, - }), - }); - render( - - - {children} - - - ); + render({children}); act(() => { children.mock.calls.at(-1)[0].onSelect([1]); }); @@ -715,20 +561,13 @@ describe('useReferenceManyFieldController', () => { total: 2, }) ); - const dataProvider = testDataProvider({ getManyReference }); + const dataProvider = testDataProvider({ + getManyReference, + }); render( - - - {children} - - + + {children} + ); await waitFor(() => { expect(children).toHaveBeenNthCalledWith( From 7e0f92d88637b3aadf0eac718fe25ceee6c3a3ac Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Mon, 2 Dec 2024 15:24:26 +0100 Subject: [PATCH 042/146] Isolate test components in stories-> `useInfiniteListController` --- .../list/useInfiniteListController.spec.tsx | 174 ++++-------------- ... => useInfiniteListController.stories.tsx} | 107 ++++++++++- 2 files changed, 135 insertions(+), 146 deletions(-) rename packages/ra-core/src/controller/list/{useInfiniteListController.security.stories.tsx => useInfiniteListController.stories.tsx} (67%) diff --git a/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx b/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx index f4f417d5c7c..8470e2d2364 100644 --- a/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx +++ b/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx @@ -22,11 +22,12 @@ import { import { CoreAdminContext } from '../../core'; import { TestMemoryRouter } from '../../routing'; import { + Basic, Authenticated, CanAccess, DisableAuthentication, -} from './useInfiniteListController.security.stories'; -import { AuthProvider } from '../../types'; +} from './useInfiniteListController.stories'; +import type { AuthProvider } from '../../types'; const InfiniteListController = ({ children, @@ -45,24 +46,22 @@ describe('useInfiniteListController', () => { debounce: 200, }; + const dataProvider = testDataProvider({ + getList: jest.fn().mockImplementation((_resource, params) => + Promise.resolve({ + data: [{ id: 0 }, { id: 1 }].slice( + 0, + params.pagination.perPage + ), + total: 2, + }) + ), + }); + describe('areAllItemsSelected', () => { it('should be false if no items are selected', async () => { - const getList = jest - .fn() - .mockImplementation(() => - Promise.resolve({ data: [{ id: 0 }, { id: 1 }], total: 2 }) - ); - const dataProvider = testDataProvider({ getList }); const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - children, - }; - render( - - - - ); + render(); await waitFor(() => { expect(children).toHaveBeenCalledWith( expect.objectContaining({ @@ -73,22 +72,8 @@ describe('useInfiniteListController', () => { }); }); it('should be false if some items are selected', async () => { - const getList = jest - .fn() - .mockImplementation(() => - Promise.resolve({ data: [{ id: 0 }, { id: 1 }], total: 2 }) - ); - const dataProvider = testDataProvider({ getList }); const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - children, - }; - render( - - - - ); + render(); act(() => { children.mock.calls.at(-1)[0].onSelect([0]); }); @@ -102,22 +87,8 @@ describe('useInfiniteListController', () => { }); }); it('should be true if all items are manually selected', async () => { - const getList = jest - .fn() - .mockImplementation(() => - Promise.resolve({ data: [{ id: 0 }, { id: 1 }], total: 2 }) - ); - const dataProvider = testDataProvider({ getList }); const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - children, - }; - render( - - - - ); + render(); act(() => { children.mock.calls.at(-1)[0].onSelect([0, 1]); }); @@ -131,22 +102,8 @@ describe('useInfiniteListController', () => { }); }); it('should be true if all items are selected with onSelectAll', async () => { - const getList = jest - .fn() - .mockImplementation(() => - Promise.resolve({ data: [{ id: 0 }, { id: 1 }], total: 2 }) - ); - const dataProvider = testDataProvider({ getList }); const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - children, - }; - render( - - - - ); + render(); act(() => { children.mock.calls.at(-1)[0].onSelectAll(); }); @@ -160,23 +117,13 @@ describe('useInfiniteListController', () => { }); }); it('should be true if all we manually reached the selectAllLimit', async () => { - const getList = jest.fn().mockImplementation(() => - Promise.resolve({ - data: [{ id: 0 }, { id: 1 }], - total: 2, - }) - ); - const dataProvider = testDataProvider({ getList }); const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - selectAllLimit: 1, - children, - }; render( - - - + ); act(() => { children.mock.calls.at(-1)[0].onSelect([0]); @@ -191,27 +138,13 @@ describe('useInfiniteListController', () => { }); }); it('should be true if all we reached the selectAllLimit with onSelectAll', async () => { - const getList = jest.fn().mockImplementation((_resource, params) => - Promise.resolve({ - data: [{ id: 0 }, { id: 1 }].slice( - 0, - params.pagination.perPage - ), - total: 2, - }) - ); - - const dataProvider = testDataProvider({ getList }); const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - selectAllLimit: 1, - children, - }; render( - - - + ); act(() => { children.mock.calls.at(-1)[0].onSelectAll(); @@ -229,23 +162,8 @@ describe('useInfiniteListController', () => { describe('onSelectAll', () => { it('should select all items if no items are selected', async () => { - const getList = jest.fn().mockImplementation(() => - Promise.resolve({ - data: [{ id: 0 }, { id: 1 }], - total: 2, - }) - ); - const dataProvider = testDataProvider({ getList }); const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - children, - }; - render( - - - - ); + render(); act(() => { children.mock.calls.at(-1)[0].onSelectAll(); }); @@ -258,23 +176,8 @@ describe('useInfiniteListController', () => { }); }); it('should select all items if some items are selected', async () => { - const getList = jest.fn().mockImplementation(() => - Promise.resolve({ - data: [{ id: 0 }, { id: 1 }], - total: 2, - }) - ); - const dataProvider = testDataProvider({ getList }); const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - children, - }; - render( - - - - ); + render(); act(() => { children.mock.calls.at(-1)[0].onSelect([0]); }); @@ -306,17 +209,14 @@ describe('useInfiniteListController', () => { total: 2, }) ); - const dataProvider = testDataProvider({ getList }); + const mockedDataProvider = testDataProvider({ getList }); const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - selectAllLimit: 1, - children, - }; render( - - - + ); act(() => { children.mock.calls.at(-1)[0].onSelectAll(); diff --git a/packages/ra-core/src/controller/list/useInfiniteListController.security.stories.tsx b/packages/ra-core/src/controller/list/useInfiniteListController.stories.tsx similarity index 67% rename from packages/ra-core/src/controller/list/useInfiniteListController.security.stories.tsx rename to packages/ra-core/src/controller/list/useInfiniteListController.stories.tsx index cf358e6df7d..8c082783ccb 100644 --- a/packages/ra-core/src/controller/list/useInfiniteListController.security.stories.tsx +++ b/packages/ra-core/src/controller/list/useInfiniteListController.stories.tsx @@ -4,10 +4,7 @@ import { QueryClient } from '@tanstack/react-query'; import { Link } from 'react-router-dom'; import { CoreAdmin, CoreAdminContext, CoreAdminUI, Resource } from '../../core'; import { AuthProvider, DataProvider } from '../../types'; -import { - InfiniteListControllerProps, - useInfiniteListController, -} from './useInfiniteListController'; +import { useInfiniteListController } from './useInfiniteListController'; import { Browser } from '../../storybook/FakeBrowser'; import { TestMemoryRouter } from '../../routing'; @@ -41,11 +38,7 @@ const defaultDataProvider = fakeDataProvider( process.env.NODE_ENV === 'development' ); -const Posts = (props: Partial) => { - const params = useInfiniteListController({ - resource: 'posts', - ...props, - }); +const List = params => { return (
{params.isPending ? ( @@ -65,6 +58,102 @@ const Posts = (props: Partial) => { ); }; +const Posts = ({ children = List, ...props }) => { + const params = useInfiniteListController({ + resource: 'posts', + ...props, + }); + return children(params); +}; + +const ListWithCheckbox = params => ( +
+ {params.isPending ? ( +

Loading...

+ ) : ( +
+
+ + +

Selected ids: {JSON.stringify(params.selectedIds)}

+ {params.selectAllLimit && ( +

selectAllLimit : {params.selectAllLimit}

+ )} +
+
    + {params.data?.map(record => ( +
  • + params.onToggleItem(record.id)} + style={{ + cursor: 'pointer', + marginRight: '10px', + }} + /> + {record.id} - {record.title} +
  • + ))} +
+
+ )} +
+); + +export const Basic = ({ + dataProvider = defaultDataProvider, + selectAllLimit, + children = props => ( + + ), +}: { + dataProvider?: DataProvider; + selectAllLimit?: number; + children?: (props) => React.JSX.Element; +}) => { + return ( + + + + + } + /> + + + + ); +}; + +export const SelectAllLimit = () => ; + const defaultAuthProvider: AuthProvider = { checkAuth: () => new Promise(resolve => setTimeout(resolve, 500)), login: () => Promise.resolve(), From d9f1a594fa294bf358b4bb677602762bece5cdba Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Mon, 2 Dec 2024 15:52:31 +0100 Subject: [PATCH 043/146] Isolate test components in stories-> `useListController` --- .../list/useListController.spec.tsx | 185 ++++-------------- .../list/useListController.stories.tsx | 100 ++++++++++ 2 files changed, 142 insertions(+), 143 deletions(-) create mode 100644 packages/ra-core/src/controller/list/useListController.stories.tsx diff --git a/packages/ra-core/src/controller/list/useListController.spec.tsx b/packages/ra-core/src/controller/list/useListController.spec.tsx index 2770fb4494c..40445cb9efe 100644 --- a/packages/ra-core/src/controller/list/useListController.spec.tsx +++ b/packages/ra-core/src/controller/list/useListController.spec.tsx @@ -22,6 +22,7 @@ import { CanAccess, DisableAuthentication, } from './useListController.security.stories'; +import { Basic } from './useListController.stories'; describe('useListController', () => { const defaultProps = { @@ -586,24 +587,21 @@ describe('useListController', () => { }); }); + const dataProvider = testDataProvider({ + getList: jest.fn().mockImplementation((_resource, params) => + Promise.resolve({ + data: [{ id: 0 }, { id: 1 }].slice( + 0, + params.pagination.perPage + ), + total: 2, + }) + ), + }); describe('areAllItemsSelected', () => { it('should be false if no items are selected', async () => { - const getList = jest - .fn() - .mockImplementation(() => - Promise.resolve({ data: [{ id: 0 }, { id: 1 }], total: 2 }) - ); - const dataProvider = testDataProvider({ getList }); - const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - children, - }; - render( - - - - ); + const children = jest.fn().mockReturnValue(<>); + render(); await waitFor(() => { expect(children).toHaveBeenCalledWith( expect.objectContaining({ @@ -614,22 +612,8 @@ describe('useListController', () => { }); }); it('should be false if some items are selected', async () => { - const getList = jest - .fn() - .mockImplementation(() => - Promise.resolve({ data: [{ id: 0 }, { id: 1 }], total: 2 }) - ); - const dataProvider = testDataProvider({ getList }); - const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - children, - }; - render( - - - - ); + const children = jest.fn().mockReturnValue(<>); + render(); act(() => { children.mock.calls.at(-1)[0].onSelect([0]); }); @@ -643,22 +627,8 @@ describe('useListController', () => { }); }); it('should be true if all items are manually selected', async () => { - const getList = jest - .fn() - .mockImplementation(() => - Promise.resolve({ data: [{ id: 0 }, { id: 1 }], total: 2 }) - ); - const dataProvider = testDataProvider({ getList }); - const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - children, - }; - render( - - - - ); + const children = jest.fn().mockReturnValue(<>); + render(); act(() => { children.mock.calls.at(-1)[0].onSelect([0, 1]); }); @@ -672,22 +642,8 @@ describe('useListController', () => { }); }); it('should be true if all items are selected with onSelectAll', async () => { - const getList = jest - .fn() - .mockImplementation(() => - Promise.resolve({ data: [{ id: 0 }, { id: 1 }], total: 2 }) - ); - const dataProvider = testDataProvider({ getList }); - const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - children, - }; - render( - - - - ); + const children = jest.fn().mockReturnValue(<>); + render(); act(() => { children.mock.calls.at(-1)[0].onSelectAll(); }); @@ -703,23 +659,13 @@ describe('useListController', () => { }); }); it('should be true if all we manually reached the selectAllLimit', async () => { - const getList = jest.fn().mockImplementation(() => - Promise.resolve({ - data: [{ id: 0 }, { id: 1 }], - total: 2, - }) - ); - const dataProvider = testDataProvider({ getList }); - const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - selectAllLimit: 1, - children, - }; + const children = jest.fn().mockReturnValue(<>); render( - - - + ); act(() => { children.mock.calls.at(-1)[0].onSelect([0]); @@ -734,27 +680,13 @@ describe('useListController', () => { }); }); it('should be true if all we reached the selectAllLimit with onSelectAll', async () => { - const getList = jest.fn().mockImplementation((_resource, params) => - Promise.resolve({ - data: [{ id: 0 }, { id: 1 }].slice( - 0, - params.pagination.perPage - ), - total: 2, - }) - ); - - const dataProvider = testDataProvider({ getList }); - const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - selectAllLimit: 1, - children, - }; + const children = jest.fn().mockReturnValue(<>); render( - - - + ); act(() => { children.mock.calls.at(-1)[0].onSelectAll(); @@ -774,23 +706,8 @@ describe('useListController', () => { describe('onSelectAll', () => { it('should select all items if no items are selected', async () => { - const getList = jest.fn().mockImplementation(() => - Promise.resolve({ - data: [{ id: 0 }, { id: 1 }], - total: 2, - }) - ); - const dataProvider = testDataProvider({ getList }); - const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - children, - }; - render( - - - - ); + const children = jest.fn().mockReturnValue(<>); + render(); act(() => { children.mock.calls.at(-1)[0].onSelectAll(); }); @@ -803,23 +720,8 @@ describe('useListController', () => { }); }); it('should select all items if some items are selected', async () => { - const getList = jest.fn().mockImplementation(() => - Promise.resolve({ - data: [{ id: 0 }, { id: 1 }], - total: 2, - }) - ); - const dataProvider = testDataProvider({ getList }); - const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - children, - }; - render( - - - - ); + const children = jest.fn().mockReturnValue(<>); + render(); act(() => { children.mock.calls.at(-1)[0].onSelect([0]); }); @@ -852,16 +754,13 @@ describe('useListController', () => { }) ); const dataProvider = testDataProvider({ getList }); - const children = jest.fn().mockReturnValue(children); - const props = { - ...defaultProps, - selectAllLimit: 1, - children, - }; + const children = jest.fn().mockReturnValue(<>); render( - - - + ); act(() => { children.mock.calls.at(-1)[0].onSelectAll(); diff --git a/packages/ra-core/src/controller/list/useListController.stories.tsx b/packages/ra-core/src/controller/list/useListController.stories.tsx new file mode 100644 index 00000000000..3353a3eca67 --- /dev/null +++ b/packages/ra-core/src/controller/list/useListController.stories.tsx @@ -0,0 +1,100 @@ +import * as React from 'react'; +import fakeDataProvider from 'ra-data-fakerest'; + +import { CoreAdminContext } from '../../core'; +import { ListController } from './ListController'; +import type { DataProvider } from '../../types'; +import type { ListControllerResult } from './useListController'; + +export default { + title: 'ra-core/controller/list/useListController', +}; + +const defaultDataProvider = fakeDataProvider( + { + posts: [ + { id: 1, title: 'Post #1', votes: 90 }, + { id: 2, title: 'Post #2', votes: 20 }, + { id: 3, title: 'Post #3', votes: 30 }, + { id: 4, title: 'Post #4', votes: 40 }, + { id: 5, title: 'Post #5', votes: 50 }, + { id: 6, title: 'Post #6', votes: 60 }, + { id: 7, title: 'Post #7', votes: 70 }, + ], + }, + process.env.NODE_ENV === 'development' +); + +const List = params => ( +
+
+ + +

Selected ids: {JSON.stringify(params.selectedIds)}

+ {params.selectAllLimit && ( +

selectAllLimit : {params.selectAllLimit}

+ )} +
+
    + {params.data?.map(record => ( +
  • + params.onToggleItem(record.id)} + style={{ + cursor: 'pointer', + marginRight: '10px', + }} + /> + {record.id} - {record.title} +
  • + ))} +
+
+); + +export const Basic = ({ + dataProvider = defaultDataProvider, + selectAllLimit, + children = List, +}: { + dataProvider?: DataProvider; + selectAllLimit?: number; + children?: (params: ListControllerResult) => JSX.Element; +}) => ( + + + +); + +export const SelectAllLimit = () => ( + } + /> +); From 04ddc21ea89d2f481cf1256a891b3d046af7cfed Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Mon, 2 Dec 2024 16:08:59 +0100 Subject: [PATCH 044/146] change `useMutation` to `queryClient.fetchQuery` in `useReferenceManyFieldController` --- .../field/useReferenceManyFieldController.ts | 74 ++++++++++++++----- 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts b/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts index 423b5c11a45..bf2956a7630 100644 --- a/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts +++ b/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useRef, useState } from 'react'; -import { UseQueryOptions, useMutation } from '@tanstack/react-query'; +import { UseQueryOptions, useQueryClient } from '@tanstack/react-query'; import get from 'lodash/get'; import isEqual from 'lodash/isEqual'; import lodashDebounce from 'lodash/debounce'; @@ -71,6 +71,7 @@ export const useReferenceManyFieldController = < const dataProvider = useDataProvider(); const storeKey = props.storeKey ?? `${resource}.${record?.id}.${reference}`; const { meta, ...otherQueryOptions } = queryOptions; + const queryClient = useQueryClient(); // pagination logic const { page, setPage, perPage, setPerPage } = usePaginationState({ @@ -202,20 +203,38 @@ export const useReferenceManyFieldController = < } ); - const { mutate: onSelectAll } = useMutation({ - mutationFn: () => - dataProvider.getManyReference(reference, { - target, - id: get(record, source) as Identifier, - pagination: { - page: 1, - perPage: selectAllLimit, - }, - sort, - filter: filterValues, - meta, - }), - onSuccess: ({ data }) => { + const onSelectAll = useCallback(async () => { + try { + const { data } = await queryClient.fetchQuery({ + queryKey: [ + resource, + 'getManyReference', + { + target, + id: get(record, source) as Identifier, + pagination: { + page: 1, + perPage: selectAllLimit, + }, + sort, + filter: filterValues, + meta, + }, + ], + queryFn: () => + dataProvider.getManyReference(reference, { + target, + id: get(record, source) as Identifier, + pagination: { + page: 1, + perPage: selectAllLimit, + }, + sort, + filter: filterValues, + meta, + }), + }); + const allIds = data?.map(({ id }) => id) || []; selectionModifiers.select(allIds); if (allIds.length === selectAllLimit) { @@ -224,12 +243,27 @@ export const useReferenceManyFieldController = < type: 'warning', }); } - }, - onError: e => { - console.error('Mutation Error: ', e); + + return data; + } catch (error) { + console.error('Mutation Error: ', error); notify('An error occurred. Please try again.'); - }, - }); + } + }, [ + dataProvider, + filterValues, + meta, + notify, + queryClient, + record, + reference, + resource, + selectAllLimit, + selectionModifiers, + sort, + source, + target, + ]); return { sort, From 31c996be5b10a9e5cae09d33554ce9fa626770d5 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Thu, 5 Dec 2024 19:28:50 +0100 Subject: [PATCH 045/146] change the structure --- .../field/useReferenceArrayFieldController.ts | 1 - .../field/useReferenceManyFieldController.ts | 74 +------------------ .../src/controller/list/ListContext.tsx | 2 - packages/ra-core/src/controller/list/index.ts | 1 + .../list/useInfiniteListController.ts | 15 ---- .../ra-core/src/controller/list/useList.ts | 7 -- .../list/useListContextWithProps.ts | 6 -- .../src/controller/list/useListController.ts | 28 ------- .../src/controller/list/useSelectAll.tsx | 47 ++++++------ .../src/button/SelectAllButton.tsx | 71 ++++++++++++++++++ packages/ra-ui-materialui/src/button/index.ts | 1 + .../src/field/ReferenceArrayField.tsx | 3 - .../src/field/ReferenceManyField.tsx | 2 - .../src/list/BulkActionsToolbar.tsx | 18 +---- .../src/list/InfiniteList.tsx | 3 - packages/ra-ui-materialui/src/list/List.tsx | 3 - .../src/list/datagrid/Datagrid.tsx | 10 ++- 17 files changed, 109 insertions(+), 183 deletions(-) create mode 100644 packages/ra-ui-materialui/src/button/SelectAllButton.tsx diff --git a/packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts b/packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts index 840a8e9b4d3..af7bde8cfaf 100644 --- a/packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts +++ b/packages/ra-core/src/controller/field/useReferenceArrayFieldController.ts @@ -17,7 +17,6 @@ export interface UseReferenceArrayFieldControllerParams< resource?: string; sort?: SortPayload; source: string; - selectAllLimit?: number; queryOptions?: Omit< UseQueryOptions, 'queryFn' | 'queryKey' diff --git a/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts b/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts index bf2956a7630..06a8bb99e31 100644 --- a/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts +++ b/packages/ra-core/src/controller/field/useReferenceManyFieldController.ts @@ -1,11 +1,11 @@ import { useCallback, useEffect, useRef, useState } from 'react'; -import { UseQueryOptions, useQueryClient } from '@tanstack/react-query'; +import { UseQueryOptions } from '@tanstack/react-query'; import get from 'lodash/get'; import isEqual from 'lodash/isEqual'; import lodashDebounce from 'lodash/debounce'; import { removeEmpty } from '../../util'; -import { useDataProvider, useGetManyReference } from '../../dataProvider'; +import { useGetManyReference } from '../../dataProvider'; import { useNotify } from '../../notification'; import { FilterPayload, Identifier, RaRecord, SortPayload } from '../../types'; import { ListControllerResult } from '../list'; @@ -64,14 +64,11 @@ export const useReferenceManyFieldController = < { data: ReferenceRecordType[]; total: number }, Error >, - selectAllLimit = 250, } = props; const notify = useNotify(); const resource = useResourceContext(props); - const dataProvider = useDataProvider(); const storeKey = props.storeKey ?? `${resource}.${record?.id}.${reference}`; const { meta, ...otherQueryOptions } = queryOptions; - const queryClient = useQueryClient(); // pagination logic const { page, setPage, perPage, setPerPage } = usePaginationState({ @@ -203,68 +200,6 @@ export const useReferenceManyFieldController = < } ); - const onSelectAll = useCallback(async () => { - try { - const { data } = await queryClient.fetchQuery({ - queryKey: [ - resource, - 'getManyReference', - { - target, - id: get(record, source) as Identifier, - pagination: { - page: 1, - perPage: selectAllLimit, - }, - sort, - filter: filterValues, - meta, - }, - ], - queryFn: () => - dataProvider.getManyReference(reference, { - target, - id: get(record, source) as Identifier, - pagination: { - page: 1, - perPage: selectAllLimit, - }, - sort, - filter: filterValues, - meta, - }), - }); - - const allIds = data?.map(({ id }) => id) || []; - selectionModifiers.select(allIds); - if (allIds.length === selectAllLimit) { - notify('ra.message.too_many_elements', { - messageArgs: { max: selectAllLimit }, - type: 'warning', - }); - } - - return data; - } catch (error) { - console.error('Mutation Error: ', error); - notify('An error occurred. Please try again.'); - } - }, [ - dataProvider, - filterValues, - meta, - notify, - queryClient, - record, - reference, - resource, - selectAllLimit, - selectionModifiers, - sort, - source, - target, - ]); - return { sort, data, @@ -278,7 +213,6 @@ export const useReferenceManyFieldController = < isLoading, isPending, onSelect: selectionModifiers.select, - onSelectAll, onToggleItem: selectionModifiers.toggle, onUnselectItems: selectionModifiers.clearSelection, page, @@ -286,9 +220,6 @@ export const useReferenceManyFieldController = < refetch, resource: reference, selectedIds, - areAllItemsSelected: - total === selectedIds.length || - selectedIds.length >= selectAllLimit, setFilters, setPage, setPerPage, @@ -318,7 +249,6 @@ export interface UseReferenceManyFieldControllerParams< sort?: SortPayload; source?: string; storeKey?: string; - selectAllLimit?: number; target: string; queryOptions?: Omit< UseQueryOptions<{ data: ReferenceRecordType[]; total: number }, Error>, diff --git a/packages/ra-core/src/controller/list/ListContext.tsx b/packages/ra-core/src/controller/list/ListContext.tsx index 5dc0692f790..f4af11d4495 100644 --- a/packages/ra-core/src/controller/list/ListContext.tsx +++ b/packages/ra-core/src/controller/list/ListContext.tsx @@ -24,9 +24,7 @@ import { ListControllerResult } from './useListController'; * @prop {Function} showFilter a callback to show one of the filters, e.g. showFilter('title', defaultValue) * @prop {Function} hideFilter a callback to hide one of the filters, e.g. hideFilter('title') * @prop {Array} selectedIds an array listing the ids of the selected rows, e.g. [123, 456] - * @prop {boolean} areAllItemsSelected boolean to indicate if the list is already fully selected * @prop {Function} onSelect callback to change the list of selected rows, e.g. onSelect([456, 789]) - * @prop {Function} onSelectAll callback to select all the records, e.g. onSelectAll() * @prop {Function} onToggleItem callback to toggle the selection of a given record based on its id, e.g. onToggleItem(456) * @prop {Function} onUnselectItems callback to clear the selection, e.g. onUnselectItems(); * @prop {string} defaultTitle the translated title based on the resource, e.g. 'Posts' diff --git a/packages/ra-core/src/controller/list/index.ts b/packages/ra-core/src/controller/list/index.ts index e1f1c39a4f2..1953cb4ad07 100644 --- a/packages/ra-core/src/controller/list/index.ts +++ b/packages/ra-core/src/controller/list/index.ts @@ -22,4 +22,5 @@ export * from './useListSortContext'; export * from './useRecordSelection'; export * from './useUnselect'; export * from './useUnselectAll'; +export * from './useSelectAll'; export * from './WithListContext'; diff --git a/packages/ra-core/src/controller/list/useInfiniteListController.ts b/packages/ra-core/src/controller/list/useInfiniteListController.ts index 7730ba7e110..b91bc07066c 100644 --- a/packages/ra-core/src/controller/list/useInfiniteListController.ts +++ b/packages/ra-core/src/controller/list/useInfiniteListController.ts @@ -24,7 +24,6 @@ import { useRecordSelection } from './useRecordSelection'; import { useListParams } from './useListParams'; import { ListControllerResult } from './useListController'; -import { useSelectAll } from './useSelectAll'; /** * Prepare data for the InfiniteList view @@ -57,7 +56,6 @@ export const useInfiniteListController = ( queryOptions, sort, storeKey, - selectAllLimit = 250, } = props; const resource = useResourceContext(props); const { meta, ...otherQueryOptions } = queryOptions ?? {}; @@ -141,14 +139,6 @@ export const useInfiniteListController = ( } ); - const onSelectAll = useSelectAll({ - selectAllLimit, - query, - filter, - meta, - resource, - }); - // change page if there is no data useEffect(() => { if ( @@ -204,7 +194,6 @@ export const useInfiniteListController = ( isLoading, isPending, onSelect: selectionModifiers.select, - onSelectAll, onToggleItem: selectionModifiers.toggle, onUnselectItems: selectionModifiers.clearSelection, page: query.page, @@ -212,9 +201,6 @@ export const useInfiniteListController = ( refetch, resource, selectedIds, - areAllItemsSelected: - total === selectedIds.length || - selectedIds.length >= selectAllLimit, setFilters: queryModifiers.setFilters, setPage: queryModifiers.setPage, setPerPage: queryModifiers.setPerPage, @@ -247,7 +233,6 @@ export interface InfiniteListControllerProps< resource?: string; sort?: SortPayload; storeKey?: string | false; - selectAllLimit?: number; } export type InfiniteListControllerResult = diff --git a/packages/ra-core/src/controller/list/useList.ts b/packages/ra-core/src/controller/list/useList.ts index dfef3073189..8ad6337c903 100644 --- a/packages/ra-core/src/controller/list/useList.ts +++ b/packages/ra-core/src/controller/list/useList.ts @@ -51,7 +51,6 @@ const refetch = () => { * @param {Number} props.perPage: Optional. The initial page size * @param {SortPayload} props.sort: Optional. The initial sort (field and order) * @param {filterCallback} prop.filterCallback Optional. A function that allows you to make a custom filter - * @param {Number} props.selectAllLimit: Optional. The number of items selected by the "SELECT ALL" button of the bulk actions toolbar */ export const useList = ( props: UseListOptions @@ -164,10 +163,6 @@ export const useList = ( }, [setDisplayedFilters, setFilterValues, setPage] ); - const onSelectAll = useCallback(() => { - const allIds = data?.map(({ id }) => id) || []; - selectionModifiers.select(allIds); - }, [data, selectionModifiers]); // handle filter prop change useEffect(() => { @@ -290,7 +285,6 @@ export const useList = ( isLoading: loadingState, isPending: pendingState, onSelect: selectionModifiers.select, - onSelectAll, onToggleItem: selectionModifiers.toggle, onUnselectItems: selectionModifiers.clearSelection, page, @@ -298,7 +292,6 @@ export const useList = ( resource: '', refetch, selectedIds, - areAllItemsSelected: data?.length === selectedIds.length, setFilters, setPage, setPerPage, diff --git a/packages/ra-core/src/controller/list/useListContextWithProps.ts b/packages/ra-core/src/controller/list/useListContextWithProps.ts index cb4abd796f3..206be897b56 100644 --- a/packages/ra-core/src/controller/list/useListContextWithProps.ts +++ b/packages/ra-core/src/controller/list/useListContextWithProps.ts @@ -33,9 +33,7 @@ import { RaRecord } from '../../types'; * @prop {Function} showFilter a callback to show one of the filters, e.g. showFilter('title', defaultValue) * @prop {Function} hideFilter a callback to hide one of the filters, e.g. hideFilter('title') * @prop {Array} selectedIds an array listing the ids of the selected rows, e.g. [123, 456] - * @prop {boolean} areAllItemsSelected boolean to indicate if the list is already fully selected * @prop {Function} onSelect callback to change the list of selected rows, e.g. onSelect([456, 789]) - * @prop {Function} onSelectAll callback to select all the records, e.g. onSelectAll() * @prop {Function} onToggleItem callback to toggle the selection of a given record based on its id, e.g. onToggleItem(456) * @prop {Function} onUnselectItems callback to clear the selection, e.g. onUnselectItems(); * @prop {string} defaultTitle the translated title based on the resource, e.g. 'Posts' @@ -83,7 +81,6 @@ const extractListContextProps = ({ isLoading, isPending, onSelect, - onSelectAll, onToggleItem, onUnselectItems, page, @@ -91,7 +88,6 @@ const extractListContextProps = ({ refetch, resource, selectedIds, - areAllItemsSelected, setFilters, setPage, setPerPage, @@ -111,7 +107,6 @@ const extractListContextProps = ({ isLoading, isPending, onSelect, - onSelectAll, onToggleItem, onUnselectItems, page, @@ -119,7 +114,6 @@ const extractListContextProps = ({ refetch, resource, selectedIds, - areAllItemsSelected, setFilters, setPage, setPerPage, diff --git a/packages/ra-core/src/controller/list/useListController.ts b/packages/ra-core/src/controller/list/useListController.ts index e1670a9b2c5..e93d9352303 100644 --- a/packages/ra-core/src/controller/list/useListController.ts +++ b/packages/ra-core/src/controller/list/useListController.ts @@ -47,7 +47,6 @@ export const useListController = ( queryOptions = {}, sort = defaultSort, storeKey, - selectAllLimit = 250, } = props; const resource = useResourceContext(props); const { meta, ...otherQueryOptions } = queryOptions; @@ -170,14 +169,6 @@ export const useListController = ( name: getResourceLabel(resource, 2), }); - const onSelectAll = useSelectAll({ - selectAllLimit, - query, - filter, - meta, - resource, - }); - return { sort: currentSort, data, @@ -193,7 +184,6 @@ export const useListController = ( isLoading, isPending, onSelect: selectionModifiers.select, - onSelectAll, onToggleItem: selectionModifiers.toggle, onUnselectItems: selectionModifiers.clearSelection, page: query.page, @@ -201,9 +191,6 @@ export const useListController = ( refetch, resource, selectedIds, - areAllItemsSelected: - total === selectedIds.length || - selectedIds.length >= selectAllLimit, setFilters: queryModifiers.setFilters, setPage: queryModifiers.setPage, setPerPage: queryModifiers.setPerPage, @@ -423,19 +410,6 @@ export interface ListControllerProps { * ); */ storeKey?: string | false; - - /** - * The number of items selected by the "SELECT ALL" button of the bulk actions toolbar. - * - * @see https://marmelab.com/react-admin/List.html#selectalllimit - * @example - * export const PostList = () => ( - * - * ... - * - * ); - */ - selectAllLimit?: number; } const defaultSort = { @@ -502,7 +476,6 @@ export interface ListControllerBaseResult { filterValues: any; hideFilter: (filterName: string) => void; onSelect: (ids: RecordType['id'][]) => void; - onSelectAll: () => void; onToggleItem: (id: RecordType['id']) => void; onUnselectItems: () => void; page: number; @@ -510,7 +483,6 @@ export interface ListControllerBaseResult { refetch: (() => void) | UseGetListHookValue['refetch']; resource: string; selectedIds: RecordType['id'][]; - areAllItemsSelected: boolean; setFilters: ( filters: any, displayedFilters?: any, diff --git a/packages/ra-core/src/controller/list/useSelectAll.tsx b/packages/ra-core/src/controller/list/useSelectAll.tsx index 1eccd5f7105..82dfcff9050 100644 --- a/packages/ra-core/src/controller/list/useSelectAll.tsx +++ b/packages/ra-core/src/controller/list/useSelectAll.tsx @@ -2,40 +2,44 @@ import { useQueryClient } from '@tanstack/react-query'; import { useNotify } from '../../notification'; import { useDataProvider } from '../../dataProvider'; import { useRecordSelection } from './useRecordSelection'; -import type { ListParams } from './useListParams'; -import type { FilterPayload } from '../../types'; +import type { FilterPayload, SortPayload } from '../../types'; +import { useResourceContext } from '../../core'; /** * Function hook to select all items of a list (until we reached the selectAllLimit) * - * @param {string} resource Required. The resource name - * @param {ListParams} query Required. The query object used to fetch the list of items - * @param {number} selectAllLimit Optional. The number of items selected by the "SELECT ALL" button of the bulk actions toolbar + * @param {SortPayload} sort Optional. The sort parameter to apply to the getList query * @param {FilterPayload} filter Optional. Permanent filter applied to all getList queries, regardless of the user selected filters * @param {any} meta Optional. Additional meta data to pass to the dataProvider + * @param {number} limit Optional. The number of items selected by the "SELECT ALL" button of the bulk actions toolbar * @returns {Function} onSelectAll A function to select all items of a list * * @example * * const onSelectAll = useSelectAll({ - * resource: 'posts', - * query: { sort: 'title', order: 'ASC' }, - * selectAllLimit: 250, + * sort: { sort: 'title', order: 'ASC' }, + * limit: 250, * }); * return ; */ export const useSelectAll = ({ - query, - resource, - selectAllLimit = 250, + sort = { field: 'id', order: 'ASC' }, filter = {}, meta = {}, + limit = 250, }: useSelectAllProps): (() => void) => { + const resource = useResourceContext(); + if (!resource) { + throw new Error( + 'useSelectAll should be used inside a ResourceContextProvider' + ); + } const notify = useNotify(); const dataProvider = useDataProvider(); const queryClient = useQueryClient(); const [_, selectionModifiers] = useRecordSelection({ resource }); + // TODO: useCallback const onSelectAll = async () => { try { const { data } = await queryClient.fetchQuery({ @@ -44,9 +48,9 @@ export const useSelectAll = ({ 'getList', { resource, - pagination: { page: 1, perPage: selectAllLimit }, - sort: { field: query.sort, order: query.order }, - filter: { ...query.filter, ...filter }, + pagination: { page: 1, perPage: limit }, + sort, + filter, meta, }, ], @@ -54,19 +58,19 @@ export const useSelectAll = ({ dataProvider.getList(resource, { pagination: { page: 1, - perPage: selectAllLimit, + perPage: limit, }, - sort: { field: query.sort, order: query.order }, - filter: { ...query.filter, ...filter }, + sort, + filter, meta, }), }); const allIds = data?.map(({ id }) => id) || []; selectionModifiers.select(allIds); - if (allIds.length === selectAllLimit) { + if (allIds.length === limit) { notify('ra.message.too_many_elements', { - messageArgs: { max: selectAllLimit }, + messageArgs: { max: limit }, type: 'warning', }); } @@ -81,9 +85,8 @@ export const useSelectAll = ({ }; export interface useSelectAllProps { - resource: string; - query: ListParams; - selectAllLimit?: number; + sort?: SortPayload; filter?: FilterPayload; meta?: any; + limit?: number; } diff --git a/packages/ra-ui-materialui/src/button/SelectAllButton.tsx b/packages/ra-ui-materialui/src/button/SelectAllButton.tsx new file mode 100644 index 00000000000..f9a97edb6e2 --- /dev/null +++ b/packages/ra-ui-materialui/src/button/SelectAllButton.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import { + type GetListParams, + type GetManyReferenceParams, + type RaRecord, + useListContext, + useSelectAll, +} from 'ra-core'; +import type { UseMutationOptions } from '@tanstack/react-query'; +import { Button, ButtonProps } from './Button'; +import { useCallback } from 'react'; + +/** + * Select All button for list forms + * + * @typedef {Object} Props the props you can use + * @prop {string} label Button label. Defaults to 'ra.action.select_all', translated. + * @prop {string} limit Maximum number of items to select. Defaults to 250. + * @prop {function} mutationOptions Object of options passed to react-query. + * + * @param {Props} props + * + * @example // with custom success side effect + * + * const MySelectAllButton = () => { + * const notify = useNotify(); + * const onSuccess = (response) => { + * notify('All items selected!', { type: 'info' }); + * }; + * return ; + * } + */ + +export const SelectAllButton = (props: SelectAllButtonProps) => { + const { + label = 'ra.action.select_all', + limit = 250, + mutationOptions, + ...rest + } = props; + + const { filter, sort, meta, total, selectedIds } = useListContext(); + const onSelectAll = useSelectAll({ limit, filter, sort, meta }); + const handleSelectAll = useCallback(() => { + onSelectAll(); + }, [onSelectAll]); + + if (total === selectedIds.length || selectedIds.length >= limit) + return null; + + return ( +
{children} diff --git a/packages/ra-ui-materialui/src/list/InfiniteList.tsx b/packages/ra-ui-materialui/src/list/InfiniteList.tsx index 50eee6c9f3d..97395664ffc 100644 --- a/packages/ra-ui-materialui/src/list/InfiniteList.tsx +++ b/packages/ra-ui-materialui/src/list/InfiniteList.tsx @@ -36,7 +36,6 @@ import { Loading } from '../layout'; * - perPage: Pagination Size * - queryOptions * - sort: Default Sort Field & Order - * - selectAllLimit: The number of items selected by the "SELECT ALL" button of the bulk actions toolbar of the Datagrid * - title * - sx: CSS API * @@ -74,7 +73,6 @@ export const InfiniteList = ({ resource, sort, storeKey, - selectAllLimit = 250, ...rest }: InfiniteListProps): ReactElement => ( @@ -90,7 +88,6 @@ export const InfiniteList = ({ resource={resource} sort={sort} storeKey={storeKey} - selectAllLimit={selectAllLimit} > {...rest} pagination={pagination} /> diff --git a/packages/ra-ui-materialui/src/list/List.tsx b/packages/ra-ui-materialui/src/list/List.tsx index 2c0e9b20feb..e562bf7ad50 100644 --- a/packages/ra-ui-materialui/src/list/List.tsx +++ b/packages/ra-ui-materialui/src/list/List.tsx @@ -32,7 +32,6 @@ import { Loading } from '../layout'; * - perPage: Pagination Size * - queryOptions * - sort: Default Sort Field & Order - * - selectAllLimit: The number of items selected by the "SELECT ALL" button of the bulk actions toolbar * - title * - sx: CSS API * @@ -69,7 +68,6 @@ export const List = ({ resource, sort, storeKey, - selectAllLimit = 250, ...rest }: ListProps): ReactElement => ( @@ -85,7 +83,6 @@ export const List = ({ resource={resource} sort={sort} storeKey={storeKey} - selectAllLimit={selectAllLimit} > {...rest} /> diff --git a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx index 9a373ad9ba6..29e01ee5108 100644 --- a/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx +++ b/packages/ra-ui-materialui/src/list/datagrid/Datagrid.tsx @@ -35,8 +35,14 @@ import { DatagridClasses, DatagridRoot } from './useDatagridStyles'; import { BulkActionsToolbar } from '../BulkActionsToolbar'; import { BulkDeleteButton } from '../../button'; import { ListNoResults } from '../ListNoResults'; - -const defaultBulkActionButtons = ; +import { SelectAllButton } from '../../button/SelectAllButton'; + +const defaultBulkActionButtons = ( + <> + + + +); /** * The Datagrid component renders a list of records as a table. From 2b6e1d84833c8e5d90879b8b5555e3eabf683344 Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Thu, 5 Dec 2024 22:16:11 +0100 Subject: [PATCH 046/146] remove useless tests --- .../useReferenceArrayFieldController.spec.tsx | 146 +------------ .../useReferenceManyFieldController.spec.tsx | 187 +---------------- .../list/useInfiniteListController.spec.tsx | 194 ----------------- .../src/controller/list/useList.spec.tsx | 109 +--------- .../list/useListController.spec.tsx | 197 ------------------ 5 files changed, 3 insertions(+), 830 deletions(-) diff --git a/packages/ra-core/src/controller/field/useReferenceArrayFieldController.spec.tsx b/packages/ra-core/src/controller/field/useReferenceArrayFieldController.spec.tsx index 610ac73983c..0a06a926efc 100644 --- a/packages/ra-core/src/controller/field/useReferenceArrayFieldController.spec.tsx +++ b/packages/ra-core/src/controller/field/useReferenceArrayFieldController.spec.tsx @@ -1,11 +1,10 @@ import * as React from 'react'; import expect from 'expect'; -import { act, render, waitFor } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import { useReferenceArrayFieldController } from './useReferenceArrayFieldController'; import { testDataProvider } from '../../dataProvider'; import { CoreAdminContext } from '../../core'; -import { ReferenceArrayField } from './ReferenceArrayField.stories'; const ReferenceArrayFieldController = props => { const { children, ...rest } = props; @@ -167,147 +166,4 @@ describe('', () => { }) ); }); - - describe('areAllItemsSelected', () => { - it('should be false if no items are selected', async () => { - const children = jest.fn().mockReturnValue('child'); - render({children}); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: false, - data: [ - { id: 1, title: 'bar1' }, - { id: 2, title: 'bar2' }, - ], - total: 2, - }) - ); - }); - }); - it('should be false if some items are selected', async () => { - const children = jest.fn().mockReturnValue('child'); - render({children}); - act(() => { - children.mock.calls.at(-1)[0].onSelect([1]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: false, - data: [ - { id: 1, title: 'bar1' }, - { id: 2, title: 'bar2' }, - ], - total: 2, - selectedIds: [1], - }) - ); - }); - }); - it('should be true if all items are manually selected', async () => { - const children = jest.fn().mockReturnValue('child'); - render({children}); - act(() => { - children.mock.calls.at(-1)[0].onSelect([1, 2]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: true, - data: [ - { id: 1, title: 'bar1' }, - { id: 2, title: 'bar2' }, - ], - total: 2, - selectedIds: [1, 2], - }) - ); - }); - }); - it('should be true if all items are selected with onSelectAll', async () => { - const children = jest.fn().mockReturnValue('child'); - render({children}); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: false, - data: [ - { id: 1, title: 'bar1' }, - { id: 2, title: 'bar2' }, - ], - total: 2, - selectedIds: [], - }) - ); - }); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: true, - data: [ - { id: 1, title: 'bar1' }, - { id: 2, title: 'bar2' }, - ], - total: 2, - selectedIds: [1, 2], - }) - ); - }); - }); - }); - - describe('onSelectAll', () => { - it('should select all items if no items are selected', async () => { - const children = jest.fn().mockReturnValue('child'); - render({children}); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - data: [ - { id: 1, title: 'bar1' }, - { id: 2, title: 'bar2' }, - ], - }) - ); - }); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [1, 2], - }) - ); - }); - }); - it('should select all items if some items are selected', async () => { - const children = jest.fn().mockReturnValue('child'); - render({children}); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - data: [ - { id: 1, title: 'bar1' }, - { id: 2, title: 'bar2' }, - ], - }) - ); - }); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [1, 2], - }) - ); - }); - }); - }); }); diff --git a/packages/ra-core/src/controller/field/useReferenceManyFieldController.spec.tsx b/packages/ra-core/src/controller/field/useReferenceManyFieldController.spec.tsx index 9d77807b7ea..64afc5570a4 100644 --- a/packages/ra-core/src/controller/field/useReferenceManyFieldController.spec.tsx +++ b/packages/ra-core/src/controller/field/useReferenceManyFieldController.spec.tsx @@ -1,18 +1,11 @@ import * as React from 'react'; -import { - act, - fireEvent, - render, - screen, - waitFor, -} from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import expect from 'expect'; import { testDataProvider } from '../../dataProvider/testDataProvider'; import { CoreAdminContext } from '../../core'; import { useReferenceManyFieldController } from './useReferenceManyFieldController'; import { memoryStore } from '../../store'; -import { Basic, SelectAllLimit } from './ReferenceManyField.stories'; const ReferenceManyFieldController = props => { const { children, page = 1, perPage = 25, ...rest } = props; @@ -419,182 +412,4 @@ describe('useReferenceManyFieldController', () => { ); }); }); - - describe('areAllItemsSelected', () => { - it('should be false if no items are selected', async () => { - const children = jest.fn().mockReturnValue('child'); - render({children}); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: false, - }) - ); - }); - }); - it('should be false if some items are selected', async () => { - const children = jest.fn().mockReturnValue('child'); - render({children}); - act(() => { - children.mock.calls.at(-1)[0].onSelect([1]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: false, - selectedIds: [1], - }) - ); - }); - }); - it('should be true if all items are manually selected', async () => { - const children = jest.fn().mockReturnValue('child'); - render({children}); - act(() => { - children.mock.calls.at(-1)[0].onSelect([0, 1]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: true, - selectedIds: [0, 1], - }) - ); - }); - }); - it('should be true if all items are selected with onSelectAll', async () => { - const children = jest.fn().mockReturnValue('child'); - render({children}); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: true, - selectedIds: [0, 1], - }) - ); - }); - }); - it('should be true if all we manually reached the selectAllLimit', async () => { - const children = jest.fn().mockReturnValue('child'); - render({children}); - act(() => { - children.mock.calls.at(-1)[0].onSelect([0]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: true, - selectedIds: [0], - }) - ); - }); - }); - it('should be true if all we reached the selectAllLimit with onSelectAll', async () => { - const children = jest.fn().mockReturnValue('child'); - render({children}); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: true, - selectedIds: [0], - }) - ); - }); - }); - }); - - describe('onSelectAll', () => { - it('should select all items if no items are selected', async () => { - const children = jest.fn().mockReturnValue('child'); - render({children}); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [0, 1], - }) - ); - }); - }); - it('should select all items if some items are selected', async () => { - const children = jest.fn().mockReturnValue('child'); - render({children}); - act(() => { - children.mock.calls.at(-1)[0].onSelect([1]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [1], - }) - ); - }); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [0, 1], - }) - ); - }); - }); - it('should select the maximum items possible until we reached the selectAllLimit', async () => { - const children = jest.fn().mockReturnValue('child'); - const getManyReference = jest - .fn() - .mockImplementation((_resource, params) => - Promise.resolve({ - data: [{ id: 0 }, { id: 1 }].slice( - 0, - params.pagination.perPage - ), - total: 2, - }) - ); - const dataProvider = testDataProvider({ - getManyReference, - }); - render( - - {children} - - ); - await waitFor(() => { - expect(children).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ - selectedIds: [], - }) - ); - }); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [0], - }) - ); - }); - await waitFor(() => { - expect(getManyReference).toHaveBeenCalledWith( - 'books', - expect.objectContaining({ - pagination: { page: 1, perPage: 1 }, - }) - ); - }); - }); - }); }); diff --git a/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx b/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx index 8470e2d2364..10aacf7eeb8 100644 --- a/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx +++ b/packages/ra-core/src/controller/list/useInfiniteListController.spec.tsx @@ -22,7 +22,6 @@ import { import { CoreAdminContext } from '../../core'; import { TestMemoryRouter } from '../../routing'; import { - Basic, Authenticated, CanAccess, DisableAuthentication, @@ -46,199 +45,6 @@ describe('useInfiniteListController', () => { debounce: 200, }; - const dataProvider = testDataProvider({ - getList: jest.fn().mockImplementation((_resource, params) => - Promise.resolve({ - data: [{ id: 0 }, { id: 1 }].slice( - 0, - params.pagination.perPage - ), - total: 2, - }) - ), - }); - - describe('areAllItemsSelected', () => { - it('should be false if no items are selected', async () => { - const children = jest.fn().mockReturnValue(children); - render(); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: false, - data: [{ id: 0 }, { id: 1 }], - }) - ); - }); - }); - it('should be false if some items are selected', async () => { - const children = jest.fn().mockReturnValue(children); - render(); - act(() => { - children.mock.calls.at(-1)[0].onSelect([0]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: false, - selectedIds: [0], - }) - ); - }); - }); - it('should be true if all items are manually selected', async () => { - const children = jest.fn().mockReturnValue(children); - render(); - act(() => { - children.mock.calls.at(-1)[0].onSelect([0, 1]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: true, - selectedIds: [0, 1], - }) - ); - }); - }); - it('should be true if all items are selected with onSelectAll', async () => { - const children = jest.fn().mockReturnValue(children); - render(); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: true, - selectedIds: [0, 1], - }) - ); - }); - }); - it('should be true if all we manually reached the selectAllLimit', async () => { - const children = jest.fn().mockReturnValue(children); - render( - - ); - act(() => { - children.mock.calls.at(-1)[0].onSelect([0]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: true, - selectedIds: [0], - }) - ); - }); - }); - it('should be true if all we reached the selectAllLimit with onSelectAll', async () => { - const children = jest.fn().mockReturnValue(children); - render( - - ); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: true, - selectedIds: [0], - }) - ); - }); - }); - }); - - describe('onSelectAll', () => { - it('should select all items if no items are selected', async () => { - const children = jest.fn().mockReturnValue(children); - render(); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [0, 1], - }) - ); - }); - }); - it('should select all items if some items are selected', async () => { - const children = jest.fn().mockReturnValue(children); - render(); - act(() => { - children.mock.calls.at(-1)[0].onSelect([0]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [0], - }) - ); - }); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [0, 1], - }) - ); - }); - }); - it('should select the maximum items possible until we reached the selectAllLimit', async () => { - const getList = jest.fn().mockImplementation((_resource, params) => - Promise.resolve({ - data: [{ id: 0 }, { id: 1 }].slice( - 0, - params.pagination.perPage - ), - total: 2, - }) - ); - const mockedDataProvider = testDataProvider({ getList }); - const children = jest.fn().mockReturnValue(children); - render( - - ); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [0], - }) - ); - }); - await waitFor(() => { - expect(getList).toHaveBeenCalledWith( - 'posts', - expect.objectContaining({ - pagination: { page: 1, perPage: 1 }, - }) - ); - }); - }); - }); - describe('queryOptions', () => { it('should accept custom client query options', async () => { jest.spyOn(console, 'error').mockImplementationOnce(() => {}); diff --git a/packages/ra-core/src/controller/list/useList.spec.tsx b/packages/ra-core/src/controller/list/useList.spec.tsx index 18ed205a1e0..5d96aada58e 100644 --- a/packages/ra-core/src/controller/list/useList.spec.tsx +++ b/packages/ra-core/src/controller/list/useList.spec.tsx @@ -3,7 +3,7 @@ import { ReactNode } from 'react'; import expect from 'expect'; import { useList, UseListOptions, UseListValue } from './useList'; -import { act, fireEvent, render, waitFor } from '@testing-library/react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; import { ListContextProvider } from './ListContextProvider'; import { useListContext } from './useListContext'; @@ -315,111 +315,4 @@ describe('', () => { ); }); }); - - describe('areAllItemsSelected', () => { - it('should be false if no items are selected', async () => { - const children = jest.fn(); - const data = [{ id: 0 }, { id: 1 }]; - render(); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: false, - data: [{ id: 0 }, { id: 1 }], - }) - ); - }); - }); - it('should be false if some items are selected', async () => { - const children = jest.fn(); - const data = [{ id: 0 }, { id: 1 }]; - render(); - act(() => { - children.mock.calls.at(-1)[0].onSelect([1]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: false, - selectedIds: [1], - }) - ); - }); - }); - it('should be true if all items are manually selected', async () => { - const children = jest.fn(); - const data = [{ id: 0 }, { id: 1 }]; - render(); - act(() => { - children.mock.calls.at(-1)[0].onSelect([0, 1]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: true, - selectedIds: [0, 1], - }) - ); - }); - }); - it('should be true if all items are selected with onSelectAll', async () => { - const children = jest.fn(); - const data = [{ id: 0 }, { id: 1 }]; - render(); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: true, - selectedIds: [0, 1], - }) - ); - }); - }); - }); - - describe('onSelectAll', () => { - it('should select all items if no items are selected', async () => { - const children = jest.fn(); - const data = [{ id: 0 }, { id: 1 }]; - render(); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [0, 1], - }) - ); - }); - }); - it('should select all items if some items are selected', async () => { - const children = jest.fn(); - const data = [{ id: 0 }, { id: 1 }]; - render(); - act(() => { - children.mock.calls.at(-1)[0].onSelect([1]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [1], - }) - ); - }); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [0, 1], - }) - ); - }); - }); - }); }); diff --git a/packages/ra-core/src/controller/list/useListController.spec.tsx b/packages/ra-core/src/controller/list/useListController.spec.tsx index 40445cb9efe..25c533febf8 100644 --- a/packages/ra-core/src/controller/list/useListController.spec.tsx +++ b/packages/ra-core/src/controller/list/useListController.spec.tsx @@ -22,7 +22,6 @@ import { CanAccess, DisableAuthentication, } from './useListController.security.stories'; -import { Basic } from './useListController.stories'; describe('useListController', () => { const defaultProps = { @@ -586,200 +585,4 @@ describe('useListController', () => { expect(authProvider.checkAuth).not.toHaveBeenCalled(); }); }); - - const dataProvider = testDataProvider({ - getList: jest.fn().mockImplementation((_resource, params) => - Promise.resolve({ - data: [{ id: 0 }, { id: 1 }].slice( - 0, - params.pagination.perPage - ), - total: 2, - }) - ), - }); - describe('areAllItemsSelected', () => { - it('should be false if no items are selected', async () => { - const children = jest.fn().mockReturnValue(<>); - render(); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: false, - data: [{ id: 0 }, { id: 1 }], - }) - ); - }); - }); - it('should be false if some items are selected', async () => { - const children = jest.fn().mockReturnValue(<>); - render(); - act(() => { - children.mock.calls.at(-1)[0].onSelect([0]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: false, - selectedIds: [0], - }) - ); - }); - }); - it('should be true if all items are manually selected', async () => { - const children = jest.fn().mockReturnValue(<>); - render(); - act(() => { - children.mock.calls.at(-1)[0].onSelect([0, 1]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: true, - selectedIds: [0, 1], - }) - ); - }); - }); - it('should be true if all items are selected with onSelectAll', async () => { - const children = jest.fn().mockReturnValue(<>); - render(); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [0, 1], - }) - ); - }); - }); - it('should be true if all we manually reached the selectAllLimit', async () => { - const children = jest.fn().mockReturnValue(<>); - render( - - ); - act(() => { - children.mock.calls.at(-1)[0].onSelect([0]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: true, - selectedIds: [0], - }) - ); - }); - }); - it('should be true if all we reached the selectAllLimit with onSelectAll', async () => { - const children = jest.fn().mockReturnValue(<>); - render( - - ); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - areAllItemsSelected: true, - data: [{ id: 0 }, { id: 1 }], - total: 2, - selectedIds: [0], - }) - ); - }); - }); - }); - - describe('onSelectAll', () => { - it('should select all items if no items are selected', async () => { - const children = jest.fn().mockReturnValue(<>); - render(); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [0, 1], - }) - ); - }); - }); - it('should select all items if some items are selected', async () => { - const children = jest.fn().mockReturnValue(<>); - render(); - act(() => { - children.mock.calls.at(-1)[0].onSelect([0]); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [0], - }) - ); - }); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [0, 1], - }) - ); - }); - }); - it('should select the maximum items possible until we reached the selectAllLimit', async () => { - const getList = jest.fn().mockImplementation((_resource, params) => - Promise.resolve({ - data: [{ id: 0 }, { id: 1 }].slice( - 0, - params.pagination.perPage - ), - total: 2, - }) - ); - const dataProvider = testDataProvider({ getList }); - const children = jest.fn().mockReturnValue(<>); - render( - - ); - act(() => { - children.mock.calls.at(-1)[0].onSelectAll(); - }); - await waitFor(() => { - expect(children).toHaveBeenCalledWith( - expect.objectContaining({ - selectedIds: [0], - }) - ); - }); - await waitFor(() => { - expect(getList).toHaveBeenCalledWith( - 'posts', - expect.objectContaining({ - pagination: { page: 1, perPage: 1 }, - }) - ); - }); - }); - }); }); From f27f69b7f9e712be064798c2973c1b4bb9d25b5e Mon Sep 17 00:00:00 2001 From: erwanMarmelab Date: Thu, 5 Dec 2024 22:21:57 +0100 Subject: [PATCH 047/146] remove useless stories --- .../field/ReferenceArrayField.stories.tsx | 6 ---- .../field/ReferenceManyField.stories.tsx | 33 +++---------------- .../useInfiniteListController.stories.tsx | 24 ++------------ .../list/useListController.stories.tsx | 24 +------------- 4 files changed, 7 insertions(+), 80 deletions(-) diff --git a/packages/ra-core/src/controller/field/ReferenceArrayField.stories.tsx b/packages/ra-core/src/controller/field/ReferenceArrayField.stories.tsx index 393fba8146a..dd9cb01f1a4 100644 --- a/packages/ra-core/src/controller/field/ReferenceArrayField.stories.tsx +++ b/packages/ra-core/src/controller/field/ReferenceArrayField.stories.tsx @@ -38,12 +38,6 @@ const ReferenceArrayFieldComponent = (props: ListControllerResult) => ( gap: '10px', }} > -

Selected ids: {JSON.stringify(params.selectedIds)}

- {params.selectAllLimit && ( -

selectAllLimit : {params.selectAllLimit}

- )}