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) )