@dynatrace/strato-components-testing 1.18.0 → 3.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.
- package/esm/jest/index.js +102 -3
- package/esm/jest/index.js.map +2 -2
- package/esm/jest/mocks/create-mock-element.js +81 -0
- package/esm/jest/mocks/create-mock-element.js.map +7 -0
- package/esm/jest/mocks/create-range-mock.js +28 -0
- package/esm/jest/mocks/create-range-mock.js.map +7 -0
- package/esm/jest/mocks/crypto-mock.js +20 -0
- package/esm/jest/mocks/crypto-mock.js.map +7 -0
- package/esm/jest/mocks/dom-rect-mock.js.map +2 -2
- package/esm/jest/mocks/element-from-point-mock.js +12 -0
- package/esm/jest/mocks/element-from-point-mock.js.map +7 -0
- package/esm/jest/mocks/fetch-mock.js +20 -0
- package/esm/jest/mocks/fetch-mock.js.map +7 -0
- package/esm/jest/mocks/framer-motion-mock.js +59 -0
- package/esm/jest/mocks/framer-motion-mock.js.map +7 -0
- package/esm/jest/mocks/intersection-observer-mock.js +40 -0
- package/esm/jest/mocks/intersection-observer-mock.js.map +7 -0
- package/esm/jest/mocks/match-media-mock.js +27 -0
- package/esm/jest/mocks/match-media-mock.js.map +7 -0
- package/esm/jest/mocks/offset-height-mock.js +21 -0
- package/esm/jest/mocks/offset-height-mock.js.map +7 -0
- package/esm/jest/mocks/offset-width-mock.js +21 -0
- package/esm/jest/mocks/offset-width-mock.js.map +7 -0
- package/esm/jest/mocks/pointer-event-mock.js +24 -0
- package/esm/jest/mocks/pointer-event-mock.js.map +7 -0
- package/esm/jest/mocks/screen-size-mock.js +16 -0
- package/esm/jest/mocks/screen-size-mock.js.map +7 -0
- package/esm/jest/mocks/scroll-into-view-mock.js +16 -0
- package/esm/jest/mocks/scroll-into-view-mock.js.map +7 -0
- package/esm/jest/mocks/streamdown.mock.js +8 -0
- package/esm/jest/mocks/streamdown.mock.js.map +7 -0
- package/esm/jest/mocks/table-virtualization-mock.js +87 -0
- package/esm/jest/mocks/table-virtualization-mock.js.map +7 -0
- package/esm/jest/mocks/virtualization-mock.js +155 -0
- package/esm/jest/mocks/virtualization-mock.js.map +7 -0
- package/esm/jest/preset/jest-preset.js +2 -1
- package/esm/jest/preset/jest-preset.js.map +2 -2
- package/esm/jest/setup/index.js +51 -0
- package/esm/jest/setup/index.js.map +2 -2
- package/esm/jest/testing-helpers/filters/filter-field.js +424 -0
- package/esm/jest/testing-helpers/filters/filter-field.js.map +7 -0
- package/esm/jest/testing-helpers/forms/base-input.js +32 -0
- package/esm/jest/testing-helpers/forms/base-input.js.map +7 -0
- package/esm/jest/testing-helpers/forms/password-input.js +29 -0
- package/esm/jest/testing-helpers/forms/password-input.js.map +7 -0
- package/esm/jest/testing-helpers/forms/search-input.js +27 -0
- package/esm/jest/testing-helpers/forms/search-input.js.map +7 -0
- package/esm/jest/testing-helpers/forms/select.js +131 -0
- package/esm/jest/testing-helpers/forms/select.js.map +7 -0
- package/esm/jest/testing-helpers/forms/text-input.js +24 -0
- package/esm/jest/testing-helpers/forms/text-input.js.map +7 -0
- package/esm/jest/testing-helpers/tables/datatable.js +794 -0
- package/esm/jest/testing-helpers/tables/datatable.js.map +7 -0
- package/esm/jest/testing-helpers/utils/isFakeTimersEnabled.js +9 -0
- package/esm/jest/testing-helpers/utils/isFakeTimersEnabled.js.map +7 -0
- package/esm/jest/testing-helpers/utils/setup-user-event.js +15 -0
- package/esm/jest/testing-helpers/utils/setup-user-event.js.map +7 -0
- package/jest/index.d.ts +22 -1
- package/jest/index.js +54 -3
- package/jest/mocks/create-mock-element.d.ts +15 -0
- package/jest/mocks/create-mock-element.js +99 -0
- package/jest/mocks/create-range-mock.d.ts +11 -0
- package/jest/mocks/create-range-mock.js +46 -0
- package/jest/mocks/crypto-mock.d.ts +12 -0
- package/jest/mocks/crypto-mock.js +48 -0
- package/jest/mocks/dom-rect-mock.d.ts +1 -0
- package/jest/mocks/element-from-point-mock.d.ts +11 -0
- package/jest/{setup.js → mocks/element-from-point-mock.js} +10 -16
- package/jest/mocks/fetch-mock.d.ts +10 -0
- package/jest/mocks/fetch-mock.js +38 -0
- package/jest/mocks/framer-motion-mock.d.ts +18 -0
- package/jest/mocks/framer-motion-mock.js +75 -0
- package/jest/mocks/intersection-observer-mock.d.ts +11 -0
- package/jest/mocks/intersection-observer-mock.js +58 -0
- package/jest/mocks/match-media-mock.d.ts +11 -0
- package/jest/mocks/match-media-mock.js +45 -0
- package/jest/mocks/offset-height-mock.d.ts +10 -0
- package/jest/mocks/offset-height-mock.js +39 -0
- package/jest/mocks/offset-width-mock.d.ts +10 -0
- package/jest/mocks/offset-width-mock.js +39 -0
- package/jest/mocks/pointer-event-mock.d.ts +10 -0
- package/jest/mocks/pointer-event-mock.js +42 -0
- package/jest/mocks/screen-size-mock.d.ts +10 -0
- package/jest/mocks/screen-size-mock.js +34 -0
- package/jest/mocks/scroll-into-view-mock.d.ts +10 -0
- package/jest/mocks/scroll-into-view-mock.js +34 -0
- package/jest/mocks/streamdown.mock.d.ts +2 -0
- package/jest/mocks/streamdown.mock.js +26 -0
- package/jest/mocks/table-virtualization-mock.d.ts +15 -0
- package/jest/mocks/table-virtualization-mock.js +102 -0
- package/jest/mocks/virtualization-mock.d.ts +9 -0
- package/jest/mocks/virtualization-mock.js +170 -0
- package/jest/preset/jest-preset.d.ts +2 -3
- package/jest/preset/jest-preset.js +2 -1
- package/jest/setup/index.js +27 -0
- package/jest/testing-helpers/filters/filter-field.d.ts +109 -0
- package/jest/testing-helpers/filters/filter-field.js +436 -0
- package/jest/testing-helpers/forms/base-input.d.ts +17 -0
- package/jest/testing-helpers/forms/base-input.js +50 -0
- package/jest/testing-helpers/forms/password-input.d.ts +16 -0
- package/jest/testing-helpers/forms/password-input.js +47 -0
- package/jest/testing-helpers/forms/search-input.d.ts +20 -0
- package/jest/testing-helpers/forms/search-input.js +45 -0
- package/jest/testing-helpers/forms/select.d.ts +65 -0
- package/jest/testing-helpers/forms/select.js +159 -0
- package/jest/testing-helpers/forms/text-input.d.ts +19 -0
- package/jest/testing-helpers/forms/text-input.js +42 -0
- package/jest/testing-helpers/tables/datatable.d.ts +239 -0
- package/jest/testing-helpers/tables/datatable.js +812 -0
- package/jest/testing-helpers/utils/isFakeTimersEnabled.d.ts +5 -0
- package/jest/testing-helpers/utils/isFakeTimersEnabled.js +27 -0
- package/jest/testing-helpers/utils/setup-user-event.d.ts +6 -0
- package/jest/testing-helpers/utils/setup-user-event.js +43 -0
- package/package.json +11 -3
- package/esm/jest/setup.js +0 -24
- package/esm/jest/setup.js.map +0 -7
- package/jest/setup.d.ts +0 -14
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
import { act, fireEvent, screen, within } from "@testing-library/react";
|
|
2
|
+
import { PointerEventsCheckLevel } from "@testing-library/user-event";
|
|
3
|
+
import { setupUserEvent } from "../utils/setup-user-event.js";
|
|
4
|
+
class DataTableSubRowsHelper {
|
|
5
|
+
constructor(rootHelper) {
|
|
6
|
+
this.rootHelper = rootHelper;
|
|
7
|
+
}
|
|
8
|
+
async getSubRowButton(rowId) {
|
|
9
|
+
try {
|
|
10
|
+
return within(this.rootHelper.element).getByLabelText(
|
|
11
|
+
"subrow with id",
|
|
12
|
+
{
|
|
13
|
+
suggest: false,
|
|
14
|
+
selector: `[data-rowid="${CSS.escape(rowId)}"] button`,
|
|
15
|
+
exact: false
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
} catch {
|
|
19
|
+
throw new Error(`Unable to locate subrow expander with id "${rowId}"`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
get getToggleAllSubRowsButton() {
|
|
23
|
+
return within(this.rootHelper.element).getByLabelText(
|
|
24
|
+
/Collapse all subrows|Expand all subrows/
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Return the expanded state of the passed subrow id.
|
|
29
|
+
*/
|
|
30
|
+
async getSubRowStateById(rowId) {
|
|
31
|
+
const subRowButton = await this.getSubRowButton(rowId);
|
|
32
|
+
return subRowButton.getAttribute("aria-expanded") === "true";
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Expands the given row identified by the passed rowId if it not yet expanded.
|
|
36
|
+
*/
|
|
37
|
+
async expandRowById(rowId) {
|
|
38
|
+
const subRowButton = await this.getSubRowButton(rowId);
|
|
39
|
+
if (subRowButton.getAttribute("aria-expanded") === "false") {
|
|
40
|
+
const userEvent = setupUserEvent();
|
|
41
|
+
await userEvent.click(subRowButton);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Collapses the given row identified by the passed rowId if not yet collapsed.
|
|
46
|
+
*/
|
|
47
|
+
async collapseRowById(rowId) {
|
|
48
|
+
const subRowButton = await this.getSubRowButton(rowId);
|
|
49
|
+
if (subRowButton.getAttribute("aria-expanded") === "true") {
|
|
50
|
+
const userEvent = setupUserEvent();
|
|
51
|
+
await userEvent.click(subRowButton);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Toggles the expanded state of a given row identified by the passed rowId if not yet collapsed.
|
|
56
|
+
*/
|
|
57
|
+
async toggleSubRowById(rowId) {
|
|
58
|
+
const subRowButton = await this.getSubRowButton(rowId);
|
|
59
|
+
const userEvent = setupUserEvent();
|
|
60
|
+
await userEvent.click(subRowButton);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Expands/Collapses all sub rows in the table.
|
|
64
|
+
*/
|
|
65
|
+
async toggleAllSubRows() {
|
|
66
|
+
const toggleAllButton = this.getToggleAllSubRowsButton;
|
|
67
|
+
const userEvent = setupUserEvent();
|
|
68
|
+
await userEvent.click(toggleAllButton);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
class DataTableRowSelectionHelper {
|
|
72
|
+
constructor(rootHelper) {
|
|
73
|
+
this.rootHelper = rootHelper;
|
|
74
|
+
}
|
|
75
|
+
async getCheckboxById(id) {
|
|
76
|
+
return await within(
|
|
77
|
+
this.rootHelper.element
|
|
78
|
+
).findByLabelText(
|
|
79
|
+
`Toggle row selection for row with id "${id}".`,
|
|
80
|
+
{
|
|
81
|
+
suggest: false,
|
|
82
|
+
selector: "input"
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
async getCheckboxSelectAll() {
|
|
87
|
+
return await within(
|
|
88
|
+
this.rootHelper.element
|
|
89
|
+
).findByLabelText(
|
|
90
|
+
// A partial of both options that could be in the aria label.
|
|
91
|
+
/select all rows/i,
|
|
92
|
+
{
|
|
93
|
+
suggest: false,
|
|
94
|
+
selector: "input"
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Ticks the selection checkbox for a row if it is not yet selected.
|
|
100
|
+
*/
|
|
101
|
+
async selectRowById(id) {
|
|
102
|
+
const checkbox = await this.getCheckboxById(id);
|
|
103
|
+
if (checkbox) {
|
|
104
|
+
if (!checkbox.checked) {
|
|
105
|
+
fireEvent.click(checkbox);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Unselects the selection checkbox for a row if it is not selected.
|
|
111
|
+
*/
|
|
112
|
+
async deselectRowById(id) {
|
|
113
|
+
const checkbox = await this.getCheckboxById(id);
|
|
114
|
+
if (checkbox) {
|
|
115
|
+
if (checkbox.checked) {
|
|
116
|
+
fireEvent.click(checkbox);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Toggles selection on the row.
|
|
122
|
+
*/
|
|
123
|
+
async toggleSelectionRowById(id) {
|
|
124
|
+
const checkbox = await this.getCheckboxById(id);
|
|
125
|
+
if (checkbox) {
|
|
126
|
+
fireEvent.click(checkbox);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Toggles selection on the row with the Shift key pressed (batch selection).
|
|
131
|
+
*/
|
|
132
|
+
async shiftToggleSelectionRowById(id) {
|
|
133
|
+
const checkbox = await this.getCheckboxById(id);
|
|
134
|
+
if (checkbox) {
|
|
135
|
+
fireEvent.click(checkbox, { shiftKey: true });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Returns the selection state for the row with the given ID.
|
|
140
|
+
*/
|
|
141
|
+
async getSelectionStateById(id) {
|
|
142
|
+
const checkbox = await this.getCheckboxById(id);
|
|
143
|
+
return checkbox.indeterminate === true ? "indeterminate" : checkbox.checked;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Returns a selection state for the currently shown checkboxes (current page).
|
|
147
|
+
* Hint: Elements might be virtualized or not shown on this page.
|
|
148
|
+
*/
|
|
149
|
+
async getSelectionState() {
|
|
150
|
+
const rows = await within(this.rootHelper.element).findAllByRole("row", {
|
|
151
|
+
hidden: true
|
|
152
|
+
});
|
|
153
|
+
const selectionState = {};
|
|
154
|
+
for (const row of rows) {
|
|
155
|
+
const rowId = row.getAttribute("data-rowid");
|
|
156
|
+
if (!rowId) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const checkbox = within(
|
|
160
|
+
this.rootHelper.element
|
|
161
|
+
).queryByLabelText(
|
|
162
|
+
`Toggle row selection for row with id "${rowId}".`,
|
|
163
|
+
{
|
|
164
|
+
suggest: false,
|
|
165
|
+
selector: "input"
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
if (!checkbox) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
selectionState[rowId] = checkbox.indeterminate ? "indeterminate" : checkbox.checked;
|
|
172
|
+
}
|
|
173
|
+
return selectionState;
|
|
174
|
+
}
|
|
175
|
+
async isSelectAllDisabled() {
|
|
176
|
+
const checkbox = await this.getCheckboxSelectAll();
|
|
177
|
+
const disabled = checkbox.getAttribute("aria-disabled");
|
|
178
|
+
return disabled === "true";
|
|
179
|
+
}
|
|
180
|
+
async getSelectAllState() {
|
|
181
|
+
const checkbox = await this.getCheckboxSelectAll();
|
|
182
|
+
const value = checkbox.getAttribute("aria-checked");
|
|
183
|
+
if (value === "true") {
|
|
184
|
+
return true;
|
|
185
|
+
} else if (value === "false") {
|
|
186
|
+
return false;
|
|
187
|
+
} else if (value === "mixed") {
|
|
188
|
+
return "indeterminate";
|
|
189
|
+
}
|
|
190
|
+
throw new Error(
|
|
191
|
+
"Could not determine the state of the select all checkbox."
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
async toggleSelectAll() {
|
|
195
|
+
const checkbox = await this.getCheckboxSelectAll();
|
|
196
|
+
fireEvent.click(checkbox);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
class DataTableUserActionHelper {
|
|
200
|
+
constructor(dataTestId) {
|
|
201
|
+
this.dataTestId = dataTestId;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Gets the root element of the DataTable
|
|
205
|
+
*/
|
|
206
|
+
get element() {
|
|
207
|
+
return screen.getByTestId(this.dataTestId);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Lets you call a HeaderAction registered on the DataTable
|
|
211
|
+
*/
|
|
212
|
+
async activateHeaderAction(headerName, actionName) {
|
|
213
|
+
const headerCells = await within(this.element).findAllByRole(
|
|
214
|
+
"columnheader",
|
|
215
|
+
{ hidden: true }
|
|
216
|
+
);
|
|
217
|
+
const headerCell = headerCells.find(
|
|
218
|
+
(element) => element.textContent === headerName
|
|
219
|
+
);
|
|
220
|
+
const headerCellRow = headerCell?.getAttribute("data-header-row");
|
|
221
|
+
const headerCellColumn = headerCell?.getAttribute("data-column");
|
|
222
|
+
const headerButton = document.querySelector(
|
|
223
|
+
`[data-testid="${this.dataTestId}"] button[data-trigger="useraction"][data-header-row="${headerCellRow}"][data-column="${headerCellColumn}"]`
|
|
224
|
+
);
|
|
225
|
+
if (!headerButton) {
|
|
226
|
+
throw new Error(`Could not find header button for ${headerName}`);
|
|
227
|
+
}
|
|
228
|
+
fireEvent.click(headerButton);
|
|
229
|
+
const menuItem = await screen.findByRole("menuitem", {
|
|
230
|
+
hidden: true,
|
|
231
|
+
name: actionName
|
|
232
|
+
});
|
|
233
|
+
fireEvent.click(menuItem);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Lets you call a CellAction registered on the DataTable
|
|
237
|
+
*/
|
|
238
|
+
async activateCellAction(headerName, row, actionName) {
|
|
239
|
+
const userEvent = setupUserEvent({
|
|
240
|
+
pointerEventsCheck: PointerEventsCheckLevel.Never
|
|
241
|
+
});
|
|
242
|
+
const header = await within(this.element).findByRole("columnheader", {
|
|
243
|
+
hidden: true,
|
|
244
|
+
name: headerName
|
|
245
|
+
});
|
|
246
|
+
const columnIndex = header.getAttribute("data-column");
|
|
247
|
+
const cellActionTrigger = document.querySelector(
|
|
248
|
+
`[data-testid="${this.dataTestId}"] [data-row="${row}"] [data-column="${columnIndex}"]`
|
|
249
|
+
);
|
|
250
|
+
if (cellActionTrigger) {
|
|
251
|
+
await userEvent.pointer({
|
|
252
|
+
keys: "[MouseRight]",
|
|
253
|
+
target: cellActionTrigger
|
|
254
|
+
});
|
|
255
|
+
const menuItem = await screen.findByRole("menuitem", {
|
|
256
|
+
hidden: true,
|
|
257
|
+
name: actionName
|
|
258
|
+
});
|
|
259
|
+
await userEvent.click(menuItem);
|
|
260
|
+
} else {
|
|
261
|
+
throw new Error(
|
|
262
|
+
`No cell action for ${headerName} (index: ${columnIndex}) / ${row} was found.`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Lets you call a RowAction registered on the DataTable
|
|
268
|
+
*/
|
|
269
|
+
async activateRowAction(row, actionName) {
|
|
270
|
+
const userEvent = setupUserEvent({
|
|
271
|
+
pointerEventsCheck: PointerEventsCheckLevel.Never
|
|
272
|
+
});
|
|
273
|
+
const header = this.element.querySelector(
|
|
274
|
+
'[role="columnheader"][data-columnid="DataTableRowActions"]'
|
|
275
|
+
);
|
|
276
|
+
if (!header) {
|
|
277
|
+
throw new Error("No RowActions column was found.");
|
|
278
|
+
}
|
|
279
|
+
const columnIndex = header.getAttribute("data-column");
|
|
280
|
+
const rowActionsCell = document.querySelector(
|
|
281
|
+
`[data-testid="${this.dataTestId}"] [data-row="${row}"] [data-column="${columnIndex}"]`
|
|
282
|
+
);
|
|
283
|
+
if (rowActionsCell) {
|
|
284
|
+
const directRowActionButton = await within(rowActionsCell).findByRole(
|
|
285
|
+
"button",
|
|
286
|
+
{ name: actionName }
|
|
287
|
+
);
|
|
288
|
+
await userEvent.click(directRowActionButton);
|
|
289
|
+
} else {
|
|
290
|
+
throw new Error(
|
|
291
|
+
`No RowActions cell (column: ${columnIndex}) / ${row} was found.`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
class DataTablePaginationHelper {
|
|
297
|
+
constructor(dataTestId) {
|
|
298
|
+
this.dataTestId = dataTestId;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Gets the root element of the DataTable
|
|
302
|
+
*/
|
|
303
|
+
get element() {
|
|
304
|
+
return screen.getByTestId(this.dataTestId);
|
|
305
|
+
}
|
|
306
|
+
get paginationElement() {
|
|
307
|
+
return within(this.element).getByRole("navigation", { hidden: true });
|
|
308
|
+
}
|
|
309
|
+
get pageSizeSelect() {
|
|
310
|
+
const inputElement = within(
|
|
311
|
+
this.paginationElement
|
|
312
|
+
).getByLabelText(/Select page size/, {
|
|
313
|
+
suggest: false
|
|
314
|
+
});
|
|
315
|
+
const triggerElement = inputElement.closest('[type="button"]');
|
|
316
|
+
if (triggerElement) {
|
|
317
|
+
return triggerElement;
|
|
318
|
+
}
|
|
319
|
+
throw new Error("Trigger for the pageSizeSelect was not found.");
|
|
320
|
+
}
|
|
321
|
+
get pageIndexSelect() {
|
|
322
|
+
const inputElement = within(
|
|
323
|
+
this.paginationElement
|
|
324
|
+
).getByLabelText(/Select current page/, {
|
|
325
|
+
suggest: false
|
|
326
|
+
});
|
|
327
|
+
const triggerElement = inputElement.closest('[type="button"]');
|
|
328
|
+
if (triggerElement) {
|
|
329
|
+
return triggerElement;
|
|
330
|
+
}
|
|
331
|
+
throw new Error("Trigger for the pageIndexSelect was not found.");
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Allows you to set the pageSize via the select in the DataTable Pagination.
|
|
335
|
+
* @param pageSize - Target pageSize the pagination should be set to.
|
|
336
|
+
*/
|
|
337
|
+
async setPageSizeTo(pageSize) {
|
|
338
|
+
await act(async () => {
|
|
339
|
+
fireEvent.click(this.pageSizeSelect);
|
|
340
|
+
});
|
|
341
|
+
const controlsInput = this.pageSizeSelect.querySelector("[aria-controls]");
|
|
342
|
+
const controlsAria = controlsInput?.getAttribute("aria-controls");
|
|
343
|
+
if (controlsAria) {
|
|
344
|
+
const portalOutletElement = document.getElementById(controlsAria);
|
|
345
|
+
if (portalOutletElement) {
|
|
346
|
+
try {
|
|
347
|
+
const selectedOption = within(portalOutletElement).getByRole(
|
|
348
|
+
"option",
|
|
349
|
+
{
|
|
350
|
+
name: `${pageSize}`,
|
|
351
|
+
hidden: true
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
if (selectedOption.getAttribute("aria-selected") === "true") {
|
|
355
|
+
const userEvent = setupUserEvent();
|
|
356
|
+
await userEvent.keyboard("{Escape /}");
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
await act(async () => {
|
|
360
|
+
fireEvent.click(selectedOption);
|
|
361
|
+
});
|
|
362
|
+
} catch (error) {
|
|
363
|
+
throw new Error(error);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Allows you to set the pageIndex via the select in the DataTable Pagination.
|
|
370
|
+
* @param pageIndex - pageIndex you want to navigate to. 0 based value
|
|
371
|
+
*/
|
|
372
|
+
async setPageIndex(pageIndex) {
|
|
373
|
+
act(() => {
|
|
374
|
+
fireEvent.click(this.pageIndexSelect);
|
|
375
|
+
});
|
|
376
|
+
const controlsInput = this.pageIndexSelect.querySelector("[aria-controls]");
|
|
377
|
+
const controlsAria = controlsInput?.getAttribute("aria-controls");
|
|
378
|
+
if (controlsAria) {
|
|
379
|
+
const portalOutletElement = document.getElementById(controlsAria);
|
|
380
|
+
if (portalOutletElement) {
|
|
381
|
+
try {
|
|
382
|
+
const selectedOption = within(portalOutletElement).getByRole(
|
|
383
|
+
"option",
|
|
384
|
+
{
|
|
385
|
+
name: `${pageIndex + 1}`,
|
|
386
|
+
hidden: true
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
if (selectedOption.getAttribute("aria-selected") === "true") {
|
|
390
|
+
const userEvent = setupUserEvent();
|
|
391
|
+
await userEvent.keyboard("{Escape /}");
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
act(() => {
|
|
395
|
+
fireEvent.click(selectedOption);
|
|
396
|
+
});
|
|
397
|
+
} catch (error) {
|
|
398
|
+
throw new Error(error);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Allows you to go to the next page using the "Next page" button.
|
|
405
|
+
*/
|
|
406
|
+
async goToNextPage() {
|
|
407
|
+
const nextButton = screen.getByRole("button", { name: /Go to next page/ });
|
|
408
|
+
const userEvent = setupUserEvent();
|
|
409
|
+
await userEvent.click(nextButton);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Allows you to go to the previous page using the "Previous page" button.
|
|
413
|
+
*/
|
|
414
|
+
async goToPreviousPage() {
|
|
415
|
+
const previousButton = screen.getByRole("button", {
|
|
416
|
+
name: /Go to previous page/
|
|
417
|
+
});
|
|
418
|
+
const userEvent = setupUserEvent();
|
|
419
|
+
await userEvent.click(previousButton);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const getSortCombination = (current, target) => {
|
|
423
|
+
if (!current || current === "other" || !target) {
|
|
424
|
+
return void 0;
|
|
425
|
+
}
|
|
426
|
+
return `from-${current}-to-${target}`;
|
|
427
|
+
};
|
|
428
|
+
class DataTableSortingHelper {
|
|
429
|
+
constructor(dataTestId, rootHelper) {
|
|
430
|
+
this.dataTestId = dataTestId;
|
|
431
|
+
this.rootHelper = rootHelper;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Gets the root element of the DataTable
|
|
435
|
+
*/
|
|
436
|
+
get element() {
|
|
437
|
+
return screen.getByTestId(this.dataTestId);
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Get a column header by header name
|
|
441
|
+
*/
|
|
442
|
+
async getColumnHeader(headerName) {
|
|
443
|
+
return (await within(this.element).findByLabelText(headerName)).closest(
|
|
444
|
+
'[role="columnheader"]'
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Will return the current sorting direction of the column provided by the parameter.
|
|
449
|
+
*/
|
|
450
|
+
async getSortingDirection(headerName) {
|
|
451
|
+
const header = await this.getColumnHeader(headerName);
|
|
452
|
+
if (header) {
|
|
453
|
+
return header.getAttribute("aria-sort");
|
|
454
|
+
}
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Will return the current `sortDescFirst` attribute of the column provided by the parameter.
|
|
459
|
+
*/
|
|
460
|
+
async getSortDescFirst(headerName) {
|
|
461
|
+
const header = await this.getColumnHeader(headerName);
|
|
462
|
+
if (header) {
|
|
463
|
+
return header.dataset.sortDescFirst === "true";
|
|
464
|
+
}
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Lets you sort the column.
|
|
469
|
+
*/
|
|
470
|
+
async sortColumn(headerName, options) {
|
|
471
|
+
const userEvent = setupUserEvent({
|
|
472
|
+
pointerEventsCheck: PointerEventsCheckLevel.Never
|
|
473
|
+
});
|
|
474
|
+
const header = await this.getColumnHeader(headerName);
|
|
475
|
+
const [currentSortDirection, sortDescFirst] = await Promise.all([
|
|
476
|
+
this.getSortingDirection(headerName),
|
|
477
|
+
this.getSortDescFirst(headerName)
|
|
478
|
+
]);
|
|
479
|
+
const columnIndex = header?.getAttribute("data-column") || null;
|
|
480
|
+
const headerRowIndex = header?.getAttribute("data-header-row") || null;
|
|
481
|
+
if (columnIndex === null || headerRowIndex === null) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const headerButtonTrigger = document.querySelector(
|
|
485
|
+
`[data-testid="${this.dataTestId}"] [data-header-row="${headerRowIndex}"][data-column="${columnIndex}"]`
|
|
486
|
+
);
|
|
487
|
+
const actionType = headerButtonTrigger?.getAttribute("aria-label")?.includes("sorting") ? "sortButton" : "headerAction";
|
|
488
|
+
if (!headerButtonTrigger || !actionType) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (actionType === "headerAction") {
|
|
492
|
+
const actionName = `Sort ${options?.direction}`;
|
|
493
|
+
await this.rootHelper.userActions.activateHeaderAction(
|
|
494
|
+
headerName,
|
|
495
|
+
actionName
|
|
496
|
+
);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (options?.multiSorting) {
|
|
500
|
+
await userEvent.keyboard("[ShiftLeft>]");
|
|
501
|
+
}
|
|
502
|
+
const combination = getSortCombination(
|
|
503
|
+
currentSortDirection,
|
|
504
|
+
options?.direction
|
|
505
|
+
);
|
|
506
|
+
if (!combination) {
|
|
507
|
+
await userEvent.click(headerButtonTrigger);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (sortDescFirst) {
|
|
511
|
+
switch (combination) {
|
|
512
|
+
case "from-none-to-ascending": {
|
|
513
|
+
await userEvent.click(headerButtonTrigger);
|
|
514
|
+
await userEvent.click(headerButtonTrigger);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
case "from-none-to-descending": {
|
|
518
|
+
await userEvent.click(headerButtonTrigger);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
case "from-ascending-to-descending": {
|
|
522
|
+
await userEvent.click(headerButtonTrigger);
|
|
523
|
+
await userEvent.click(headerButtonTrigger);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
case "from-descending-to-ascending": {
|
|
527
|
+
await userEvent.click(headerButtonTrigger);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
switch (combination) {
|
|
533
|
+
case "from-none-to-ascending": {
|
|
534
|
+
await userEvent.click(headerButtonTrigger);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
case "from-none-to-descending": {
|
|
538
|
+
await userEvent.click(headerButtonTrigger);
|
|
539
|
+
await userEvent.click(headerButtonTrigger);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
case "from-ascending-to-descending": {
|
|
543
|
+
await userEvent.click(headerButtonTrigger);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
case "from-descending-to-ascending": {
|
|
547
|
+
await userEvent.click(headerButtonTrigger);
|
|
548
|
+
await userEvent.click(headerButtonTrigger);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
class DataTableInteractiveRowsHelper {
|
|
555
|
+
constructor(_rootHelper) {
|
|
556
|
+
this._rootHelper = _rootHelper;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Returns the id of the currently active row or null if no rows are selected.
|
|
560
|
+
*/
|
|
561
|
+
async getActivatedRowId() {
|
|
562
|
+
const activatedRow = await within(
|
|
563
|
+
this._rootHelper.element
|
|
564
|
+
).queryByLabelText(/Deactivate row with id/i, {
|
|
565
|
+
selector: '[data-trigger="interactive-row"]'
|
|
566
|
+
});
|
|
567
|
+
if (activatedRow) {
|
|
568
|
+
return activatedRow.getAttribute("data-rowid") ?? null;
|
|
569
|
+
}
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Toggles the row identified by the given rowId as active.
|
|
574
|
+
*/
|
|
575
|
+
async toggleActiveRow(rowId) {
|
|
576
|
+
const activatedRow = await within(this._rootHelper.element).findByLabelText(
|
|
577
|
+
/(Activate|Deactivate) row with id/i,
|
|
578
|
+
{
|
|
579
|
+
selector: `[data-rowid="${CSS.escape(
|
|
580
|
+
rowId
|
|
581
|
+
)}"][data-trigger="interactive-row"]`
|
|
582
|
+
}
|
|
583
|
+
);
|
|
584
|
+
const userEvent = setupUserEvent();
|
|
585
|
+
await userEvent.click(activatedRow);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
class DataTableDownloadHelper {
|
|
589
|
+
constructor(rootHelper) {
|
|
590
|
+
this.rootHelper = rootHelper;
|
|
591
|
+
}
|
|
592
|
+
async getDownloadMenuTrigger() {
|
|
593
|
+
const menuToggle = await within(
|
|
594
|
+
this.rootHelper.element
|
|
595
|
+
).getByLabelText(/Download table data/, {
|
|
596
|
+
suggest: false,
|
|
597
|
+
selector: "button"
|
|
598
|
+
});
|
|
599
|
+
return menuToggle;
|
|
600
|
+
}
|
|
601
|
+
async openDownloadMenu() {
|
|
602
|
+
const isMenuOpen = await this.isMenuOpen();
|
|
603
|
+
if (!isMenuOpen) {
|
|
604
|
+
const trigger = await this.getDownloadMenuTrigger();
|
|
605
|
+
const userEvent = setupUserEvent();
|
|
606
|
+
await userEvent.click(trigger);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
async isMenuOpen() {
|
|
610
|
+
const trigger = await this.getDownloadMenuTrigger();
|
|
611
|
+
return trigger.getAttribute("aria-expanded") === "true";
|
|
612
|
+
}
|
|
613
|
+
async getMenuItem(name) {
|
|
614
|
+
return screen.getByRole("menuitem", { suggest: false, hidden: true, name });
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Activates the _download all_ item in the DataTable.Toolbar menu.
|
|
618
|
+
*/
|
|
619
|
+
async downloadAllCSV() {
|
|
620
|
+
await this.openDownloadMenu();
|
|
621
|
+
const csvSubMenu = await this.getMenuItem("Download as CSV");
|
|
622
|
+
fireEvent.click(csvSubMenu);
|
|
623
|
+
const downloadAll = await this.getMenuItem("All");
|
|
624
|
+
fireEvent.click(downloadAll);
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Activates the _download current page_ item in the DataTable.Toolbar menu.
|
|
628
|
+
*/
|
|
629
|
+
async downloadPageCSV() {
|
|
630
|
+
await this.openDownloadMenu();
|
|
631
|
+
const csvSubMenu = await this.getMenuItem("Download as CSV");
|
|
632
|
+
fireEvent.click(csvSubMenu);
|
|
633
|
+
const downloadAll = await this.getMenuItem("Current page");
|
|
634
|
+
fireEvent.click(downloadAll);
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Activates the _download selected_ item in the DataTable.Toolbar menu.
|
|
638
|
+
*/
|
|
639
|
+
async downloadSelectedRowsCSV() {
|
|
640
|
+
await this.openDownloadMenu();
|
|
641
|
+
const csvSubMenu = await this.getMenuItem("Download as CSV");
|
|
642
|
+
fireEvent.click(csvSubMenu);
|
|
643
|
+
const downloadAll = await this.getMenuItem(/Selected rows/);
|
|
644
|
+
fireEvent.click(downloadAll);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
class DataTableExpandableRowHelper {
|
|
648
|
+
constructor(rootHelper) {
|
|
649
|
+
this.rootHelper = rootHelper;
|
|
650
|
+
}
|
|
651
|
+
getExpandedRowIds() {
|
|
652
|
+
const expandedRows = this.rootHelper.element.querySelectorAll(
|
|
653
|
+
'[role="row"][data-rowid$="-expanded"]'
|
|
654
|
+
);
|
|
655
|
+
const result = [];
|
|
656
|
+
for (const expandedRow of Array.from(expandedRows)) {
|
|
657
|
+
const { rowid } = expandedRow.dataset;
|
|
658
|
+
if (rowid) {
|
|
659
|
+
result.push(rowid.replace("-expanded", ""));
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return result;
|
|
663
|
+
}
|
|
664
|
+
async expandRowById(rowId) {
|
|
665
|
+
const rowDetailsTrigger = await within(
|
|
666
|
+
this.rootHelper.element
|
|
667
|
+
).findByLabelText(/Expand row with id/, {
|
|
668
|
+
suggest: false,
|
|
669
|
+
exact: false,
|
|
670
|
+
selector: `[data-rowid="${CSS.escape(rowId)}"] button`
|
|
671
|
+
});
|
|
672
|
+
if (rowDetailsTrigger) {
|
|
673
|
+
const expanded = rowDetailsTrigger.getAttribute("aria-expanded");
|
|
674
|
+
if (expanded === "false") {
|
|
675
|
+
const userEvent = setupUserEvent();
|
|
676
|
+
await userEvent.click(rowDetailsTrigger);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
async collapseRowById(rowId) {
|
|
681
|
+
const rowDetailsTrigger = await within(
|
|
682
|
+
this.rootHelper.element
|
|
683
|
+
).findByLabelText(/Collapse row with id/, {
|
|
684
|
+
suggest: false,
|
|
685
|
+
exact: false,
|
|
686
|
+
selector: `[data-rowid="${CSS.escape(rowId)}"] button`
|
|
687
|
+
});
|
|
688
|
+
if (rowDetailsTrigger) {
|
|
689
|
+
const expanded = rowDetailsTrigger.getAttribute("aria-expanded");
|
|
690
|
+
if (expanded === "true") {
|
|
691
|
+
const userEvent = setupUserEvent();
|
|
692
|
+
await userEvent.click(rowDetailsTrigger);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
async toggleRowById(rowId) {
|
|
697
|
+
const rowDetailsTrigger = await within(
|
|
698
|
+
this.rootHelper.element
|
|
699
|
+
).findByLabelText(/(Expand|Collapse) row with id/, {
|
|
700
|
+
suggest: false,
|
|
701
|
+
exact: false,
|
|
702
|
+
selector: `[data-rowid="${CSS.escape(rowId)}"] button`
|
|
703
|
+
});
|
|
704
|
+
if (rowDetailsTrigger) {
|
|
705
|
+
const userEvent = setupUserEvent();
|
|
706
|
+
await userEvent.click(rowDetailsTrigger);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
class DataTableHelper {
|
|
711
|
+
constructor(dataTestId) {
|
|
712
|
+
this.dataTestId = dataTestId;
|
|
713
|
+
this.userActions = new DataTableUserActionHelper(dataTestId);
|
|
714
|
+
this.pagination = new DataTablePaginationHelper(dataTestId);
|
|
715
|
+
this.sorting = new DataTableSortingHelper(dataTestId, this);
|
|
716
|
+
this.rowSelection = new DataTableRowSelectionHelper(this);
|
|
717
|
+
this.subRows = new DataTableSubRowsHelper(this);
|
|
718
|
+
this.interactiveRows = new DataTableInteractiveRowsHelper(this);
|
|
719
|
+
this.download = new DataTableDownloadHelper(this);
|
|
720
|
+
this.expandableRows = new DataTableExpandableRowHelper(this);
|
|
721
|
+
}
|
|
722
|
+
get tableElement() {
|
|
723
|
+
return within(this.element).getByRole("grid", { hidden: true });
|
|
724
|
+
}
|
|
725
|
+
getHeaderRow(index) {
|
|
726
|
+
const headerCells = this.tableElement.querySelectorAll(
|
|
727
|
+
`[role="columnheader"][data-header-row="${index}"]`
|
|
728
|
+
);
|
|
729
|
+
return Array.from(headerCells);
|
|
730
|
+
}
|
|
731
|
+
getAllCellsInBodyRow(index) {
|
|
732
|
+
const rowCells = this.tableElement.querySelectorAll(
|
|
733
|
+
`[data-rowgroup="body"] [data-row="${index}"] [role="cell"]`
|
|
734
|
+
);
|
|
735
|
+
return Array.from(rowCells);
|
|
736
|
+
}
|
|
737
|
+
userActions;
|
|
738
|
+
pagination;
|
|
739
|
+
sorting;
|
|
740
|
+
rowSelection;
|
|
741
|
+
subRows;
|
|
742
|
+
interactiveRows;
|
|
743
|
+
download;
|
|
744
|
+
expandableRows;
|
|
745
|
+
/**
|
|
746
|
+
* Gets the root element of the DataTable
|
|
747
|
+
*/
|
|
748
|
+
get element() {
|
|
749
|
+
return screen.getByTestId(this.dataTestId);
|
|
750
|
+
}
|
|
751
|
+
get headerRowCount() {
|
|
752
|
+
const headerCells = this.tableElement.querySelectorAll(
|
|
753
|
+
'[role="columnheader"]'
|
|
754
|
+
);
|
|
755
|
+
const uniqueRows = /* @__PURE__ */ new Set();
|
|
756
|
+
for (const headerCell of Array.from(headerCells)) {
|
|
757
|
+
const headerRowAttr = headerCell.dataset.headerRow;
|
|
758
|
+
if (headerRowAttr) {
|
|
759
|
+
uniqueRows.add(headerRowAttr);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return uniqueRows.size;
|
|
763
|
+
}
|
|
764
|
+
getHeaderRowContent(index) {
|
|
765
|
+
return this.getHeaderRow(index).map((element) => element.textContent);
|
|
766
|
+
}
|
|
767
|
+
getRowContent(index) {
|
|
768
|
+
return this.getAllCellsInBodyRow(index).map(
|
|
769
|
+
(element) => element.textContent
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
get rowCount() {
|
|
773
|
+
const rows = this.tableElement.querySelectorAll(
|
|
774
|
+
'[data-rowgroup="body"] [role="row"]:not([data-rowid*="expanded"])'
|
|
775
|
+
);
|
|
776
|
+
return rows.length;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
function getDataTableHelper(testid) {
|
|
780
|
+
return new DataTableHelper(testid);
|
|
781
|
+
}
|
|
782
|
+
export {
|
|
783
|
+
DataTableDownloadHelper,
|
|
784
|
+
DataTableExpandableRowHelper,
|
|
785
|
+
DataTableHelper,
|
|
786
|
+
DataTableInteractiveRowsHelper,
|
|
787
|
+
DataTablePaginationHelper,
|
|
788
|
+
DataTableRowSelectionHelper,
|
|
789
|
+
DataTableSortingHelper,
|
|
790
|
+
DataTableSubRowsHelper,
|
|
791
|
+
DataTableUserActionHelper,
|
|
792
|
+
getDataTableHelper
|
|
793
|
+
};
|
|
794
|
+
//# sourceMappingURL=datatable.js.map
|