@purpurds/table 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/dist/LICENSE.txt +213 -0
  2. package/dist/cell-types/badge-cell.d.ts +8 -0
  3. package/dist/cell-types/badge-cell.d.ts.map +1 -0
  4. package/dist/cell-types/body-text-cell.d.ts +8 -0
  5. package/dist/cell-types/body-text-cell.d.ts.map +1 -0
  6. package/dist/cell-types/button-cell.d.ts +8 -0
  7. package/dist/cell-types/button-cell.d.ts.map +1 -0
  8. package/dist/cell-types/button-group-cell.d.ts +16 -0
  9. package/dist/cell-types/button-group-cell.d.ts.map +1 -0
  10. package/dist/cell-types/cta-link-cell.d.ts +8 -0
  11. package/dist/cell-types/cta-link-cell.d.ts.map +1 -0
  12. package/dist/cell-types/date-cell.d.ts +8 -0
  13. package/dist/cell-types/date-cell.d.ts.map +1 -0
  14. package/dist/cell-types/empty-cell.d.ts +4 -0
  15. package/dist/cell-types/empty-cell.d.ts.map +1 -0
  16. package/dist/cell-types/error-message-cell.d.ts +8 -0
  17. package/dist/cell-types/error-message-cell.d.ts.map +1 -0
  18. package/dist/cell-types/icon-text-cell.d.ts +8 -0
  19. package/dist/cell-types/icon-text-cell.d.ts.map +1 -0
  20. package/dist/cell-types/lead-text-cell.d.ts +8 -0
  21. package/dist/cell-types/lead-text-cell.d.ts.map +1 -0
  22. package/dist/cell-types/link-cell.d.ts +8 -0
  23. package/dist/cell-types/link-cell.d.ts.map +1 -0
  24. package/dist/cell-types/number-cell.d.ts +8 -0
  25. package/dist/cell-types/number-cell.d.ts.map +1 -0
  26. package/dist/cell-types/row-selection-cell.d.ts +8 -0
  27. package/dist/cell-types/row-selection-cell.d.ts.map +1 -0
  28. package/dist/cell-types/row-toggle-cell.d.ts +8 -0
  29. package/dist/cell-types/row-toggle-cell.d.ts.map +1 -0
  30. package/dist/cell-types/toggle-cell.d.ts +8 -0
  31. package/dist/cell-types/toggle-cell.d.ts.map +1 -0
  32. package/dist/cell-types/warning-message-cell.d.ts +8 -0
  33. package/dist/cell-types/warning-message-cell.d.ts.map +1 -0
  34. package/dist/metadata.js +17 -0
  35. package/dist/story-utils/column-def.d.ts +5 -0
  36. package/dist/story-utils/column-def.d.ts.map +1 -0
  37. package/dist/story-utils/table-data.d.ts +35 -0
  38. package/dist/story-utils/table-data.d.ts.map +1 -0
  39. package/dist/story-utils/use-fetch-table-data-hook.d.ts +11 -0
  40. package/dist/story-utils/use-fetch-table-data-hook.d.ts.map +1 -0
  41. package/dist/styles.css +1 -0
  42. package/dist/table-action-bar.d.ts +26 -0
  43. package/dist/table-action-bar.d.ts.map +1 -0
  44. package/dist/table-body.d.ts +10 -0
  45. package/dist/table-body.d.ts.map +1 -0
  46. package/dist/table-column-header-cell.d.ts +28 -0
  47. package/dist/table-column-header-cell.d.ts.map +1 -0
  48. package/dist/table-export-drawer.d.ts +17 -0
  49. package/dist/table-export-drawer.d.ts.map +1 -0
  50. package/dist/table-header.d.ts +11 -0
  51. package/dist/table-header.d.ts.map +1 -0
  52. package/dist/table-row-cell-skeleton.d.ts +14 -0
  53. package/dist/table-row-cell-skeleton.d.ts.map +1 -0
  54. package/dist/table-row-cell.d.ts +25 -0
  55. package/dist/table-row-cell.d.ts.map +1 -0
  56. package/dist/table-row.d.ts +11 -0
  57. package/dist/table-row.d.ts.map +1 -0
  58. package/dist/table-settings-drawer.d.ts +41 -0
  59. package/dist/table-settings-drawer.d.ts.map +1 -0
  60. package/dist/table-toolbar.d.ts +37 -0
  61. package/dist/table-toolbar.d.ts.map +1 -0
  62. package/dist/table.cjs.js +259 -0
  63. package/dist/table.cjs.js.map +1 -0
  64. package/dist/table.d.ts +20 -0
  65. package/dist/table.d.ts.map +1 -0
  66. package/dist/table.es.js +13585 -0
  67. package/dist/table.es.js.map +1 -0
  68. package/dist/test-utils/column-def.d.ts +6 -0
  69. package/dist/test-utils/column-def.d.ts.map +1 -0
  70. package/dist/test-utils/helpers.d.ts +138 -0
  71. package/dist/test-utils/helpers.d.ts.map +1 -0
  72. package/dist/test-utils/table-data.d.ts +33 -0
  73. package/dist/test-utils/table-data.d.ts.map +1 -0
  74. package/dist/types.d.ts +420 -0
  75. package/dist/types.d.ts.map +1 -0
  76. package/dist/use-screen-size.hook.d.ts +7 -0
  77. package/dist/use-screen-size.hook.d.ts.map +1 -0
  78. package/dist/use-truncated-hook.d.ts +10 -0
  79. package/dist/use-truncated-hook.d.ts.map +1 -0
  80. package/dist/utils/custom-functions.d.ts +9 -0
  81. package/dist/utils/custom-functions.d.ts.map +1 -0
  82. package/dist/utils/unit-conversions.d.ts +19 -0
  83. package/dist/utils/unit-conversions.d.ts.map +1 -0
  84. package/dist/utils/unit-conversions.spec.d.ts +2 -0
  85. package/dist/utils/unit-conversions.spec.d.ts.map +1 -0
  86. package/eslint.config.mjs +2 -0
  87. package/package.json +82 -0
  88. package/src/cell-types/badge-cell.tsx +25 -0
  89. package/src/cell-types/body-text-cell.tsx +54 -0
  90. package/src/cell-types/button-cell.tsx +26 -0
  91. package/src/cell-types/button-group-cell.tsx +54 -0
  92. package/src/cell-types/cta-link-cell.tsx +25 -0
  93. package/src/cell-types/date-cell.tsx +33 -0
  94. package/src/cell-types/empty-cell.tsx +6 -0
  95. package/src/cell-types/error-message-cell.tsx +30 -0
  96. package/src/cell-types/icon-text-cell.tsx +30 -0
  97. package/src/cell-types/lead-text-cell.tsx +19 -0
  98. package/src/cell-types/link-cell.tsx +58 -0
  99. package/src/cell-types/number-cell.tsx +27 -0
  100. package/src/cell-types/row-selection-cell.tsx +22 -0
  101. package/src/cell-types/row-toggle-cell.tsx +23 -0
  102. package/src/cell-types/toggle-cell.tsx +19 -0
  103. package/src/cell-types/warning-message-cell.tsx +30 -0
  104. package/src/global.d.ts +4 -0
  105. package/src/story-utils/column-def.ts +148 -0
  106. package/src/story-utils/table-data.tsx +262 -0
  107. package/src/story-utils/use-fetch-table-data-hook.tsx +30 -0
  108. package/src/table-action-bar.module.scss +106 -0
  109. package/src/table-action-bar.test.tsx +111 -0
  110. package/src/table-action-bar.tsx +104 -0
  111. package/src/table-body.tsx +25 -0
  112. package/src/table-column-header-cell.tsx +305 -0
  113. package/src/table-export-drawer.module.scss +9 -0
  114. package/src/table-export-drawer.test.tsx +75 -0
  115. package/src/table-export-drawer.tsx +59 -0
  116. package/src/table-header.tsx +35 -0
  117. package/src/table-kitchen-sink.test.tsx +1196 -0
  118. package/src/table-row-cell-skeleton.tsx +61 -0
  119. package/src/table-row-cell.test.tsx +360 -0
  120. package/src/table-row-cell.tsx +188 -0
  121. package/src/table-row.tsx +30 -0
  122. package/src/table-settings-drawer.module.scss +25 -0
  123. package/src/table-settings-drawer.test.tsx +350 -0
  124. package/src/table-settings-drawer.tsx +254 -0
  125. package/src/table-toolbar.module.scss +17 -0
  126. package/src/table-toolbar.test.tsx +95 -0
  127. package/src/table-toolbar.tsx +136 -0
  128. package/src/table.module.scss +367 -0
  129. package/src/table.stories.tsx +1246 -0
  130. package/src/table.story.css +11 -0
  131. package/src/table.test.tsx +318 -0
  132. package/src/table.tsx +501 -0
  133. package/src/test-utils/column-def.ts +152 -0
  134. package/src/test-utils/helpers.ts +234 -0
  135. package/src/test-utils/table-data.tsx +318 -0
  136. package/src/types.ts +496 -0
  137. package/src/use-screen-size.hook.ts +23 -0
  138. package/src/use-truncated-hook.tsx +74 -0
  139. package/src/utils/custom-functions.ts +52 -0
  140. package/src/utils/unit-conversions.spec.ts +92 -0
  141. package/src/utils/unit-conversions.ts +30 -0
  142. package/vitest.setup.ts +60 -0
@@ -0,0 +1,1196 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { Pagination } from "@purpurds/pagination";
3
+ import { ColumnFiltersState, RowSelectionState } from "@tanstack/react-table";
4
+ import { cleanup, fireEvent, render, screen, within } from "@testing-library/react";
5
+ import userEvent from "@testing-library/user-event";
6
+ import { axe } from "vitest-axe";
7
+
8
+ import { PaginationState,Table } from "./table.tsx";
9
+ import { createColumnDefKitchenSink } from "./test-utils/column-def.ts";
10
+ import {
11
+ copy,
12
+ filterOnSelect,
13
+ filterOnString,
14
+ getCellInRowContent,
15
+ getCheckBoxForTableBodyRow,
16
+ getCheckBoxForTableHeaderRow,
17
+ getComboBoxFilterForHeader,
18
+ getInputFilterForHeader,
19
+ getRadioToggleForTableBodyRow,
20
+ getTableBody,
21
+ getTableColumnHeaderByText,
22
+ getTableHead,
23
+ Selectors,
24
+ } from "./test-utils/helpers.ts";
25
+ import { tableDataLarge } from "./test-utils/table-data.tsx";
26
+
27
+ const toggleExpandMock = vi.fn();
28
+ const onExportDataMock = vi.fn();
29
+ const onPrimaryButtonClickMock = vi.fn();
30
+ const clickCellButtonMock = vi.fn();
31
+ const clickButtonGroupCellButtonMock = vi.fn();
32
+
33
+ const TestComponent = ({
34
+ enableMultiRowSelection = true,
35
+ }: {
36
+ enableMultiRowSelection: boolean;
37
+ }) => {
38
+ const exportFormats = ["csv", "xlsx"];
39
+
40
+ const [pagination, setPagination] = useState<PaginationState>({
41
+ pageIndex: 0,
42
+ pageSize: 5,
43
+ });
44
+ const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
45
+ const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
46
+ const [columnVisibility, setColumnVisibility] = useState({});
47
+
48
+ const [displayData, setDisplayData] = useState(tableDataLarge);
49
+ const [rowCount, setRowCount] = useState(tableDataLarge.length);
50
+
51
+ const [showOnlySelectedRows, setShowOnlySelectedRows] = useState(false);
52
+
53
+ useEffect(() => {
54
+ if (showOnlySelectedRows) {
55
+ const selectedRows = Object.keys(rowSelection).filter(
56
+ (key) => (rowSelection as Record<string, unknown>)[key]
57
+ );
58
+
59
+ const filteredData = tableDataLarge.filter((row) => selectedRows.includes(`${row.id}`));
60
+
61
+ setDisplayData(filteredData);
62
+ } else {
63
+ setDisplayData(tableDataLarge);
64
+ }
65
+ }, [showOnlySelectedRows, rowSelection]);
66
+
67
+ const handleOnPageChange = (page: { currentPage: number; pageSize: number }) => {
68
+ setPagination({ pageIndex: page.currentPage - 1, pageSize: page.pageSize });
69
+ };
70
+
71
+ const paginationComponent = (
72
+ <Pagination
73
+ onPageChange={handleOnPageChange}
74
+ availablePageSizes={[5, 10, 50]}
75
+ currentPage={pagination.pageIndex + 1}
76
+ pageSize={pagination.pageSize}
77
+ totalItems={rowCount}
78
+ nextButtonAriaLabel="Go to next page"
79
+ nextButtonText="Next"
80
+ outOfLabel="of"
81
+ pageSelectorListBoxLabel="Select a page"
82
+ pageSelectorNoOptionsText="Page does not exist"
83
+ pageSizeLabel="Items per page"
84
+ previousButtonAriaLabel="Go to previous page"
85
+ previousButtonText="Previous"
86
+ stepNumberPrefix="Go to page"
87
+ />
88
+ );
89
+
90
+ return (
91
+ <div style={{ width: "80rem" }}>
92
+ {/* @ts-expect-error Some props are required depending on if enableMultiRowSelection is true. */}
93
+ <Table
94
+ actionbarCopy={copy.actionBar}
95
+ columns={createColumnDefKitchenSink(clickCellButtonMock, clickButtonGroupCellButtonMock)}
96
+ data={displayData}
97
+ enableActionBar={enableMultiRowSelection} // Enables action bar only when multi row selection is enabled
98
+ enableFilters={true}
99
+ enableRowSelection={true}
100
+ enableMultiRowSelection={enableMultiRowSelection}
101
+ enableSorting={true}
102
+ enableToolbar={true}
103
+ exportDrawerCopy={copy.exportDrawer}
104
+ exportFormats={exportFormats}
105
+ getRowId={(row) => `${row.id}`}
106
+ onColumnFiltersChange={setColumnFilters}
107
+ onColumnVisibilityChange={setColumnVisibility}
108
+ onExportData={onExportDataMock}
109
+ onRowsCountChange={setRowCount}
110
+ onRowSelectionChange={setRowSelection}
111
+ onToggleExpand={toggleExpandMock}
112
+ onPrimaryButtonClick={onPrimaryButtonClickMock}
113
+ paginationComponent={paginationComponent}
114
+ showOnlySelectedRows={showOnlySelectedRows}
115
+ setShowOnlySelectedRows={setShowOnlySelectedRows}
116
+ settingsDrawerCopy={copy.settingsDrawer}
117
+ sortingAriaLabels={copy.sortingAriaLabels}
118
+ state={{ pagination, columnFilters, rowSelection, columnVisibility }}
119
+ toolbarCopy={copy.toolbar}
120
+ actionBarTotalRowCount={tableDataLarge.length}
121
+ rowSelectionAriaLabels={copy.rowSelectionAriaLabels}
122
+ variant="primary"
123
+ />
124
+ </div>
125
+ );
126
+ };
127
+
128
+ describe("Data Table - Kitchen sink", () => {
129
+ describe("With row selection", () => {
130
+ let table: HTMLTableElement;
131
+
132
+ beforeEach(async () => {
133
+ render(<TestComponent enableMultiRowSelection={true} />);
134
+
135
+ table = screen.getByRole("table");
136
+ });
137
+
138
+ afterEach(() => {
139
+ cleanup();
140
+ });
141
+
142
+ it("should have 10 headers", () => {
143
+ const expectedHeaders = [
144
+ "checkbox",
145
+ "ID",
146
+ "Name",
147
+ "Link",
148
+ "Age",
149
+ "Badge",
150
+ "Position",
151
+ "Action",
152
+ "Date",
153
+ "Buttons",
154
+ ];
155
+
156
+ const headers = within(getTableHead(table)).getAllByRole("columnheader");
157
+ expect(headers).toHaveLength(expectedHeaders.length);
158
+
159
+ expectedHeaders.forEach((expectedHeader, index) => {
160
+ const withinHeader = within(headers[index]);
161
+ if (expectedHeader === "checkbox") {
162
+ expect(withinHeader.getByRole("checkbox")).toBeDefined();
163
+ } else {
164
+ const paragraph = withinHeader.getByRole("paragraph");
165
+ expect(paragraph.textContent?.trim()).toBe(expectedHeader);
166
+ }
167
+ });
168
+ });
169
+
170
+ it("should have 5 rows of data", () => {
171
+ const tableRows = within(getTableBody(table)).getAllByRole("row");
172
+ expect(tableRows).toHaveLength(5);
173
+ });
174
+
175
+ it("should have data in first row", () => {
176
+ const tableRow1 = within(getTableBody(table)).getAllByRole("row")[0];
177
+
178
+ const tableRowCells = within(tableRow1).getAllByRole("cell");
179
+
180
+ const expectedCellsTextContent = [
181
+ "",
182
+ "12345",
183
+ "Name 1",
184
+ "Link 2",
185
+ "40",
186
+ "Information",
187
+ "1",
188
+ "ClickMe",
189
+ "2025-03-26 11:14",
190
+ [
191
+ { testId: "download-icon", ariaLabeL: "ClickMe" },
192
+ { testId: "remove-icon", ariaLabeL: "ClickMe" },
193
+ { testId: "more-vertical-icon", ariaLabeL: "ClickMe" },
194
+ ],
195
+ ];
196
+
197
+ tableRowCells.forEach((tableRowCell, index) => {
198
+ if (typeof expectedCellsTextContent[index] === "string") {
199
+ expect(tableRowCell).toHaveTextContent(expectedCellsTextContent[index]);
200
+ } else {
201
+ expectedCellsTextContent[index].forEach((expectedElement, buttonIndex) => {
202
+ const button = within(tableRowCell).getAllByRole("button")[buttonIndex];
203
+ const svg = within(button).getByTestId(expectedElement.testId);
204
+ expect(svg).toBeInTheDocument();
205
+ expect(button).toHaveAttribute("aria-label", expectedElement.ariaLabeL);
206
+ });
207
+ }
208
+ });
209
+ });
210
+
211
+ it.each(["Name", "Age", "Link", "Position"])(
212
+ "should have sorting icons on '%s' column header",
213
+ (column) => {
214
+ const header = getTableColumnHeaderByText(table, column);
215
+ const button = within(header).getByRole("button");
216
+ expect(button).toHaveAttribute("aria-label", "Sort column");
217
+ }
218
+ );
219
+
220
+ it.each([
221
+ ["Search by id", "ID"],
222
+ ["Search by name", "Name"],
223
+ ["Search by age", "Age"],
224
+ ])("should have placeholder '%s' on '%s' input filter", (expectedPlaceholder, column) => {
225
+ const input = getInputFilterForHeader(table, column);
226
+ expect(input).toHaveAttribute("placeholder", expectedPlaceholder);
227
+ });
228
+
229
+ it("should have right alignment on position column cells", () => {
230
+ const positionCell = getCellInRowContent(table, 1, "Position");
231
+ const paragraph = within(positionCell).getByRole("paragraph");
232
+
233
+ const hasRightAlignClass = paragraph.className
234
+ .split(" ")
235
+ .some((c) => c.includes("__right-align"));
236
+
237
+ expect(hasRightAlignClass).toBe(true);
238
+ });
239
+
240
+ it("should have disabled clear filters button when no filters are applied", () => {
241
+ const toolbar = screen.getByTestId(Selectors.TOOLBAR.ROOT);
242
+ const clearFiltersButton = within(toolbar).getByTestId(
243
+ Selectors.TOOLBAR.CLEAR_FILTERS_BUTTON
244
+ );
245
+
246
+ expect(clearFiltersButton).toHaveAttribute("aria-disabled", "true");
247
+ });
248
+
249
+ describe("Tool bar", () => {
250
+ let toolbar: HTMLElement;
251
+
252
+ beforeEach(() => {
253
+ toolbar = screen.getByTestId(Selectors.TOOLBAR.ROOT);
254
+ });
255
+
256
+ describe("Clearing all filters", () => {
257
+ beforeEach(async () => {
258
+ await filterOnString(table, "Name", "Name");
259
+ await filterOnSelect(table, "Badge", "Special");
260
+ });
261
+
262
+ it("should enable clear filters button when filters are applied", async () => {
263
+ const clearFiltersButton = within(toolbar).getByTestId(
264
+ Selectors.TOOLBAR.CLEAR_FILTERS_BUTTON
265
+ );
266
+
267
+ expect(clearFiltersButton).not.toHaveAttribute("aria-disabled", "true");
268
+ });
269
+
270
+ it("should have 2 rows of data", () => {
271
+ const tableRows = within(getTableBody(table)).getAllByRole("row");
272
+ expect(tableRows).toHaveLength(2);
273
+ });
274
+
275
+ it("Name input filter should contain 'Name'", () => {
276
+ expect(getInputFilterForHeader(table, "Name")).toHaveValue("Name");
277
+ });
278
+
279
+ it("Badge select should be on 'special'", () => {
280
+ expect(getComboBoxFilterForHeader(table, "Badge")).toHaveValue("special");
281
+ });
282
+
283
+ describe("Clearing all filters", () => {
284
+ beforeEach(async () => {
285
+ const clearFiltersButton = within(toolbar).getByTestId(
286
+ Selectors.TOOLBAR.CLEAR_FILTERS_BUTTON
287
+ );
288
+
289
+ await userEvent.click(clearFiltersButton);
290
+ });
291
+
292
+ it("should have 5 rows of data when clicking on 'Clear filters'", async () => {
293
+ const tableRows = within(getTableBody(table)).getAllByRole("row");
294
+ expect(tableRows).toHaveLength(5);
295
+ });
296
+
297
+ it("Name input filter should be empty", () => {
298
+ expect(getInputFilterForHeader(table, "Name")).toHaveValue("");
299
+ });
300
+
301
+ it("Badge select should be on default", () => {
302
+ expect(getComboBoxFilterForHeader(table, "Badge")).toHaveValue("");
303
+ });
304
+ });
305
+ });
306
+
307
+ describe("Export table", () => {
308
+ describe("Export button when multiple export methods", () => {
309
+ beforeEach(async () => {
310
+ const exportButton = within(toolbar).getByTestId(Selectors.TOOLBAR.EXPORT_BUTTON);
311
+
312
+ await userEvent.click(exportButton);
313
+ });
314
+
315
+ it("should open export drawer", async () => {
316
+ expect(screen.getByTestId(Selectors.EXPORT_DRAWER.CONTENT)).toBeVisible();
317
+ });
318
+
319
+ it("should call 'onExportData' when clicking on export button", async () => {
320
+ const exportLinks = screen.getAllByTestId(Selectors.EXPORT_DRAWER.EXPORT_BUTTON);
321
+ await userEvent.click(exportLinks[0]);
322
+ expect(onExportDataMock).toHaveBeenCalledWith("csv");
323
+ });
324
+ });
325
+ });
326
+
327
+ describe("Expand table", () => {
328
+ let expandButton: HTMLButtonElement;
329
+
330
+ beforeEach(() => {
331
+ expandButton = screen.getByTestId(Selectors.TOOLBAR.EXPAND_BUTTON);
332
+ });
333
+
334
+ it("should have expand button", () => {
335
+ expect(expandButton).toHaveTextContent("Expand");
336
+ });
337
+
338
+ it("should send an event when clicking expand button", async () => {
339
+ await userEvent.click(expandButton);
340
+ expect(toggleExpandMock).toHaveBeenCalled();
341
+ });
342
+ });
343
+
344
+ describe("Settings drawer", () => {
345
+ let closeButton: HTMLButtonElement;
346
+ let withinDrawerContent: ReturnType<typeof within>;
347
+ let withinGeneralSettings: ReturnType<typeof within>;
348
+ let withinVisibleColumns: ReturnType<typeof within>;
349
+
350
+ beforeEach(async () => {
351
+ const settingsButton = within(toolbar).getByTestId(Selectors.TOOLBAR.SETTINGS_BUTTON);
352
+
353
+ await userEvent.click(settingsButton);
354
+
355
+ withinDrawerContent = within(screen.getByTestId(Selectors.SETTINGS_DRAWER.CONTENT));
356
+ closeButton = within(screen.getByTestId(Selectors.SETTINGS_DRAWER.HEADER_ROW)).getByRole(
357
+ "button"
358
+ );
359
+ });
360
+
361
+ it("should open the drawer", () => {
362
+ expect(screen.getByTestId(Selectors.SETTINGS_DRAWER.CONTENT)).toBeVisible();
363
+ });
364
+
365
+ it("should be possible to close the drawer", async () => {
366
+ await userEvent.click(closeButton);
367
+ expect(screen.queryByTestId(Selectors.SETTINGS_DRAWER.CONTENT)).toBeNull();
368
+ });
369
+
370
+ describe("General settings", () => {
371
+ let toggleElements: HTMLElement[];
372
+
373
+ beforeEach(() => {
374
+ withinGeneralSettings = within(
375
+ withinDrawerContent.getByTestId(Selectors.SETTINGS_DRAWER.GENERAL_SETTINGS)
376
+ );
377
+
378
+ toggleElements = withinGeneralSettings.getAllByTestId(Selectors.SETTINGS_DRAWER.TOGGLE);
379
+ });
380
+
381
+ describe("Toggle show filters", () => {
382
+ beforeEach(async () => {
383
+ await userEvent.click(toggleElements[0]);
384
+ await userEvent.click(closeButton);
385
+ });
386
+
387
+ it.each(["ID", "Name", "Link", "Age", "Badge"])(
388
+ "should not have a filter on '%s'",
389
+ (column) => {
390
+ const header = getTableColumnHeaderByText(table, column);
391
+ const input = within(header).queryByRole("textbox");
392
+ expect(input).not.toBeInTheDocument();
393
+ const listbox = within(header).queryByRole("checkbox");
394
+ expect(listbox).not.toBeInTheDocument();
395
+ }
396
+ );
397
+
398
+ describe("Clear filters button", () => {
399
+ it("should not be visible when filters are hidden", async () => {
400
+ const toolbar = screen.getByTestId(Selectors.TOOLBAR.ROOT);
401
+ const clearFiltersButton = within(toolbar).queryByTestId(
402
+ Selectors.TOOLBAR.CLEAR_FILTERS_BUTTON
403
+ );
404
+
405
+ expect(clearFiltersButton).not.toBeInTheDocument();
406
+ });
407
+ });
408
+ });
409
+ });
410
+
411
+ describe("Visible columns", () => {
412
+ let toggleElements: HTMLElement[];
413
+ let toggleLabels: HTMLLabelElement[];
414
+
415
+ beforeEach(() => {
416
+ withinVisibleColumns = within(
417
+ withinDrawerContent.getByTestId(Selectors.SETTINGS_DRAWER.VISIBLE_COLUMNS)
418
+ );
419
+
420
+ toggleElements = withinVisibleColumns.getAllByTestId(Selectors.SETTINGS_DRAWER.TOGGLE);
421
+ toggleLabels = withinVisibleColumns.getAllByTestId(
422
+ Selectors.SETTINGS_DRAWER.TOGGLE_LABEL
423
+ );
424
+ });
425
+
426
+ it.each([
427
+ { label: "ID", index: 0, dataState: "checked" },
428
+ { label: "Name", index: 1, dataState: "checked" },
429
+ { label: "Link", index: 2, dataState: "checked" },
430
+ { label: "Age", index: 3, dataState: "checked" },
431
+ { label: "Badge", index: 4, dataState: "checked" },
432
+ { label: "Position", index: 5, dataState: "checked" },
433
+ { label: "Action", index: 6, dataState: "checked" },
434
+ { label: "Date", index: 7, dataState: "checked" },
435
+ { label: "Buttons", index: 8, dataState: "checked" },
436
+ ])("should have '%s' toggle", ({ label, index, dataState }) => {
437
+ expect(toggleElements[index]).toHaveRole("switch");
438
+ expect(toggleElements[index]).toHaveAttribute("data-state", dataState);
439
+ expect(toggleElements[index]).not.toBeDisabled();
440
+ expect(toggleLabels[index]).toHaveTextContent(label);
441
+ });
442
+
443
+ it("should disable last visible column toggle", () => {
444
+ const toggleIndex = 2;
445
+ toggleElements.forEach((toggleElement, index) => {
446
+ if (index === toggleIndex) {
447
+ return;
448
+ }
449
+ fireEvent.click(toggleElement);
450
+ });
451
+ expect(toggleElements[toggleIndex]).toHaveAttribute("data-state", "checked");
452
+ expect(toggleElements[toggleIndex]).toBeDisabled();
453
+ expect(toggleLabels[toggleIndex]).toHaveTextContent("Link");
454
+ });
455
+
456
+ describe("Toggle Age column visibility off", () => {
457
+ beforeEach(async () => {
458
+ const ageToggle = toggleElements[3];
459
+ await userEvent.click(ageToggle);
460
+ await userEvent.click(closeButton);
461
+ });
462
+
463
+ it("should have 9 headers", async () => {
464
+ const expectedHeaders = [
465
+ "checkbox",
466
+ "ID",
467
+ "Name",
468
+ "Link",
469
+ "Badge",
470
+ "Position",
471
+ "Action",
472
+ "Date",
473
+ "Buttons",
474
+ ];
475
+
476
+ const headers = within(getTableHead(table)).getAllByRole("columnheader");
477
+ expect(headers).toHaveLength(expectedHeaders.length);
478
+
479
+ expectedHeaders.forEach((expectedHeader, index) => {
480
+ const withinHeader = within(headers[index]);
481
+ if (expectedHeader === "checkbox") {
482
+ expect(withinHeader.getByRole("checkbox")).toBeDefined();
483
+ } else {
484
+ const paragraph = withinHeader.getByRole("paragraph");
485
+ expect(paragraph.textContent?.trim()).toBe(expectedHeader);
486
+ }
487
+ });
488
+ });
489
+
490
+ it("should have data in first row", () => {
491
+ const tableRow1 = within(getTableBody(table)).getAllByRole("row")[0];
492
+
493
+ const tableRowCells = within(tableRow1).getAllByRole("cell");
494
+
495
+ const expectedCellsTextContent = [
496
+ "",
497
+ "12345",
498
+ "Name 1",
499
+ "Link 2",
500
+ "Information",
501
+ "1",
502
+ "ClickMe",
503
+ "2025-03-26 11:14",
504
+ [
505
+ { testId: "download-icon", ariaLabeL: "ClickMe" },
506
+ { testId: "remove-icon", ariaLabeL: "ClickMe" },
507
+ { testId: "more-vertical-icon", ariaLabeL: "ClickMe" },
508
+ ],
509
+ ];
510
+
511
+ tableRowCells.forEach((tableRowCell, index) => {
512
+ if (typeof expectedCellsTextContent[index] === "string") {
513
+ expect(tableRowCell).toHaveTextContent(expectedCellsTextContent[index]);
514
+ } else {
515
+ expectedCellsTextContent[index].forEach((expectedElement, buttonIndex) => {
516
+ const button = within(tableRowCell).getAllByRole("button")[buttonIndex];
517
+ const svg = within(button).getByTestId(expectedElement.testId);
518
+ expect(svg).toBeInTheDocument();
519
+ expect(button).toHaveAttribute("aria-label", expectedElement.ariaLabeL);
520
+ });
521
+ }
522
+ });
523
+ });
524
+ });
525
+ });
526
+
527
+ describe("Reset settings", () => {
528
+ let visibleColumnsToggleElements: HTMLElement[];
529
+ let generalSettingsToggleElements: HTMLElement[];
530
+ let resetSettingsButton: HTMLButtonElement;
531
+
532
+ beforeEach(async () => {
533
+ withinVisibleColumns = within(
534
+ withinDrawerContent.getByTestId(Selectors.SETTINGS_DRAWER.VISIBLE_COLUMNS)
535
+ );
536
+
537
+ visibleColumnsToggleElements = withinVisibleColumns.getAllByTestId(
538
+ Selectors.SETTINGS_DRAWER.TOGGLE
539
+ );
540
+
541
+ const ageToggle = visibleColumnsToggleElements[4];
542
+ await userEvent.click(ageToggle);
543
+
544
+ withinGeneralSettings = within(
545
+ withinDrawerContent.getByTestId(Selectors.SETTINGS_DRAWER.GENERAL_SETTINGS)
546
+ );
547
+
548
+ generalSettingsToggleElements = withinGeneralSettings.getAllByTestId(
549
+ Selectors.SETTINGS_DRAWER.TOGGLE
550
+ );
551
+ await userEvent.click(generalSettingsToggleElements[0]);
552
+ await userEvent.click(generalSettingsToggleElements[1]);
553
+ await userEvent.click(generalSettingsToggleElements[2]);
554
+ });
555
+
556
+ it("should have toggled off general settings", () => {
557
+ generalSettingsToggleElements.forEach((toggleElement) => {
558
+ expect(toggleElement).toHaveAttribute("data-state", "unchecked");
559
+ });
560
+ });
561
+
562
+ describe("When clicking on reset button", () => {
563
+ beforeEach(async () => {
564
+ resetSettingsButton = withinDrawerContent.getByTestId(
565
+ Selectors.SETTINGS_DRAWER.FOOTER_RESET_SETTINGS_BUTTON
566
+ );
567
+
568
+ await userEvent.click(resetSettingsButton);
569
+ });
570
+
571
+ it("should have reset general settings", () => {
572
+ generalSettingsToggleElements.forEach((toggleElement) => {
573
+ expect(toggleElement).toHaveAttribute("data-state", "checked");
574
+ });
575
+ });
576
+
577
+ it("should have reset visible columns", () => {
578
+ visibleColumnsToggleElements.forEach((toggleElement) => {
579
+ expect(toggleElement).toHaveAttribute("data-state", "checked");
580
+ });
581
+ });
582
+ });
583
+ });
584
+ });
585
+ });
586
+
587
+ describe("Paginating table", () => {
588
+ describe("When selecting page 3", () => {
589
+ beforeEach(async () => {
590
+ const page3Button = within(screen.getByTestId(Selectors.PAGINATION.ROOT)).getByText("3");
591
+
592
+ await userEvent.click(page3Button);
593
+ });
594
+
595
+ it("should have 1 rows of data", () => {
596
+ const tableRows = within(getTableBody(table)).getAllByRole("row");
597
+ expect(tableRows).toHaveLength(1);
598
+ });
599
+
600
+ it("should have data in first row", () => {
601
+ const tableRow1 = within(getTableBody(table)).getAllByRole("row")[0];
602
+
603
+ const tableRowCells = within(tableRow1).getAllByRole("cell");
604
+
605
+ const expectedCellsTextContent = [
606
+ "checkbox",
607
+ "23414",
608
+ "Name 52",
609
+ "Link 12",
610
+ "30",
611
+ "Special",
612
+ "11",
613
+ "ClickMe",
614
+ "2025-03-26 11:14",
615
+ "-", // Button group is undefined for this table row
616
+ ];
617
+
618
+ tableRowCells.forEach((tableRowCell, index) => {
619
+ if (expectedCellsTextContent[index] === "checkbox") {
620
+ expect(within(tableRowCell).getByRole("checkbox")).toBeInTheDocument();
621
+ } else {
622
+ expect(tableRowCell).toHaveTextContent(expectedCellsTextContent[index]);
623
+ }
624
+ });
625
+ });
626
+ });
627
+
628
+ describe("When changing page size", () => {
629
+ beforeEach(async () => {
630
+ const pageSizeSelector = within(
631
+ screen.getByTestId(Selectors.PAGINATION.ROOT)
632
+ ).getByTestId(Selectors.PAGINATION.PAGE_SIZE_SELECT);
633
+
634
+ await userEvent.selectOptions(pageSizeSelector, "10");
635
+ });
636
+
637
+ it("should have 10 rows of data", () => {
638
+ const tableRows = within(getTableBody(table)).getAllByRole("row");
639
+ expect(tableRows).toHaveLength(10);
640
+ });
641
+
642
+ it("should have data in first row", () => {
643
+ const tableRow1 = within(getTableBody(table)).getAllByRole("row")[0];
644
+
645
+ const tableRowCells = within(tableRow1).getAllByRole("cell");
646
+
647
+ const expectedCellsTextContent = [
648
+ "checkbox",
649
+ "12345",
650
+ "Name 1",
651
+ "Link 2",
652
+ "40",
653
+ "Information",
654
+ "1",
655
+ "ClickMe",
656
+ "2025-03-26 11:14",
657
+ [
658
+ { testId: "download-icon", ariaLabeL: "ClickMe" },
659
+ { testId: "remove-icon", ariaLabeL: "ClickMe" },
660
+ { testId: "more-vertical-icon", ariaLabeL: "ClickMe" },
661
+ ],
662
+ ];
663
+
664
+ tableRowCells.forEach((tableRowCell, index) => {
665
+ if (expectedCellsTextContent[index] === "checkbox") {
666
+ expect(within(tableRowCell).getByRole("checkbox")).toBeInTheDocument();
667
+ } else if (typeof expectedCellsTextContent[index] === "string") {
668
+ expect(tableRowCell).toHaveTextContent(expectedCellsTextContent[index]);
669
+ } else {
670
+ expectedCellsTextContent[index].forEach((expectedElement, buttonIndex) => {
671
+ const button = within(tableRowCell).getAllByRole("button")[buttonIndex];
672
+ const svg = within(button).getByTestId(expectedElement.testId);
673
+ expect(svg).toBeInTheDocument();
674
+ expect(button).toHaveAttribute("aria-label", expectedElement.ariaLabeL);
675
+ });
676
+ }
677
+ });
678
+ });
679
+ });
680
+ });
681
+
682
+ describe("Select table rows", () => {
683
+ it("should not display batch action bar", () => {
684
+ const checkbox = getCheckBoxForTableHeaderRow(table);
685
+ expect(checkbox).toHaveAttribute("aria-checked", "false");
686
+ const actionBar = screen.queryByTestId(Selectors.ACTION_BAR.ROOT);
687
+ expect(actionBar).not.toBeVisible();
688
+ });
689
+
690
+ it("should display batch action bar when user selects a row", async () => {
691
+ const checkbox = getCheckBoxForTableBodyRow(table, 0);
692
+ expect(checkbox).not.toBeChecked();
693
+ await userEvent.click(checkbox);
694
+ expect(checkbox).toBeChecked();
695
+
696
+ const actionBar = screen.getByTestId(Selectors.ACTION_BAR.ROOT);
697
+ expect(actionBar).toBeVisible();
698
+ });
699
+
700
+ describe("When user selects rows 1, 3 and 4", () => {
701
+ let actionBar: HTMLElement;
702
+
703
+ beforeEach(async () => {
704
+ const checkbox0 = getCheckBoxForTableBodyRow(table, 0);
705
+ await userEvent.click(checkbox0);
706
+ const checkbox2 = getCheckBoxForTableBodyRow(table, 2);
707
+ await userEvent.click(checkbox2);
708
+ const checkbox4 = getCheckBoxForTableBodyRow(table, 4);
709
+ await userEvent.click(checkbox4);
710
+
711
+ actionBar = screen.getByTestId(Selectors.ACTION_BAR.ROOT);
712
+ });
713
+
714
+ it("should display batch action bar with '3 of 11 selected", () => {
715
+ const selectedText = within(actionBar).getByTestId(Selectors.ACTION_BAR.SELECTED_TEXT);
716
+ expect(selectedText).toHaveTextContent("3 of 11 selected");
717
+ });
718
+
719
+ it("should be possible to clear the selection", async () => {
720
+ const cancelButton = within(actionBar).getByTestId(Selectors.ACTION_BAR.CANCEL_BUTTON);
721
+ await userEvent.click(cancelButton);
722
+ expect(actionBar).toHaveStyle("visibility: hidden");
723
+ });
724
+
725
+ it("should display 'indeterminate' state on header checkbox", () => {
726
+ const checkbox = getCheckBoxForTableHeaderRow(table);
727
+ expect(checkbox).toHaveAttribute("aria-checked", "mixed");
728
+ });
729
+
730
+ it("should send 'onPrimaryButtonClick' event when clicking on primary button", async () => {
731
+ const primaryButton = within(actionBar).getByTestId(Selectors.ACTION_BAR.PRIMARY_BUTTON);
732
+ await userEvent.click(primaryButton);
733
+ expect(onPrimaryButtonClickMock).toHaveBeenCalled();
734
+ });
735
+
736
+ describe("When user toggles show only selected rows", () => {
737
+ beforeEach(async () => {
738
+ const toggleSelected = within(actionBar).getByTestId(
739
+ Selectors.ACTION_BAR.TOGGLE_SELECTED
740
+ );
741
+ await userEvent.click(toggleSelected);
742
+ });
743
+
744
+ it("should only display selected rows", () => {
745
+ const tableRows = within(getTableBody(table)).getAllByRole("row");
746
+ expect(tableRows).toHaveLength(3);
747
+ });
748
+
749
+ it("should display 'Showing 3 of 3 rows'", () => {
750
+ const tableRowsCount = screen.getByTestId(Selectors.TOOLBAR.TABLE_ROWS_COUNT);
751
+ expect(tableRowsCount).toHaveTextContent("Showing 3 of 3 rows");
752
+ });
753
+ });
754
+ });
755
+
756
+ describe("When user selects header checkbox", () => {
757
+ let actionBar: HTMLElement;
758
+
759
+ beforeEach(async () => {
760
+ const checkbox = getCheckBoxForTableHeaderRow(table);
761
+ await userEvent.click(checkbox);
762
+
763
+ actionBar = screen.getByTestId(Selectors.ACTION_BAR.ROOT);
764
+ });
765
+
766
+ it("should display batch action bar with '5 of 11 selected'", () => {
767
+ // Checking select all checkbox only selects the rows for the page
768
+ const selectedText = within(actionBar).getByTestId(Selectors.ACTION_BAR.SELECTED_TEXT);
769
+ expect(selectedText).toHaveTextContent("5 of 11 selected");
770
+ });
771
+ });
772
+ });
773
+
774
+ describe("Table Actions", () => {
775
+ describe("Button", () => {
776
+ it("should send an event with correct values when clicking on button", async () => {
777
+ const cell = getCellInRowContent(table, 0, "Action");
778
+ await userEvent.click(within(cell).getByRole("button"));
779
+
780
+ expect(clickCellButtonMock).toHaveBeenCalled();
781
+
782
+ const lastCalledWith = clickCellButtonMock.mock.calls[0][0];
783
+ expect(lastCalledWith).toMatchObject({
784
+ id: "12345_button",
785
+ row: {
786
+ id: "12345",
787
+ index: 0,
788
+ original: tableDataLarge[0],
789
+ },
790
+ });
791
+ });
792
+ });
793
+
794
+ describe("Button groups", () => {
795
+ let buttons: HTMLButtonElement[];
796
+
797
+ beforeEach(() => {
798
+ clickButtonGroupCellButtonMock.mockClear();
799
+ const cell = getCellInRowContent(table, 0, "Buttons");
800
+ buttons = within(cell).getAllByRole("button");
801
+ });
802
+
803
+ it("click on button 1", async () => {
804
+ const button = buttons[0];
805
+ await userEvent.click(button);
806
+
807
+ expect(clickButtonGroupCellButtonMock).toHaveBeenCalled();
808
+
809
+ const lastCalledWith = clickButtonGroupCellButtonMock.mock.calls[0][0];
810
+ expect(lastCalledWith).toMatchObject({
811
+ id: "12345_buttonGroup",
812
+ buttonId: "12345_buttonGroup_button-1",
813
+ row: {
814
+ id: "12345",
815
+ index: 0,
816
+ original: tableDataLarge[0],
817
+ },
818
+ });
819
+ });
820
+
821
+ it("click on button 2", async () => {
822
+ const button = buttons[1];
823
+ await userEvent.click(button);
824
+
825
+ expect(clickButtonGroupCellButtonMock).toHaveBeenCalled();
826
+
827
+ const lastCalledWith = clickButtonGroupCellButtonMock.mock.calls[0][0];
828
+ expect(lastCalledWith).toMatchObject({
829
+ id: "12345_buttonGroup",
830
+ buttonId: "12345_buttonGroup_button-2",
831
+ row: {
832
+ id: "12345",
833
+ index: 0,
834
+ original: tableDataLarge[0],
835
+ },
836
+ });
837
+ });
838
+
839
+ it("click on button 3", async () => {
840
+ const button = buttons[2];
841
+ await userEvent.click(button);
842
+
843
+ expect(clickButtonGroupCellButtonMock).toHaveBeenCalled();
844
+
845
+ const lastCalledWith = clickButtonGroupCellButtonMock.mock.calls[0][0];
846
+ expect(lastCalledWith).toMatchObject({
847
+ id: "12345_buttonGroup",
848
+ buttonId: "12345_buttonGroup_button-3",
849
+ row: {
850
+ id: "12345",
851
+ index: 0,
852
+ original: tableDataLarge[0],
853
+ },
854
+ });
855
+ });
856
+ });
857
+ });
858
+
859
+ // TODO: Skipping this test since its failing on multiple issues, see Violations at bottom of file.
860
+ describe.skip("Accessibility", () => {
861
+ it("is accessible", async () => {
862
+ const { container } = render(<TestComponent enableMultiRowSelection={true} />);
863
+ const results = await axe(container, { elementRef: true });
864
+
865
+ const buttonNameViolation = results.violations.find(
866
+ (violation) => violation.id === "button-name"
867
+ );
868
+
869
+ // "Buttons must have discernible text (button-name)"
870
+ // This is rule is failing on the toggles and checkboxes due to them being buttons with roles instead of input elements
871
+ if (buttonNameViolation) {
872
+ buttonNameViolation.nodes = buttonNameViolation.nodes.filter(
873
+ (node) =>
874
+ !node.element?.matches('[role="switch"]') ||
875
+ !node.element?.matches('[role="checkbox"]')
876
+ );
877
+
878
+ // remove the violation if there are no more nodes
879
+ if (!buttonNameViolation.nodes.length) {
880
+ results.violations.splice(results.violations.indexOf(buttonNameViolation), 1);
881
+ }
882
+ }
883
+
884
+ // console.log("Violations", results.violations[2].nodes[0]);
885
+
886
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
887
+ // @ts-ignore
888
+ // expect(results).toHaveNoViolations();
889
+ expect(results.violations.length).toBe(0);
890
+ });
891
+ });
892
+ });
893
+
894
+ describe("With row toggle", () => {
895
+ let table: HTMLTableElement;
896
+
897
+ beforeEach(async () => {
898
+ render(<TestComponent enableMultiRowSelection={false} />);
899
+
900
+ table = screen.getByRole("table");
901
+ });
902
+
903
+ afterEach(() => {
904
+ cleanup();
905
+ });
906
+
907
+ it("should have 10 headers", () => {
908
+ const expectedHeaders = [
909
+ "rowSelectionAriaLabels header",
910
+ "ID",
911
+ "Name",
912
+ "Link",
913
+ "Age",
914
+ "Badge",
915
+ "Position",
916
+ "Action",
917
+ "Date",
918
+ "Buttons",
919
+ ];
920
+
921
+ const headers = within(getTableHead(table)).getAllByRole("columnheader");
922
+ expect(headers).toHaveLength(expectedHeaders.length);
923
+
924
+ expectedHeaders.forEach((expectedHeader, index) => {
925
+ expect(headers[index]).toHaveTextContent(expectedHeader);
926
+ });
927
+ });
928
+
929
+ it("should have 5 rows of data", () => {
930
+ const tableRows = within(getTableBody(table)).getAllByRole("row");
931
+ expect(tableRows).toHaveLength(5);
932
+ });
933
+
934
+ it("should have data in first row", () => {
935
+ const tableRow1 = within(getTableBody(table)).getAllByRole("row")[0];
936
+
937
+ const tableRowCells = within(tableRow1).getAllByRole("cell");
938
+
939
+ const expectedCellsTextContent = [
940
+ "",
941
+ "12345",
942
+ "Name 1",
943
+ "Link 2",
944
+ "40",
945
+ "Information",
946
+ "1",
947
+ "ClickMe",
948
+ "2025-03-26 11:14",
949
+ [
950
+ { testId: "download-icon", ariaLabeL: "ClickMe" },
951
+ { testId: "remove-icon", ariaLabeL: "ClickMe" },
952
+ { testId: "more-vertical-icon", ariaLabeL: "ClickMe" },
953
+ ],
954
+ ];
955
+
956
+ tableRowCells.forEach((tableRowCell, index) => {
957
+ if (typeof expectedCellsTextContent[index] === "string") {
958
+ expect(tableRowCell).toHaveTextContent(expectedCellsTextContent[index]);
959
+ } else {
960
+ expectedCellsTextContent[index].forEach((expectedElement, buttonIndex) => {
961
+ const button = within(tableRowCell).getAllByRole("button")[buttonIndex];
962
+ const svg = within(button).getByTestId(expectedElement.testId);
963
+ expect(svg).toBeInTheDocument();
964
+ expect(button).toHaveAttribute("aria-label", expectedElement.ariaLabeL);
965
+ });
966
+ }
967
+ });
968
+ });
969
+
970
+ describe("Select table rows", () => {
971
+ let toggleRow0: HTMLButtonElement;
972
+
973
+ beforeEach(async () => {
974
+ toggleRow0 = getRadioToggleForTableBodyRow(table, 0);
975
+ await userEvent.click(toggleRow0);
976
+ });
977
+
978
+ it("should not display batch action bar when user toggles a row", async () => {
979
+ expect(toggleRow0).toBeChecked();
980
+
981
+ const actionBar = screen.queryByTestId(Selectors.ACTION_BAR.ROOT);
982
+ expect(actionBar).not.toBeInTheDocument();
983
+ });
984
+
985
+ it("should only have 1 checked row", async () => {
986
+ const toggleRow3 = getRadioToggleForTableBodyRow(table, 2);
987
+ await userEvent.click(toggleRow3);
988
+
989
+ expect(toggleRow0).not.toBeChecked();
990
+ expect(toggleRow3).toBeChecked();
991
+ });
992
+ });
993
+ });
994
+
995
+ // TODO: Impement this around certain sections of the table
996
+ describe.skip("Accessibility", () => {
997
+ it("is accessible", async () => {
998
+ const { container } = render(<TestComponent enableMultiRowSelection={true} />);
999
+ const results = await axe(container, { elementRef: true });
1000
+
1001
+ const buttonNameViolation = results.violations.find(
1002
+ (violation) => violation.id === "button-name"
1003
+ );
1004
+
1005
+ // "Buttons must have discernible text (button-name)"
1006
+ // This is rule is failing on the toggles and checkboxes due to them being buttons with roles instead of input elements
1007
+ if (buttonNameViolation) {
1008
+ buttonNameViolation.nodes = buttonNameViolation.nodes.filter(
1009
+ (node) =>
1010
+ !node.element?.matches('[role="switch"]') || !node.element?.matches('[role="checkbox"]')
1011
+ );
1012
+
1013
+ // remove the violation if there are no more nodes
1014
+ if (!buttonNameViolation.nodes.length) {
1015
+ results.violations.splice(results.violations.indexOf(buttonNameViolation), 1);
1016
+ }
1017
+ }
1018
+
1019
+ // console.log("Violations", results.violations[2].nodes[0]);
1020
+
1021
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1022
+ // @ts-ignore
1023
+ // expect(results).toHaveNoViolations();
1024
+ expect(results.violations.length).toBe(0);
1025
+ });
1026
+ });
1027
+ });
1028
+
1029
+ describe("With row toggle", () => {
1030
+ let table: HTMLTableElement;
1031
+
1032
+ beforeEach(async () => {
1033
+ render(<TestComponent enableMultiRowSelection={false} />);
1034
+
1035
+ table = screen.getByRole("table");
1036
+ });
1037
+
1038
+ afterEach(() => {
1039
+ cleanup();
1040
+ });
1041
+
1042
+ it("should have 10 headers", () => {
1043
+ const expectedHeaders = [
1044
+ "rowSelectionAriaLabels header",
1045
+ "ID",
1046
+ "Name",
1047
+ "Link",
1048
+ "Age",
1049
+ "Badge",
1050
+ "Position",
1051
+ "Action",
1052
+ "Date",
1053
+ "Buttons",
1054
+ ];
1055
+
1056
+ const headers = within(getTableHead(table)).getAllByRole("columnheader");
1057
+ expect(headers).toHaveLength(expectedHeaders.length);
1058
+
1059
+ expectedHeaders.forEach((expectedHeader, index) => {
1060
+ expect(headers[index]).toHaveTextContent(expectedHeader);
1061
+ });
1062
+ });
1063
+
1064
+ it("should have 5 rows of data", () => {
1065
+ const tableRows = within(getTableBody(table)).getAllByRole("row");
1066
+ expect(tableRows).toHaveLength(5);
1067
+ });
1068
+
1069
+ it("should have data in first row", () => {
1070
+ const tableRow1 = within(getTableBody(table)).getAllByRole("row")[0];
1071
+
1072
+ const tableRowCells = within(tableRow1).getAllByRole("cell");
1073
+
1074
+ const expectedCellsTextContent = [
1075
+ "",
1076
+ "12345",
1077
+ "Name 1",
1078
+ "Link 2",
1079
+ "40",
1080
+ "Information",
1081
+ "1",
1082
+ "ClickMe",
1083
+ "2025-03-26 11:14",
1084
+ [
1085
+ { testId: "download-icon", ariaLabeL: "ClickMe" },
1086
+ { testId: "remove-icon", ariaLabeL: "ClickMe" },
1087
+ { testId: "more-vertical-icon", ariaLabeL: "ClickMe" },
1088
+ ],
1089
+ ];
1090
+
1091
+ tableRowCells.forEach((tableRowCell, index) => {
1092
+ if (typeof expectedCellsTextContent[index] === "string") {
1093
+ expect(tableRowCell).toHaveTextContent(expectedCellsTextContent[index]);
1094
+ } else {
1095
+ expectedCellsTextContent[index].forEach((expectedElement, buttonIndex) => {
1096
+ const button = within(tableRowCell).getAllByRole("button")[buttonIndex];
1097
+ const svg = within(button).getByTestId(expectedElement.testId);
1098
+ expect(svg).toBeInTheDocument();
1099
+ expect(button).toHaveAttribute("aria-label", expectedElement.ariaLabeL);
1100
+ });
1101
+ }
1102
+ });
1103
+ });
1104
+
1105
+ describe("Select table rows", () => {
1106
+ let toggleRow0: HTMLButtonElement;
1107
+
1108
+ beforeEach(async () => {
1109
+ toggleRow0 = getRadioToggleForTableBodyRow(table, 0);
1110
+ await userEvent.click(toggleRow0);
1111
+ });
1112
+
1113
+ it("should not display batch action bar when user toggles a row", async () => {
1114
+ expect(toggleRow0).toBeChecked();
1115
+
1116
+ const actionBar = screen.queryByTestId(Selectors.ACTION_BAR.ROOT);
1117
+ expect(actionBar).not.toBeInTheDocument();
1118
+ });
1119
+
1120
+ it("should only have 1 checked row", async () => {
1121
+ const toggleRow3 = getRadioToggleForTableBodyRow(table, 2);
1122
+ await userEvent.click(toggleRow3);
1123
+
1124
+ expect(toggleRow0).not.toBeChecked();
1125
+ expect(toggleRow3).toBeChecked();
1126
+ });
1127
+ });
1128
+ });
1129
+
1130
+ // TODO: Impement this around certain sections of the table
1131
+ describe.skip("Accessibility", () => {
1132
+ it("is accessible", async () => {
1133
+ const { container } = render(<TestComponent enableMultiRowSelection={false} />);
1134
+ const results = await axe(container);
1135
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1136
+ // @ts-ignore TODO fix this
1137
+ expect(results).toHaveNoViolations();
1138
+ });
1139
+ });
1140
+
1141
+ // Violations [
1142
+ // {
1143
+ // id: 'aria-allowed-role',
1144
+ // impact: 'minor',
1145
+ // tags: [ 'cat.aria', 'best-practice' ],
1146
+ // description: 'Ensure role attribute has an appropriate value for the element',
1147
+ // help: 'ARIA role should be appropriate for the element',
1148
+ // helpUrl: 'https://dequeuniversity.com/rules/axe/4.10/aria-allowed-role?application=axeAPI',
1149
+ // nodes: [ [Object] ]
1150
+ // html: <input aria-autocomplete="list" aria-controls=":r1a:-listbox" autocomplete="off" id=":r1a:-input" role="combobox" min="1" max="3" data-testid="purpur-pagination-page-selector-autocomplete-input-input" aria-invalid="false" class="_purpur-text-field__input_nj9f4_27" type="number" value="1"></input>
1151
+ // },
1152
+ // {
1153
+ // id: 'aria-required-attr',
1154
+ // impact: 'critical',
1155
+ // // should be no space between 9.4 and .1.2 but it fails @telia/pii/no-ip
1156
+ // tags: [ 'cat.aria', 'wcag2a', 'wcag412', 'EN-301-549', 'EN-9.4 .1.2' ],
1157
+ // description: 'Ensure elements with ARIA roles have all required ARIA attributes',
1158
+ // help: 'Required ARIA attributes must be provided',
1159
+ // helpUrl: 'https://dequeuniversity.com/rules/axe/4.10/aria-required-attr?application=axeAPI',
1160
+ // nodes: [ [Object] ]
1161
+ // html: <input aria-autocomplete="list" aria-controls=":r1a:-listbox" autocomplete="off" id=":r1a:-input" role="combobox" min="1" max="3" data-testid="purpur-pagination-page-selector-autocomplete-input-input" aria-invalid="false" class="_purpur-text-field__input_nj9f4_27" type="number" value="1">
1162
+ // },
1163
+ // {
1164
+ // id: 'aria-valid-attr-value',
1165
+ // impact: 'critical',
1166
+ // // should be no space between 9.4 and .1.2 but it fails @telia/pii/no-ip
1167
+ // tags: [ 'cat.aria', 'wcag2a', 'wcag412', 'EN-301-549', 'EN-9.4 .1.2' ],
1168
+ // description: 'Ensure all ARIA attributes have valid values',
1169
+ // help: 'ARIA attributes must conform to valid values',
1170
+ // helpUrl: 'https://dequeuniversity.com/rules/axe/4.10/aria-valid-attr-value?application=axeAPI',
1171
+ // nodes: [ [Object] ]
1172
+ // html:<input aria-autocomplete="list" aria-controls=":r1a:-listbox" autocomplete="off" id=":r1a:-input" role="combobox" min="1" max="3" data-testid="purpur-pagination-page-selector-autocomplete-input-input" aria-invalid="false" class="_purpur-text-field__input_nj9f4_27" type="number" value="1">
1173
+ // },
1174
+ // {
1175
+ // id: 'label',
1176
+ // impact: 'critical',
1177
+ // tags: [
1178
+ // 'cat.forms',
1179
+ // 'wcag2a',
1180
+ // 'wcag412',
1181
+ // 'section508',
1182
+ // 'section508.22.n',
1183
+ // 'TTv5',
1184
+ // 'TT5.c',
1185
+ // 'EN-301-549',
1186
+ // // should be no space between 9.4 and .1.2 but it fails @telia/pii/no-ip
1187
+ // 'EN-9.4 .1.2',
1188
+ // 'ACT'
1189
+ // ],
1190
+ // description: 'Ensure every form element has a label',
1191
+ // help: 'Form elements must have labels',
1192
+ // helpUrl: 'https://dequeuniversity.com/rules/axe/4.10/label?application=axeAPI',
1193
+ // nodes: [ [Object] ]
1194
+ // html:<input aria-autocomplete="list" aria-controls=":r1a:-listbox" autocomplete="off" id=":r1a:-input" role="combobox" min="1" max="3" data-testid="purpur-pagination-page-selector-autocomplete-input-input" aria-invalid="false" class="_purpur-text-field__input_nj9f4_27" type="number" value="1">
1195
+ // }
1196
+ // ]