mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 15:59:41 +02:00
fix(app): datatable global checkbox doesn't reflect the selected state (#470)
This commit is contained in:
parent
438b1f9815
commit
b57855f20d
10 changed files with 110 additions and 18 deletions
|
@ -1,4 +1,5 @@
|
|||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
createColumnHelper,
|
||||
|
@ -170,7 +171,7 @@ describe('Datatable', () => {
|
|||
fireEvent.click(selectAllCheckbox);
|
||||
|
||||
// Check if all rows on the page are selected
|
||||
expect(screen.getByText('2 item(s) selected')).toBeInTheDocument();
|
||||
expect(screen.getByText('2 items selected')).toBeInTheDocument();
|
||||
|
||||
// Deselect
|
||||
fireEvent.click(selectAllCheckbox);
|
||||
|
@ -192,13 +193,44 @@ describe('Datatable', () => {
|
|||
fireEvent.click(selectAllCheckbox, { shiftKey: true });
|
||||
|
||||
// Check if all rows on the page are selected
|
||||
expect(screen.getByText('3 item(s) selected')).toBeInTheDocument();
|
||||
expect(screen.getByText('3 items selected')).toBeInTheDocument();
|
||||
|
||||
// Deselect
|
||||
fireEvent.click(selectAllCheckbox, { shiftKey: true });
|
||||
const checkboxes: HTMLInputElement[] = screen.queryAllByRole('checkbox');
|
||||
expect(checkboxes.filter((checkbox) => checkbox.checked).length).toBe(0);
|
||||
});
|
||||
|
||||
it('shows indeterminate state and correct footer text when hidden rows are selected', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<DatatableWithStore
|
||||
dataset={mockData}
|
||||
columns={mockColumns}
|
||||
data-cy="test-table"
|
||||
title="Test table with search"
|
||||
/>
|
||||
);
|
||||
|
||||
// Select Jane
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
await user.click(checkboxes[2]); // Select the second row
|
||||
|
||||
// Search for John (will hide selected Jane)
|
||||
const searchInput = screen.getByPlaceholderText('Search...');
|
||||
await user.type(searchInput, 'John');
|
||||
|
||||
// Check if the footer text is correct
|
||||
expect(
|
||||
await screen.findByText('1 item selected (1 hidden by filters)')
|
||||
).toBeInTheDocument();
|
||||
|
||||
// Check if the checkbox is indeterminate
|
||||
const selectAllCheckbox: HTMLInputElement =
|
||||
screen.getByLabelText('Select all rows');
|
||||
expect(selectAllCheckbox.indeterminate).toBe(true);
|
||||
expect(selectAllCheckbox.checked).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// Test the defaultGlobalFilterFn used in searches
|
||||
|
|
|
@ -171,6 +171,14 @@ export function Datatable<D extends DefaultType>({
|
|||
|
||||
const selectedRowModel = tableInstance.getSelectedRowModel();
|
||||
const selectedItems = selectedRowModel.rows.map((row) => row.original);
|
||||
const filteredItems = tableInstance
|
||||
.getFilteredRowModel()
|
||||
.rows.map((row) => row.original);
|
||||
|
||||
const hiddenSelectedItems = useMemo(
|
||||
() => _.difference(selectedItems, filteredItems),
|
||||
[selectedItems, filteredItems]
|
||||
);
|
||||
|
||||
return (
|
||||
<Table.Container noWidget={noWidget} aria-label={title}>
|
||||
|
@ -203,6 +211,7 @@ export function Datatable<D extends DefaultType>({
|
|||
pageSize={tableState.pagination.pageSize}
|
||||
pageCount={tableInstance.getPageCount()}
|
||||
totalSelected={selectedItems.length}
|
||||
totalHiddenSelected={hiddenSelectedItems.length}
|
||||
/>
|
||||
</Table.Container>
|
||||
);
|
||||
|
|
|
@ -5,6 +5,7 @@ import { SelectedRowsCount } from './SelectedRowsCount';
|
|||
|
||||
interface Props {
|
||||
totalSelected: number;
|
||||
totalHiddenSelected: number;
|
||||
pageSize: number;
|
||||
page: number;
|
||||
onPageChange(page: number): void;
|
||||
|
@ -14,6 +15,7 @@ interface Props {
|
|||
|
||||
export function DatatableFooter({
|
||||
totalSelected,
|
||||
totalHiddenSelected,
|
||||
pageSize,
|
||||
page,
|
||||
onPageChange,
|
||||
|
@ -22,7 +24,7 @@ export function DatatableFooter({
|
|||
}: Props) {
|
||||
return (
|
||||
<Table.Footer>
|
||||
<SelectedRowsCount value={totalSelected} />
|
||||
<SelectedRowsCount value={totalSelected} hidden={totalHiddenSelected} />
|
||||
<PaginationControls
|
||||
showAll
|
||||
pageLimit={pageSize}
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
import { addPlural } from '@/react/common/string-utils';
|
||||
|
||||
interface SelectedRowsCountProps {
|
||||
value: number;
|
||||
hidden: number;
|
||||
}
|
||||
|
||||
export function SelectedRowsCount({ value }: SelectedRowsCountProps) {
|
||||
export function SelectedRowsCount({ value, hidden }: SelectedRowsCountProps) {
|
||||
return value !== 0 ? (
|
||||
<div className="infoBar">{value} item(s) selected</div>
|
||||
<div className="infoBar">
|
||||
{addPlural(value, 'item')} selected
|
||||
{hidden !== 0 && ` (${hidden} hidden by filters)`}
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,19 @@
|
|||
import { ColumnDef, Row } from '@tanstack/react-table';
|
||||
import { ColumnDef, Row, Table } from '@tanstack/react-table';
|
||||
|
||||
import { Checkbox } from '@@/form-components/Checkbox';
|
||||
|
||||
function allRowsSelected<T>(table: Table<T>) {
|
||||
return table.getCoreRowModel().rows.every((row) => row.getIsSelected());
|
||||
}
|
||||
|
||||
function someRowsSelected<T>(table: Table<T>) {
|
||||
return table.getCoreRowModel().rows.some((row) => row.getIsSelected());
|
||||
}
|
||||
|
||||
function somePageRowsSelected<T>(table: Table<T>) {
|
||||
return table.getRowModel().rows.some((row) => row.getIsSelected());
|
||||
}
|
||||
|
||||
export function createSelectColumn<T>(dataCy: string): ColumnDef<T> {
|
||||
let lastSelectedId = '';
|
||||
|
||||
|
@ -11,15 +23,15 @@ export function createSelectColumn<T>(dataCy: string): ColumnDef<T> {
|
|||
<Checkbox
|
||||
id="select-all"
|
||||
data-cy={`select-all-checkbox-${dataCy}`}
|
||||
checked={table.getIsAllPageRowsSelected()}
|
||||
indeterminate={table.getIsSomeRowsSelected()}
|
||||
checked={allRowsSelected(table)}
|
||||
indeterminate={!allRowsSelected(table) && someRowsSelected(table)}
|
||||
onChange={(e) => {
|
||||
// Select all rows if shift key is held down, otherwise only page rows
|
||||
if (e.nativeEvent instanceof MouseEvent && e.nativeEvent.shiftKey) {
|
||||
table.getToggleAllRowsSelectedHandler()(e);
|
||||
table.toggleAllRowsSelected();
|
||||
return;
|
||||
}
|
||||
table.getToggleAllPageRowsSelectedHandler()(e);
|
||||
table.toggleAllPageRowsSelected(!somePageRowsSelected(table));
|
||||
}}
|
||||
disabled={table.getRowModel().rows.every((row) => !row.getCanSelect())}
|
||||
onClick={(e) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue