diff --git a/cypress/e2e/list.cy.js b/cypress/e2e/list.cy.js
index 15648cae0f7..a88bc9c4913 100644
--- a/cypress/e2e/list.cy.js
+++ b/cypress/e2e/list.cy.js
@@ -170,6 +170,37 @@ describe('List Page', () => {
);
});
+ it('should not create duplicate selections when checking page after individual selections', () => {
+ cy.contains('1-10 of 13'); // wait for data
+ cy.get('input[type="checkbox"]').eq(1).click();
+ cy.get('input[type="checkbox"]').eq(2).click();
+ cy.contains('2 items selected');
+ ListPagePosts.toggleSelectAll();
+ cy.contains('10 items selected');
+ cy.get(ListPagePosts.elements.selectedItem).should(els =>
+ expect(els).to.have.length(10)
+ );
+ });
+
+ it('should handle uncheck and recheck without duplicates', () => {
+ cy.contains('1-10 of 13'); // wait for data
+ cy.get('input[type="checkbox"]').eq(1).click();
+ cy.get('input[type="checkbox"]').eq(2).click();
+ cy.get('input[type="checkbox"]').eq(3).click();
+ cy.contains('3 items selected');
+ ListPagePosts.toggleSelectAll();
+ cy.contains('10 items selected');
+ ListPagePosts.toggleSelectAll();
+ cy.get(ListPagePosts.elements.bulkActionsToolbar).should(
+ 'not.be.visible'
+ );
+ ListPagePosts.toggleSelectAll();
+ cy.contains('10 items selected');
+ cy.get(ListPagePosts.elements.selectedItem).should(els =>
+ expect(els).to.have.length(10)
+ );
+ });
+
it('should allow to trigger a custom bulk action on selected items', () => {
cy.contains('1-10 of 13'); // wait for data
ListPagePosts.toggleSelectAll();
diff --git a/packages/ra-ui-materialui/src/list/datatable/DataTable.spec.tsx b/packages/ra-ui-materialui/src/list/datatable/DataTable.spec.tsx
index 219ed3a535d..0277f304871 100644
--- a/packages/ra-ui-materialui/src/list/datatable/DataTable.spec.tsx
+++ b/packages/ra-ui-materialui/src/list/datatable/DataTable.spec.tsx
@@ -270,6 +270,33 @@ describe('DataTable', () => {
fireEvent.click(checkboxes[4], { shiftKey: true });
await screen.findByText('4 items selected');
});
+ it('should not create duplicate selections when checking page after individual selections', async () => {
+ render();
+ const checkboxes = await screen.findAllByRole('checkbox');
+ fireEvent.click(checkboxes[1]);
+ fireEvent.click(checkboxes[2]);
+ await screen.findByText('2 items selected');
+ fireEvent.click(checkboxes[0]);
+ await screen.findByText('5 items selected');
+ const selectAllButton = await screen.findByText('Select all');
+ selectAllButton.click();
+ await screen.findByText('7 items selected');
+ });
+ it('should handle uncheck and recheck without duplicates', async () => {
+ render();
+ const checkboxes = await screen.findAllByRole('checkbox');
+ fireEvent.click(checkboxes[1]);
+ fireEvent.click(checkboxes[2]);
+ await screen.findByText('2 items selected');
+ fireEvent.click(checkboxes[0]);
+ await screen.findByText('5 items selected');
+ fireEvent.click(checkboxes[0]);
+ await waitFor(() => {
+ expect(screen.queryByText('items selected')).toBeNull();
+ });
+ fireEvent.click(checkboxes[0]);
+ await screen.findByText('5 items selected');
+ });
});
describe('isRowSelectable', () => {
it('should allow to disable row selection', async () => {
diff --git a/packages/ra-ui-materialui/src/list/datatable/SelectPageCheckbox.tsx b/packages/ra-ui-materialui/src/list/datatable/SelectPageCheckbox.tsx
index 48b67f2fe78..87e8642814f 100644
--- a/packages/ra-ui-materialui/src/list/datatable/SelectPageCheckbox.tsx
+++ b/packages/ra-ui-materialui/src/list/datatable/SelectPageCheckbox.tsx
@@ -1,12 +1,12 @@
-import * as React from 'react';
-import { useCallback } from 'react';
+import { Checkbox } from '@mui/material';
import {
useDataTableCallbacksContext,
useDataTableDataContext,
useDataTableSelectedIdsContext,
useTranslate,
} from 'ra-core';
-import { Checkbox } from '@mui/material';
+import * as React from 'react';
+import { useCallback } from 'react';
export const SelectPageCheckbox = () => {
const data = useDataTableDataContext();
@@ -22,11 +22,11 @@ export const SelectPageCheckbox = () => {
event.target.checked
? selectedIds.concat(
data
- .filter(record =>
- !selectedIds.includes(record.id) &&
- isRowSelectable
- ? isRowSelectable(record)
- : true
+ .filter(
+ record =>
+ !selectedIds.includes(record.id) &&
+ (!isRowSelectable ||
+ isRowSelectable(record))
)
.map(record => record.id)
)