@rickcedwhat/playwright-smart-table 6.15.0 → 6.17.0
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/dist/engine/rowFinder.d.ts +4 -2
- package/dist/engine/rowFinder.js +22 -49
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/presets/glide/index.d.ts +1 -0
- package/dist/presets/glide/index.js +9 -2
- package/dist/presets/index.d.ts +3 -2
- package/dist/presets/index.js +4 -1
- package/dist/presets/mui.d.ts +23 -7
- package/dist/presets/mui.js +349 -274
- package/dist/presets/rdg.d.ts +31 -7
- package/dist/presets/rdg.js +77 -6
- package/dist/smartRow.js +2 -1
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +25 -0
- package/dist/types.d.ts +21 -0
- package/dist/useTable.js +16 -5
- package/dist/utils/mergeTableConfig.d.ts +22 -0
- package/dist/utils/mergeTableConfig.js +60 -0
- package/package.json +18 -1
package/dist/presets/mui.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.muiDataGrid = exports.muiTable = void 0;
|
|
4
|
+
exports.createMuiTable = createMuiTable;
|
|
5
|
+
exports.createMuiDataGrid = createMuiDataGrid;
|
|
4
6
|
const debugUtils_1 = require("../utils/debugUtils");
|
|
7
|
+
const DEFAULT_LABELS = {
|
|
8
|
+
nextPage: 'Go to next page',
|
|
9
|
+
previousPage: 'Go to previous page',
|
|
10
|
+
firstPage: 'Go to first page',
|
|
11
|
+
};
|
|
5
12
|
/**
|
|
6
13
|
* Safely wait for MUI pagination to stabilize.
|
|
7
14
|
* Polls for displayed row text changes and waits for loading overlays to disappear.
|
|
@@ -45,120 +52,136 @@ async function getPaginationRoot(context) {
|
|
|
45
52
|
return context.root.locator('xpath=following::div[contains(@class, "MuiTablePagination-root")][1]').first();
|
|
46
53
|
}
|
|
47
54
|
/**
|
|
48
|
-
*
|
|
55
|
+
* Creates a preset configuration for the standard Material UI `<Table />` component.
|
|
49
56
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
57
|
+
* @param opts.buttonLabels - Override aria-labels for pagination buttons (use for non-English locales).
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* // French locale
|
|
61
|
+
* const table = useTable(loc, createMuiTable({ buttonLabels: { nextPage: 'Page suivante', previousPage: 'Page précédente' } }));
|
|
55
62
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
await nextBtn.click({ force: true });
|
|
72
|
-
await waitForMuiPaginationStabilization(context, displayedRows, oldText);
|
|
73
|
-
return true;
|
|
74
|
-
},
|
|
75
|
-
goPrevious: async (context) => {
|
|
76
|
-
const root = await getPaginationRoot(context);
|
|
77
|
-
const prevBtn = root.locator('button[aria-label="Go to previous page"]');
|
|
78
|
-
if (await prevBtn.count() === 0 || await prevBtn.isDisabled())
|
|
79
|
-
return false;
|
|
80
|
-
const displayedRows = root.locator('.MuiTablePagination-displayedRows');
|
|
81
|
-
const oldText = await displayedRows.innerText().catch(() => '');
|
|
82
|
-
await prevBtn.click({ force: true });
|
|
83
|
-
await waitForMuiPaginationStabilization(context, displayedRows, oldText);
|
|
84
|
-
return true;
|
|
85
|
-
},
|
|
86
|
-
goToFirst: async (context) => {
|
|
87
|
-
const root = await getPaginationRoot(context);
|
|
88
|
-
const prevBtn = root.locator('button[aria-label="Go to previous page"]');
|
|
89
|
-
const displayedRows = root.locator('.MuiTablePagination-displayedRows');
|
|
90
|
-
let clicked = false;
|
|
91
|
-
let noProgressCount = 0;
|
|
92
|
-
const NO_PROGRESS_LIMIT = 3;
|
|
93
|
-
while (await prevBtn.count() > 0 && !(await prevBtn.isDisabled())) {
|
|
63
|
+
function createMuiTable(opts) {
|
|
64
|
+
const labels = Object.assign(Object.assign({}, DEFAULT_LABELS), opts === null || opts === void 0 ? void 0 : opts.buttonLabels);
|
|
65
|
+
return {
|
|
66
|
+
rowSelector: 'tbody tr, tbody .MuiTableRow-root',
|
|
67
|
+
cellSelector: 'td, th, .MuiTableCell-body',
|
|
68
|
+
headerSelector: 'thead th, .MuiTableCell-head',
|
|
69
|
+
headerTransformer: ({ text }) => text.replace(/\bsorted\s+(?:ascending|descending)\b/i, '').trim(),
|
|
70
|
+
strategies: {
|
|
71
|
+
pagination: {
|
|
72
|
+
goNext: async (context) => {
|
|
73
|
+
const root = await getPaginationRoot(context);
|
|
74
|
+
const nextBtn = root.getByRole('button', { name: labels.nextPage, exact: true });
|
|
75
|
+
if (await nextBtn.count() === 0 || await nextBtn.isDisabled())
|
|
76
|
+
return false;
|
|
77
|
+
const displayedRows = root.locator('.MuiTablePagination-displayedRows');
|
|
94
78
|
const oldText = await displayedRows.innerText().catch(() => '');
|
|
95
|
-
|
|
79
|
+
// Force click ignores cookie banners obscuring the footer
|
|
80
|
+
await nextBtn.click({ force: true });
|
|
96
81
|
await waitForMuiPaginationStabilization(context, displayedRows, oldText);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
82
|
+
return true;
|
|
83
|
+
},
|
|
84
|
+
goPrevious: async (context) => {
|
|
85
|
+
const root = await getPaginationRoot(context);
|
|
86
|
+
const prevBtn = root.getByRole('button', { name: labels.previousPage, exact: true });
|
|
87
|
+
if (await prevBtn.count() === 0 || await prevBtn.isDisabled())
|
|
88
|
+
return false;
|
|
89
|
+
const displayedRows = root.locator('.MuiTablePagination-displayedRows');
|
|
90
|
+
const oldText = await displayedRows.innerText().catch(() => '');
|
|
91
|
+
await prevBtn.click({ force: true });
|
|
92
|
+
await waitForMuiPaginationStabilization(context, displayedRows, oldText);
|
|
93
|
+
return true;
|
|
94
|
+
},
|
|
95
|
+
goToFirst: async (context) => {
|
|
96
|
+
const root = await getPaginationRoot(context);
|
|
97
|
+
const displayedRows = root.locator('.MuiTablePagination-displayedRows');
|
|
98
|
+
// MUI TablePagination optionally renders a "first page" button (showFirstButton prop)
|
|
99
|
+
const firstBtn = root.getByRole('button', { name: labels.firstPage, exact: true });
|
|
100
|
+
if (await firstBtn.count() > 0 && !(await firstBtn.isDisabled())) {
|
|
101
|
+
const oldText = await displayedRows.innerText().catch(() => '');
|
|
102
|
+
await firstBtn.click({ force: true });
|
|
103
|
+
await waitForMuiPaginationStabilization(context, displayedRows, oldText);
|
|
104
|
+
return true;
|
|
105
105
|
}
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
// Fall back to stepping backward one page at a time
|
|
107
|
+
const prevBtn = root.getByRole('button', { name: labels.previousPage, exact: true });
|
|
108
|
+
let clicked = false;
|
|
109
|
+
let noProgressCount = 0;
|
|
110
|
+
const NO_PROGRESS_LIMIT = 3;
|
|
111
|
+
while (await prevBtn.count() > 0 && !(await prevBtn.isDisabled())) {
|
|
112
|
+
const oldText = await displayedRows.innerText().catch(() => '');
|
|
113
|
+
await prevBtn.click({ force: true });
|
|
114
|
+
await waitForMuiPaginationStabilization(context, displayedRows, oldText);
|
|
115
|
+
clicked = true;
|
|
116
|
+
const newText = await displayedRows.innerText().catch(() => '');
|
|
117
|
+
if (newText === oldText) {
|
|
118
|
+
noProgressCount++;
|
|
119
|
+
if (noProgressCount >= NO_PROGRESS_LIMIT) {
|
|
120
|
+
(0, debugUtils_1.logDebug)(context.config, 'error', `goToFirst: no pagination progress after ${NO_PROGRESS_LIMIT} consecutive clicks — aborting to avoid infinite loop`);
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
noProgressCount = 0;
|
|
126
|
+
}
|
|
108
127
|
}
|
|
128
|
+
return clicked;
|
|
109
129
|
}
|
|
110
|
-
return clicked;
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
sorting: {
|
|
114
|
-
getSortState: async ({ columnName, context }) => {
|
|
115
|
-
if (!context.getHeaderCell)
|
|
116
|
-
return 'none';
|
|
117
|
-
const header = await context.getHeaderCell(columnName);
|
|
118
|
-
const sortLabel = header.locator('.MuiTableSortLabel-root').first();
|
|
119
|
-
if (!(await sortLabel.isVisible().catch(() => false)))
|
|
120
|
-
return 'none';
|
|
121
|
-
const className = await sortLabel.getAttribute('class') || '';
|
|
122
|
-
if (className.includes('directionAsc'))
|
|
123
|
-
return 'asc';
|
|
124
|
-
if (className.includes('directionDesc'))
|
|
125
|
-
return 'desc';
|
|
126
|
-
// Often, if the label is active but lacks explicit direction classes, MUI defaults to ascending
|
|
127
|
-
if (className.includes('active'))
|
|
128
|
-
return 'asc';
|
|
129
|
-
return 'none';
|
|
130
130
|
},
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
131
|
+
sorting: {
|
|
132
|
+
getSortState: async ({ columnName, context }) => {
|
|
133
|
+
if (!context.getHeaderCell)
|
|
134
|
+
return 'none';
|
|
135
|
+
const header = await context.getHeaderCell(columnName);
|
|
136
|
+
const sortLabel = header.locator('.MuiTableSortLabel-root').first();
|
|
137
|
+
if (!(await sortLabel.isVisible().catch(() => false)))
|
|
138
|
+
return 'none';
|
|
139
|
+
const className = await sortLabel.getAttribute('class') || '';
|
|
140
|
+
if (className.includes('directionAsc'))
|
|
141
|
+
return 'asc';
|
|
142
|
+
if (className.includes('directionDesc'))
|
|
143
|
+
return 'desc';
|
|
144
|
+
// Often, if the label is active but lacks explicit direction classes, MUI defaults to ascending
|
|
145
|
+
if (className.includes('active'))
|
|
146
|
+
return 'asc';
|
|
147
|
+
return 'none';
|
|
148
|
+
},
|
|
149
|
+
doSort: async ({ columnName, direction, context }) => {
|
|
150
|
+
var _a, _b;
|
|
151
|
+
if (!context.getHeaderCell)
|
|
152
|
+
return;
|
|
153
|
+
const header = await context.getHeaderCell(columnName);
|
|
154
|
+
const sortLabel = header.locator('.MuiTableSortLabel-root').first();
|
|
155
|
+
const target = await sortLabel.isVisible().catch(() => false) ? sortLabel : header;
|
|
156
|
+
let current = await ((_a = context.config.strategies.sorting) === null || _a === void 0 ? void 0 : _a.getSortState({ columnName, context })) || 'none';
|
|
157
|
+
let attempts = 0;
|
|
158
|
+
// Click until the state matches the target direction, max 3 times (none -> asc -> desc)
|
|
159
|
+
while (current !== direction && attempts < 3) {
|
|
160
|
+
await target.click();
|
|
161
|
+
await context.page.waitForTimeout(100);
|
|
162
|
+
current = await ((_b = context.config.strategies.sorting) === null || _b === void 0 ? void 0 : _b.getSortState({ columnName, context })) || 'none';
|
|
163
|
+
attempts++;
|
|
164
|
+
}
|
|
146
165
|
}
|
|
166
|
+
},
|
|
167
|
+
dedupe: async (row) => {
|
|
168
|
+
// MUI virtualized tables attach data-index to rows
|
|
169
|
+
const dataIndex = await row.getAttribute('data-index');
|
|
170
|
+
if (dataIndex !== null)
|
|
171
|
+
return dataIndex;
|
|
172
|
+
// Unvirtualized tables don't recycle nodes, but if they do duplicate, fallback to first cell's text
|
|
173
|
+
const text = await row.locator('td, th').first().innerText().catch(() => '');
|
|
174
|
+
return text || String(Math.random());
|
|
147
175
|
}
|
|
148
|
-
},
|
|
149
|
-
dedupe: async (row) => {
|
|
150
|
-
// MUI virtualized tables attach data-index to rows
|
|
151
|
-
const dataIndex = await row.getAttribute('data-index');
|
|
152
|
-
if (dataIndex !== null)
|
|
153
|
-
return dataIndex;
|
|
154
|
-
// Unvirtualized tables don't recycle nodes, but if they do duplicate, fallback to first cell's text
|
|
155
|
-
const text = await row.locator('td, th').first().innerText().catch(() => '');
|
|
156
|
-
return text || String(Math.random());
|
|
157
176
|
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/** Default preset for MUI `<Table />`. For non-English locales use `createMuiTable({ buttonLabels })`. */
|
|
180
|
+
exports.muiTable = createMuiTable();
|
|
160
181
|
/**
|
|
161
|
-
*
|
|
182
|
+
* Creates a preset configuration for the MUI DataGrid component (@mui/x-data-grid).
|
|
183
|
+
*
|
|
184
|
+
* @param opts.buttonLabels - Override aria-labels for pagination buttons (use for non-English locales).
|
|
162
185
|
*
|
|
163
186
|
* Handles:
|
|
164
187
|
* - Selectors for `.MuiDataGrid-row`, `.MuiDataGrid-cell`, and `.MuiDataGrid-columnHeader`.
|
|
@@ -167,186 +190,238 @@ exports.muiTable = {
|
|
|
167
190
|
* - Vertical Virtualization deduplication using `data-rowindex`.
|
|
168
191
|
* - Horizontal Virtualization support via `aria-colindex`.
|
|
169
192
|
*/
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
193
|
+
function createMuiDataGrid(opts) {
|
|
194
|
+
const labels = Object.assign(Object.assign({}, DEFAULT_LABELS), opts === null || opts === void 0 ? void 0 : opts.buttonLabels);
|
|
195
|
+
return {
|
|
196
|
+
rowSelector: '.MuiDataGrid-row',
|
|
197
|
+
cellSelector: '.MuiDataGrid-cell',
|
|
198
|
+
headerSelector: '.MuiDataGrid-columnHeader',
|
|
199
|
+
strategies: {
|
|
200
|
+
pagination: {
|
|
201
|
+
goNext: async (context) => {
|
|
202
|
+
const footer = context.root.locator('.MuiDataGrid-footerContainer');
|
|
203
|
+
const nextBtn = footer.getByRole('button', { name: labels.nextPage, exact: true });
|
|
204
|
+
try {
|
|
205
|
+
await nextBtn.waitFor({ state: 'visible', timeout: 2000 });
|
|
206
|
+
}
|
|
207
|
+
catch (e) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
if (await nextBtn.isDisabled())
|
|
211
|
+
return false;
|
|
212
|
+
const displayedRows = footer.locator('.MuiTablePagination-displayedRows');
|
|
213
|
+
const oldText = await displayedRows.innerText().catch(() => '');
|
|
214
|
+
await nextBtn.scrollIntoViewIfNeeded().catch(() => { });
|
|
215
|
+
await nextBtn.click({ force: true });
|
|
216
|
+
await waitForMuiPaginationStabilization(context, displayedRows, oldText);
|
|
217
|
+
return true;
|
|
218
|
+
},
|
|
219
|
+
goPrevious: async (context) => {
|
|
220
|
+
const footer = context.root.locator('.MuiDataGrid-footerContainer');
|
|
221
|
+
const prevBtn = footer.getByRole('button', { name: labels.previousPage, exact: true });
|
|
222
|
+
try {
|
|
223
|
+
await prevBtn.waitFor({ state: 'visible', timeout: 2000 });
|
|
224
|
+
}
|
|
225
|
+
catch (e) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
if (await prevBtn.isDisabled())
|
|
229
|
+
return false;
|
|
230
|
+
const displayedRows = footer.locator('.MuiTablePagination-displayedRows');
|
|
231
|
+
const oldText = await displayedRows.innerText().catch(() => '');
|
|
232
|
+
await prevBtn.scrollIntoViewIfNeeded().catch(() => { });
|
|
233
|
+
await prevBtn.click({ force: true });
|
|
234
|
+
await waitForMuiPaginationStabilization(context, displayedRows, oldText);
|
|
235
|
+
return true;
|
|
236
|
+
},
|
|
237
|
+
goToFirst: async (context) => {
|
|
238
|
+
const footer = context.root.locator('.MuiDataGrid-footerContainer');
|
|
239
|
+
const displayedRows = footer.locator('.MuiTablePagination-displayedRows');
|
|
240
|
+
const firstBtn = footer.getByRole('button', { name: labels.firstPage, exact: true });
|
|
241
|
+
try {
|
|
242
|
+
await firstBtn.waitFor({ state: 'visible', timeout: 1000 });
|
|
243
|
+
if (!(await firstBtn.isDisabled())) {
|
|
244
|
+
const oldText = await displayedRows.innerText().catch(() => '');
|
|
245
|
+
await firstBtn.scrollIntoViewIfNeeded().catch(() => { });
|
|
246
|
+
await firstBtn.click({ force: true });
|
|
247
|
+
await waitForMuiPaginationStabilization(context, displayedRows, oldText);
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
catch (_a) {
|
|
252
|
+
// first-page button not present; fall back to stepping backward
|
|
253
|
+
}
|
|
254
|
+
const prevBtn = footer.getByRole('button', { name: labels.previousPage, exact: true });
|
|
255
|
+
let clicked = false;
|
|
256
|
+
let noProgressCount = 0;
|
|
257
|
+
const NO_PROGRESS_LIMIT = 3;
|
|
258
|
+
while (true) {
|
|
259
|
+
try {
|
|
260
|
+
await prevBtn.waitFor({ state: 'visible', timeout: 2000 });
|
|
261
|
+
}
|
|
262
|
+
catch (_b) {
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
if (await prevBtn.isDisabled())
|
|
266
|
+
break;
|
|
267
|
+
const oldText = await displayedRows.innerText().catch(() => '');
|
|
268
|
+
await prevBtn.scrollIntoViewIfNeeded().catch(() => { });
|
|
269
|
+
await prevBtn.click({ force: true });
|
|
270
|
+
await waitForMuiPaginationStabilization(context, displayedRows, oldText);
|
|
271
|
+
clicked = true;
|
|
272
|
+
const newText = await displayedRows.innerText().catch(() => '');
|
|
273
|
+
if (newText === oldText) {
|
|
274
|
+
noProgressCount++;
|
|
275
|
+
if (noProgressCount >= NO_PROGRESS_LIMIT) {
|
|
276
|
+
(0, debugUtils_1.logDebug)(context.config, 'error', `goToFirst: no pagination progress after ${NO_PROGRESS_LIMIT} consecutive clicks — aborting to avoid infinite loop`);
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
noProgressCount = 0;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return clicked;
|
|
184
285
|
}
|
|
185
|
-
if (await nextBtn.isDisabled())
|
|
186
|
-
return false;
|
|
187
|
-
const displayedRows = footer.locator('.MuiTablePagination-displayedRows');
|
|
188
|
-
const oldText = await displayedRows.innerText().catch(() => '');
|
|
189
|
-
await nextBtn.scrollIntoViewIfNeeded().catch(() => { });
|
|
190
|
-
await nextBtn.click({ force: true });
|
|
191
|
-
await waitForMuiPaginationStabilization(context, displayedRows, oldText);
|
|
192
|
-
return true;
|
|
193
286
|
},
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
await
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
return false;
|
|
205
|
-
const displayedRows = footer.locator('.MuiTablePagination-displayedRows');
|
|
206
|
-
const oldText = await displayedRows.innerText().catch(() => '');
|
|
207
|
-
await prevBtn.scrollIntoViewIfNeeded().catch(() => { });
|
|
208
|
-
await prevBtn.click({ force: true });
|
|
209
|
-
await waitForMuiPaginationStabilization(context, displayedRows, oldText);
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
sorting: {
|
|
214
|
-
getSortState: async ({ columnName, context }) => {
|
|
215
|
-
if (!context.getHeaderCell)
|
|
287
|
+
sorting: {
|
|
288
|
+
getSortState: async ({ columnName, context }) => {
|
|
289
|
+
if (!context.getHeaderCell)
|
|
290
|
+
return 'none';
|
|
291
|
+
const header = await context.getHeaderCell(columnName);
|
|
292
|
+
const sortAttr = await header.getAttribute('aria-sort');
|
|
293
|
+
if (sortAttr === 'ascending')
|
|
294
|
+
return 'asc';
|
|
295
|
+
if (sortAttr === 'descending')
|
|
296
|
+
return 'desc';
|
|
216
297
|
return 'none';
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
await
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
298
|
+
},
|
|
299
|
+
doSort: async ({ columnName, direction, context }) => {
|
|
300
|
+
var _a, _b;
|
|
301
|
+
if (!context.getHeaderCell)
|
|
302
|
+
return;
|
|
303
|
+
const header = await context.getHeaderCell(columnName);
|
|
304
|
+
let current = await ((_a = context.config.strategies.sorting) === null || _a === void 0 ? void 0 : _a.getSortState({ columnName, context })) || 'none';
|
|
305
|
+
let attempts = 0;
|
|
306
|
+
while (current !== direction && attempts < 3) {
|
|
307
|
+
const clickTarget = header.locator('.MuiDataGrid-columnHeaderTitleContainer').first();
|
|
308
|
+
if (await clickTarget.isVisible()) {
|
|
309
|
+
await clickTarget.click({ force: true });
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
await header.click({ force: true });
|
|
313
|
+
}
|
|
314
|
+
// Wait for stabilization (using DataGrid specific overlay check inside helper)
|
|
315
|
+
const displayedRows = context.root.locator('.MuiTablePagination-displayedRows');
|
|
316
|
+
const text = await displayedRows.innerText().catch(() => '');
|
|
317
|
+
await waitForMuiPaginationStabilization(context, displayedRows, text);
|
|
318
|
+
current = await ((_b = context.config.strategies.sorting) === null || _b === void 0 ? void 0 : _b.getSortState({ columnName, context })) || 'none';
|
|
319
|
+
attempts++;
|
|
239
320
|
}
|
|
240
|
-
// Sorting can trigger large re-renders/virtualization shifts
|
|
241
|
-
await context.page.waitForTimeout(500);
|
|
242
|
-
// Wait for stabilization (using DataGrid specific overlay check inside helper)
|
|
243
|
-
const displayedRows = context.root.locator('.MuiTablePagination-displayedRows');
|
|
244
|
-
const text = await displayedRows.innerText().catch(() => '');
|
|
245
|
-
await waitForMuiPaginationStabilization(context, displayedRows, text);
|
|
246
|
-
current = await ((_b = context.config.strategies.sorting) === null || _b === void 0 ? void 0 : _b.getSortState({ columnName, context })) || 'none';
|
|
247
|
-
attempts++;
|
|
248
321
|
}
|
|
249
|
-
}
|
|
250
|
-
},
|
|
251
|
-
dedupe: async (row) => {
|
|
252
|
-
var _a;
|
|
253
|
-
// DataGrid uses data-rowindex for its own virtualization management
|
|
254
|
-
return (_a = (await row.getAttribute('data-rowindex').catch(() => null))) !== null && _a !== void 0 ? _a : '';
|
|
255
|
-
},
|
|
256
|
-
getCellLocator: ({ row, columnIndex }) => {
|
|
257
|
-
// Horizontal virtualization uses aria-colindex (1-indexed)
|
|
258
|
-
return row.locator(`[aria-colindex="${columnIndex + 1}"]`);
|
|
259
|
-
},
|
|
260
|
-
// MUI DataGrid virtualizes rows vertically (always) and columns horizontally (when
|
|
261
|
-
// columnBuffer is configured). The virtualScroller is a descendant of root, so
|
|
262
|
-
// scroll methods use querySelector rather than closest().
|
|
263
|
-
viewport: {
|
|
264
|
-
getVisibleRowRange: async ({ root, config }) => {
|
|
265
|
-
const rowSel = config.rowSelector;
|
|
266
|
-
return root.evaluate((el, rowSel) => {
|
|
267
|
-
const indices = Array.from(el.querySelectorAll(rowSel))
|
|
268
|
-
.map(r => Number(r.getAttribute('data-rowindex')))
|
|
269
|
-
.filter(n => !isNaN(n));
|
|
270
|
-
if (!indices.length)
|
|
271
|
-
return { first: 0, last: 0 };
|
|
272
|
-
return { first: Math.min(...indices), last: Math.max(...indices) };
|
|
273
|
-
}, rowSel);
|
|
274
322
|
},
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
return
|
|
279
|
-
const firstRow = el.querySelector(rowSel);
|
|
280
|
-
if (!firstRow)
|
|
281
|
-
return { first: 0, last: 0 };
|
|
282
|
-
const indices = Array.from(firstRow.querySelectorAll(cellSel))
|
|
283
|
-
.map(c => Number(c.getAttribute('aria-colindex')) - 1)
|
|
284
|
-
.filter(n => !isNaN(n));
|
|
285
|
-
if (!indices.length)
|
|
286
|
-
return { first: 0, last: 0 };
|
|
287
|
-
return { first: Math.min(...indices), last: Math.max(...indices) };
|
|
288
|
-
}, { rowSel, cellSel });
|
|
323
|
+
dedupe: async (row) => {
|
|
324
|
+
var _a;
|
|
325
|
+
// DataGrid uses data-rowindex for its own virtualization management
|
|
326
|
+
return (_a = (await row.getAttribute('data-rowindex').catch(() => null))) !== null && _a !== void 0 ? _a : '';
|
|
289
327
|
},
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const scroller = el.querySelector('.MuiDataGrid-virtualScroller');
|
|
294
|
-
if (!scroller)
|
|
295
|
-
return;
|
|
296
|
-
const row = el.querySelector(`${rowSel}[data-rowindex="${idx}"]`);
|
|
297
|
-
if (row) {
|
|
298
|
-
row.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
const visibleRows = Array.from(el.querySelectorAll(rowSel));
|
|
302
|
-
const heights = visibleRows
|
|
303
|
-
.map(visibleRow => visibleRow.getBoundingClientRect().height)
|
|
304
|
-
.filter(height => height > 0);
|
|
305
|
-
const estimatedHeight = heights.length
|
|
306
|
-
? heights.reduce((sum, height) => sum + height, 0) / heights.length
|
|
307
|
-
: 52;
|
|
308
|
-
scroller.scrollTop = Math.max(0, idx * estimatedHeight - 20);
|
|
309
|
-
}, { rowSel, idx: rowIndex });
|
|
310
|
-
await root.locator(`${rowSel}[data-rowindex="${rowIndex}"]`)
|
|
311
|
-
.waitFor({ state: 'attached', timeout: 3000 });
|
|
328
|
+
getCellLocator: ({ row, columnIndex }) => {
|
|
329
|
+
// Horizontal virtualization uses aria-colindex (1-indexed)
|
|
330
|
+
return row.locator(`[aria-colindex="${columnIndex + 1}"]`);
|
|
312
331
|
},
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
332
|
+
// MUI DataGrid virtualizes rows vertically (always) and columns horizontally (when
|
|
333
|
+
// columnBuffer is configured). The virtualScroller is a descendant of root, so
|
|
334
|
+
// scroll methods use querySelector rather than closest().
|
|
335
|
+
viewport: {
|
|
336
|
+
getVisibleRowRange: async ({ root, config }) => {
|
|
337
|
+
const rowSel = config.rowSelector;
|
|
338
|
+
return root.evaluate((el, rowSel) => {
|
|
339
|
+
const indices = Array.from(el.querySelectorAll(rowSel))
|
|
340
|
+
.map(r => Number(r.getAttribute('data-rowindex')))
|
|
341
|
+
.filter(n => !isNaN(n));
|
|
342
|
+
if (!indices.length)
|
|
343
|
+
return { first: 0, last: 0 };
|
|
344
|
+
return { first: Math.min(...indices), last: Math.max(...indices) };
|
|
345
|
+
}, rowSel);
|
|
346
|
+
},
|
|
347
|
+
getVisibleColumnRange: async ({ root, config }) => {
|
|
348
|
+
const rowSel = config.rowSelector;
|
|
349
|
+
const cellSel = typeof config.cellSelector === 'string' ? config.cellSelector : '[aria-colindex]';
|
|
350
|
+
return root.evaluate((el, { rowSel, cellSel }) => {
|
|
351
|
+
const firstRow = el.querySelector(rowSel);
|
|
352
|
+
if (!firstRow)
|
|
353
|
+
return { first: 0, last: 0 };
|
|
354
|
+
const indices = Array.from(firstRow.querySelectorAll(cellSel))
|
|
355
|
+
.map(c => Number(c.getAttribute('aria-colindex')) - 1)
|
|
356
|
+
.filter(n => !isNaN(n));
|
|
357
|
+
if (!indices.length)
|
|
358
|
+
return { first: 0, last: 0 };
|
|
359
|
+
return { first: Math.min(...indices), last: Math.max(...indices) };
|
|
360
|
+
}, { rowSel, cellSel });
|
|
361
|
+
},
|
|
362
|
+
scrollToRow: async ({ root, config }, rowIndex) => {
|
|
363
|
+
const rowSel = config.rowSelector;
|
|
364
|
+
await root.evaluate((el, { rowSel, idx }) => {
|
|
365
|
+
const scroller = el.querySelector('.MuiDataGrid-virtualScroller');
|
|
366
|
+
if (!scroller)
|
|
367
|
+
return;
|
|
368
|
+
const row = el.querySelector(`${rowSel}[data-rowindex="${idx}"]`);
|
|
369
|
+
if (row) {
|
|
370
|
+
row.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const visibleRows = Array.from(el.querySelectorAll(rowSel));
|
|
374
|
+
const heights = visibleRows
|
|
375
|
+
.map(visibleRow => visibleRow.getBoundingClientRect().height)
|
|
376
|
+
.filter(height => height > 0);
|
|
377
|
+
const estimatedHeight = heights.length
|
|
378
|
+
? heights.reduce((sum, height) => sum + height, 0) / heights.length
|
|
379
|
+
: 52;
|
|
380
|
+
scroller.scrollTop = Math.max(0, idx * estimatedHeight - 20);
|
|
381
|
+
}, { rowSel, idx: rowIndex });
|
|
382
|
+
await root.locator(`${rowSel}[data-rowindex="${rowIndex}"]`)
|
|
383
|
+
.waitFor({ state: 'attached', timeout: 3000 });
|
|
384
|
+
},
|
|
385
|
+
scrollToColumn: async ({ root, config }, colIndex) => {
|
|
386
|
+
const headerSel = typeof config.headerSelector === 'string' ? config.headerSelector : null;
|
|
387
|
+
await root.evaluate((el, { headerSel, idx }) => {
|
|
388
|
+
// virtualScroller is a descendant — querySelector, not closest
|
|
389
|
+
const scroller = el.querySelector('.MuiDataGrid-virtualScroller');
|
|
390
|
+
if (!scroller || !headerSel)
|
|
391
|
+
return;
|
|
392
|
+
const headers = Array.from(el.querySelectorAll(headerSel));
|
|
393
|
+
const target = headers[idx];
|
|
394
|
+
if (!target) {
|
|
395
|
+
const widths = headers
|
|
396
|
+
.map(header => header.getBoundingClientRect().width)
|
|
397
|
+
.filter(width => width > 0);
|
|
398
|
+
const estimatedWidth = widths.length
|
|
399
|
+
? widths.reduce((sum, width) => sum + width, 0) / widths.length
|
|
400
|
+
: 100;
|
|
401
|
+
scroller.scrollLeft = Math.max(0, idx * estimatedWidth - 20);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const cRect = scroller.getBoundingClientRect();
|
|
405
|
+
const tRect = target.getBoundingClientRect();
|
|
406
|
+
if (tRect.left < cRect.left) {
|
|
407
|
+
scroller.scrollLeft -= (cRect.left - tRect.left) + 20;
|
|
408
|
+
}
|
|
409
|
+
else if (tRect.right > cRect.right) {
|
|
410
|
+
scroller.scrollLeft += (tRect.right - cRect.right) + 20;
|
|
411
|
+
}
|
|
412
|
+
}, { headerSel, idx: colIndex });
|
|
413
|
+
await root.locator(`${config.rowSelector} [aria-colindex="${colIndex + 1}"]`)
|
|
414
|
+
.first()
|
|
415
|
+
.waitFor({ state: 'attached', timeout: 3000 });
|
|
416
|
+
},
|
|
344
417
|
},
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
418
|
+
loading: {
|
|
419
|
+
isTableLoading: async (context) => {
|
|
420
|
+
return await context.root.locator('.MuiDataGrid-overlay').isVisible().catch(() => false);
|
|
421
|
+
}
|
|
349
422
|
}
|
|
350
423
|
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
/** Default preset for MUI DataGrid. For non-English locales use `createMuiDataGrid({ buttonLabels })`. */
|
|
427
|
+
exports.muiDataGrid = createMuiDataGrid();
|