Skip to content

Commit f319e71

Browse files
authored
Merge pull request #1938 from dxc-technology/Mil4n0r/resultsettable_checkbox-fix
Added new logic for resultsetTable to manage sorting and adding/removing rows properly
2 parents c2b6ec9 + 771a460 commit f319e71

File tree

5 files changed

+121
-64
lines changed

5 files changed

+121
-64
lines changed

lib/src/divider/Divider.stories.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import React from "react";
22
import Title from "../../.storybook/components/Title";
33
import DxcDivider from "./Divider";
4-
import { DxcFlex, DxcParagraph } from "../main";
4+
import DxcFlex from "../flex/Flex";
5+
import DxcParagraph from "../paragraph/Paragraph";
56
import ExampleContainer from "../../.storybook/components/ExampleContainer";
67

78
export default {

lib/src/icon/Icon.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from "react";
22
import DxcIcon from "./Icon";
33
import Title from "../../.storybook/components/Title";
44
import ExampleContainer from "../../.storybook/components/ExampleContainer";
5-
import { DxcTypography } from "../main";
5+
import DxcTypography from "../typography/Typography";
66

77
export default {
88
title: "Icon",

lib/src/resultset-table/ResultsetTable.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,5 +409,5 @@ export const DropdownAction = ResultsetActionsCellDropdown.bind({});
409409
DropdownAction.play = async ({ canvasElement }) => {
410410
const canvas = within(canvasElement);
411411
const dropdown = canvas.getAllByRole("button")[5];
412-
await userEvent.click(dropdown);
412+
userEvent.click(dropdown);
413413
};

lib/src/resultset-table/ResultsetTable.test.tsx

Lines changed: 73 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { render, fireEvent, act } from "@testing-library/react";
33
import userEvent from "@testing-library/user-event";
44
import DxcResultsetTable from "./ResultsetTable";
55
import { ActionCellsPropsType } from "../table/types";
6+
import DxcCheckbox from "../checkbox/Checkbox";
67

78
// Mocking DOMRect for Radix Primitive Popover
89
(global as any).globalThis = global;
@@ -178,54 +179,43 @@ const rows = [
178179
},
179180
],
180181
];
181-
const rows2 = [
182+
183+
const columnsWithCheckbox = [
184+
{ displayValue: "Id", isSortable: true },
185+
{ displayValue: "Checkbox", isSortable: false },
186+
{ displayValue: "Name", isSortable: false },
187+
{ displayValue: "City", isSortable: false },
188+
];
189+
190+
const rowsWithCheckbox = [
182191
[
192+
{ displayValue: "001", sortValue: "001" },
183193
{
184-
displayValue: "546",
185-
sortValue: "465",
186-
},
187-
{
188-
displayValue: "OtherValue",
189-
sortValue: "OtherValue",
190-
},
191-
{
192-
displayValue: "OtherValue",
193-
sortValue: "OtherValue",
194+
displayValue: <DxcCheckbox size="fillParent" defaultChecked={true} />,
194195
},
196+
{ displayValue: "Peter" },
197+
{ displayValue: "Miami" },
195198
],
196199
[
200+
{ displayValue: "002", sortValue: "002" },
197201
{
198-
displayValue: "978",
199-
sortValue: "465",
200-
},
201-
{
202-
displayValue: "OtherValue",
203-
sortValue: "OtherValue",
204-
},
205-
{
206-
displayValue: "OtherValue",
207-
sortValue: "OtherValue",
208-
},
209-
{
210-
displayValue: "",
202+
displayValue: <DxcCheckbox size="fillParent" />,
211203
},
204+
{ displayValue: "Louis" },
205+
{ displayValue: "London" },
212206
],
213207
[
208+
{ displayValue: "003", sortValue: "003" },
214209
{
215-
displayValue: "678",
216-
sortValue: "344",
217-
},
218-
{
219-
displayValue: "OtherValue",
220-
sortValue: "OtherValue",
221-
},
222-
{
223-
displayValue: "OtherValue",
224-
sortValue: "OtherValue",
210+
displayValue: <DxcCheckbox size="fillParent" />,
225211
},
212+
{ displayValue: "Lana" },
213+
{ displayValue: "Amsterdam" },
226214
],
227215
];
228216

217+
const rows2 = [...rows].slice(0, -1);
218+
229219
describe("Resultset table component tests", () => {
230220
test("Resultset table rendered correctly", () => {
231221
const { getByText } = render(<DxcResultsetTable columns={columns} rows={rows} itemsPerPage={3} />);
@@ -256,7 +246,7 @@ describe("Resultset table component tests", () => {
256246
window.HTMLElement.prototype.scrollIntoView = () => {};
257247
window.HTMLElement.prototype.scrollTo = () => {};
258248
const { getByText, getAllByRole } = render(
259-
<DxcResultsetTable columns={columns} showGoToPage rows={rows} itemsPerPage={3} />
249+
<DxcResultsetTable columns={columns} showGoToPage rows={rows} itemsPerPage={3} />,
260250
);
261251
expect(getByText("Peter")).toBeTruthy();
262252
expect(getByText("Louis")).toBeTruthy();
@@ -294,16 +284,57 @@ describe("Resultset table component tests", () => {
294284
expect(component.queryByText("Cosmin")).not.toBeTruthy();
295285
});
296286

297-
test("Resultset table change rows should go to first page", () => {
298-
const { queryByText, rerender } = render(<DxcResultsetTable columns={columns} rows={rows} itemsPerPage={3} />);
287+
test("Resultset table should go one page back when removing the last page data", () => {
288+
const { getAllByRole, queryByText, rerender } = render(
289+
<DxcResultsetTable columns={columns} rows={rows} itemsPerPage={3} />,
290+
);
291+
expect(queryByText("1 to 3 of 10")).toBeTruthy();
292+
const lastButton = getAllByRole("button")[4];
299293
expect(queryByText("Peter")).toBeTruthy();
294+
fireEvent.click(lastButton);
295+
expect(queryByText("10 to 10 of 10")).toBeTruthy();
300296
rerender(<DxcResultsetTable columns={columns} rows={rows2} itemsPerPage={3} />);
301-
expect(queryByText("1 to 3 of 3")).toBeTruthy();
297+
expect(queryByText("7 to 9 of 9")).toBeTruthy();
298+
});
299+
300+
test("Resultset table shouldn't go one page back when there is data left in the last page", () => {
301+
const { getAllByRole, queryByText, rerender } = render(
302+
<DxcResultsetTable columns={columns} rows={rows} itemsPerPage={2} />,
303+
);
304+
expect(queryByText("1 to 2 of 10")).toBeTruthy();
305+
const lastButton = getAllByRole("button")[4];
306+
expect(queryByText("Peter")).toBeTruthy();
307+
fireEvent.click(lastButton);
308+
expect(queryByText("9 to 10 of 10")).toBeTruthy();
309+
rerender(<DxcResultsetTable columns={columns} rows={rows2} itemsPerPage={2} />);
310+
expect(queryByText("9 to 9 of 9")).toBeTruthy();
311+
});
312+
313+
test("Resultset table uncontrolled components maintain its value when sorting", async () => {
314+
const { getAllByRole } = render(
315+
<DxcResultsetTable columns={columnsWithCheckbox} rows={rowsWithCheckbox} itemsPerPage={3} />,
316+
);
317+
const columnHeader = getAllByRole("columnheader")[0];
318+
const sortButton = getAllByRole("button")[0];
319+
320+
expect(getAllByRole("checkbox")[0].getAttribute("aria-checked")).toBe("true");
321+
322+
expect(columnHeader.getAttribute("aria-sort")).toBe("none");
323+
324+
fireEvent.click(sortButton);
325+
326+
expect(columnHeader.getAttribute("aria-sort")).toBe("ascending");
327+
328+
fireEvent.click(sortButton);
329+
330+
expect(columnHeader.getAttribute("aria-sort")).toBe("descending");
331+
332+
expect(getAllByRole("checkbox")[0].getAttribute("aria-checked")).toBe("false");
302333
});
303334

304335
test("Resultset table change itemsPerPage should go to first page", () => {
305336
const { getAllByRole } = render(
306-
<DxcResultsetTable columns={columns} rows={rows} itemsPerPage={3} itemsPerPageOptions={[2, 3]} />
337+
<DxcResultsetTable columns={columns} rows={rows} itemsPerPage={3} itemsPerPageOptions={[2, 3]} />,
307338
);
308339
const lastButton = getAllByRole("button")[4];
309340
expect(getAllByRole("row").length - 1).toEqual(3);
@@ -320,7 +351,7 @@ describe("Resultset table component tests", () => {
320351
test("Resultset table with ActionsCell", () => {
321352
const onSelectOption = jest.fn();
322353
const onClick = jest.fn();
323-
const actions: ActionCellsPropsType['actions'] = [
354+
const actions: ActionCellsPropsType["actions"] = [
324355
{
325356
title: "icon1",
326357
onClick: onSelectOption,
@@ -344,7 +375,7 @@ describe("Resultset table component tests", () => {
344375
title: "icon2",
345376
onClick: onClick,
346377
},
347-
] ;
378+
];
348379
const actionRows = [
349380
[
350381
{
@@ -362,7 +393,7 @@ describe("Resultset table component tests", () => {
362393
],
363394
];
364395
const { getAllByRole, getByRole, getByText } = render(
365-
<DxcResultsetTable columns={columns} rows={actionRows} itemsPerPage={3} />
396+
<DxcResultsetTable columns={columns} rows={actionRows} itemsPerPage={3} />,
366397
);
367398
const dropdown = getAllByRole("button")[2];
368399
act(() => {

lib/src/resultset-table/ResultsetTable.tsx

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useMemo, useEffect } from "react";
1+
import React, { useState, useMemo, useEffect, useRef } from "react";
22
import styled, { ThemeProvider } from "styled-components";
33
import { spaces } from "../common/variables";
44
import DxcPaginator from "../paginator/Paginator";
@@ -12,10 +12,10 @@ import CoreTokens from "../common/coreTokens";
1212
const normalizeSortValue = (sortValue: string | React.ReactNode) =>
1313
typeof sortValue === "string" ? sortValue.toUpperCase() : sortValue;
1414

15-
const sortArray = (index: number, order: "ascending" | "descending", resultset: Row[][]) =>
15+
const sortArray = (index: number, order: "ascending" | "descending", resultset: { id: string; cells: Row[] }[]) =>
1616
resultset.slice().sort((element1, element2) => {
17-
const sortValueA = normalizeSortValue(element1[index].sortValue || element1[index].displayValue);
18-
const sortValueB = normalizeSortValue(element2[index].sortValue || element2[index].displayValue);
17+
const sortValueA = normalizeSortValue(element1.cells[index].sortValue || element1[index].displayValue);
18+
const sortValueB = normalizeSortValue(element2.cells[index].sortValue || element2[index].displayValue);
1919
let comparison = 0;
2020
if (typeof sortValueA === "object") {
2121
comparison = -1;
@@ -36,9 +36,19 @@ const getMaxItemsPerPageIndex = (
3636
minItemsPerPageIndex: number,
3737
itemsPerPage: number,
3838
resultset: Row[][],
39-
page: number
39+
page: number,
4040
) => (minItemsPerPageIndex + itemsPerPage > resultset.length ? resultset.length : itemsPerPage * page - 1);
4141

42+
const assignIdsToRows = (resultset: Row[][]) => {
43+
if (resultset.length > 0) {
44+
return resultset.map((row, index) => ({
45+
cells: row,
46+
id: `row_${index}`,
47+
}));
48+
}
49+
return [];
50+
};
51+
4252
const DxcResultsetTable = ({
4353
columns,
4454
rows,
@@ -56,18 +66,23 @@ const DxcResultsetTable = ({
5666
const [sortColumnIndex, changeSortColumnIndex] = useState(-1);
5767
const [sortOrder, changeSortOrder] = useState<"ascending" | "descending">("ascending");
5868

69+
const prevRowCountRef = useRef<number>(rows.length);
70+
71+
const rowsWithIds = useMemo(() => assignIdsToRows(rows), [rows]);
72+
5973
const minItemsPerPageIndex = useMemo(() => getMinItemsPerPageIndex(page, itemsPerPage, page), [itemsPerPage, page]);
6074
const maxItemsPerPageIndex = useMemo(
6175
() => getMaxItemsPerPageIndex(minItemsPerPageIndex, itemsPerPage, rows, page),
62-
[itemsPerPage, minItemsPerPageIndex, page, rows]
76+
[itemsPerPage, minItemsPerPageIndex, page, rows],
6377
);
78+
6479
const sortedResultset = useMemo(
65-
() => (sortColumnIndex !== -1 ? sortArray(sortColumnIndex, sortOrder, rows) : rows),
66-
[sortColumnIndex, sortOrder, rows]
80+
() => (sortColumnIndex !== -1 ? sortArray(sortColumnIndex, sortOrder, rowsWithIds) : rowsWithIds),
81+
[sortColumnIndex, sortOrder, rowsWithIds],
6782
);
6883
const filteredResultset = useMemo(
6984
() => sortedResultset && sortedResultset.slice(minItemsPerPageIndex, maxItemsPerPageIndex + 1),
70-
[sortedResultset, minItemsPerPageIndex, maxItemsPerPageIndex]
85+
[sortedResultset, minItemsPerPageIndex, maxItemsPerPageIndex],
7186
);
7287

7388
const goToPage = (newPage: number) => {
@@ -81,16 +96,25 @@ const DxcResultsetTable = ({
8196
sortColumnIndex === -1 || sortColumnIndex !== columnIndex
8297
? "ascending"
8398
: sortOrder === "ascending"
84-
? "descending"
85-
: "ascending"
99+
? "descending"
100+
: "ascending",
86101
);
87102
};
88103

89104
useEffect(() => {
90105
if (!hidePaginator) {
91-
rows.length > 0 ? changePage(1) : changePage(0);
106+
if (rows.length === 0) {
107+
changePage(0);
108+
} else if (rows.length < prevRowCountRef.current) {
109+
const lastPage = Math.ceil(rows.length / itemsPerPage);
110+
const prevLastPage = Math.ceil(prevRowCountRef.current / itemsPerPage);
111+
if (lastPage < prevLastPage) {
112+
changePage(Math.min(lastPage, page));
113+
}
114+
}
115+
prevRowCountRef.current = rows.length;
92116
}
93-
}, [rows]);
117+
}, [rows.length]);
94118

95119
return (
96120
<ThemeProvider theme={colorsTheme.table}>
@@ -130,9 +154,9 @@ const DxcResultsetTable = ({
130154
</tr>
131155
</thead>
132156
<tbody>
133-
{filteredResultset.map((cells, rowIndex) => (
134-
<tr key={`resultSetTableCell_${page}_${rowIndex}`}>
135-
{cells.map((cellContent, cellIndex) => (
157+
{filteredResultset.map((row) => (
158+
<tr key={`resultSetTableCell_${row.id}`}>
159+
{row.cells.map((cellContent, cellIndex) => (
136160
<td key={`resultSetTableCellContent_${cellIndex}`}>{cellContent.displayValue}</td>
137161
))}
138162
</tr>
@@ -156,7 +180,8 @@ const DxcResultsetTable = ({
156180
);
157181
};
158182

159-
const calculateWidth = (margin: string | object) => `calc(100% - ${getMargin(margin, "left")} - ${getMargin(margin, "right")})`;
183+
const calculateWidth = (margin: string | object) =>
184+
`calc(100% - ${getMargin(margin, "left")} - ${getMargin(margin, "right")})`;
160185

161186
const DxcResultsetTableContainer = styled.div<{ margin: ResultsetTablePropsType["margin"] }>`
162187
width: ${(props) => calculateWidth(props.margin)};
@@ -178,8 +203,8 @@ const HeaderContainer = styled.span<{ isSortable: Column["isSortable"]; mode: Re
178203
props.theme.headerTextAlign === "center"
179204
? "center"
180205
: props.theme.headerTextAlign === "right"
181-
? "flex-end"
182-
: "flex-start"};
206+
? "flex-end"
207+
: "flex-start"};
183208
gap: ${CoreTokens.spacing_8};
184209
width: fit-content;
185210
border: 1px solid transparent;

0 commit comments

Comments
 (0)