@rickcedwhat/playwright-smart-table 6.17.1 → 6.19.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 +10 -0
- package/dist/engine/rowFinder.js +40 -61
- package/dist/engine/rowResolution.d.ts +28 -0
- package/dist/engine/rowResolution.js +73 -0
- package/dist/engine/tableIteration.d.ts +3 -1
- package/dist/engine/tableIteration.js +53 -13
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/plugins/index.d.ts +3 -0
- package/dist/presets/mui.js +25 -0
- package/dist/smartRow.js +172 -11
- package/dist/strategies/fill.js +5 -1
- package/dist/strategies/viewport.js +46 -3
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +117 -13
- package/dist/types.d.ts +110 -11
- package/dist/useTable.js +117 -8
- package/package.json +16 -6
package/dist/useTable.js
CHANGED
|
@@ -27,6 +27,7 @@ const filterEngine_1 = require("./filterEngine");
|
|
|
27
27
|
const tableMapper_1 = require("./engine/tableMapper");
|
|
28
28
|
const rowFinder_1 = require("./engine/rowFinder");
|
|
29
29
|
const tableIteration_1 = require("./engine/tableIteration");
|
|
30
|
+
const rowResolution_1 = require("./engine/rowResolution");
|
|
30
31
|
const debugUtils_1 = require("./utils/debugUtils");
|
|
31
32
|
const smartRowArray_1 = require("./utils/smartRowArray");
|
|
32
33
|
const elementTracker_1 = require("./utils/elementTracker");
|
|
@@ -69,9 +70,11 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
69
70
|
const vp = config.strategies.viewport;
|
|
70
71
|
let colRangeCache = null;
|
|
71
72
|
let rowRangeCache = null;
|
|
73
|
+
let rowIndicesCache = null;
|
|
72
74
|
_clearViewportCache = () => {
|
|
73
75
|
colRangeCache = null;
|
|
74
76
|
rowRangeCache = null;
|
|
77
|
+
rowIndicesCache = null;
|
|
75
78
|
};
|
|
76
79
|
config.strategies.viewport = Object.assign(Object.assign({}, vp), { getVisibleColumnRange: vp.getVisibleColumnRange
|
|
77
80
|
? async (ctx) => {
|
|
@@ -85,15 +88,23 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
85
88
|
rowRangeCache = await vp.getVisibleRowRange(ctx);
|
|
86
89
|
return rowRangeCache;
|
|
87
90
|
}
|
|
91
|
+
: undefined, getVisibleRowIndices: vp.getVisibleRowIndices
|
|
92
|
+
? async (ctx) => {
|
|
93
|
+
if (!rowIndicesCache)
|
|
94
|
+
rowIndicesCache = await vp.getVisibleRowIndices(ctx);
|
|
95
|
+
return rowIndicesCache;
|
|
96
|
+
}
|
|
88
97
|
: undefined, scrollToColumn: vp.scrollToColumn
|
|
89
98
|
? async (ctx, colIndex) => {
|
|
90
99
|
colRangeCache = null;
|
|
91
100
|
rowRangeCache = null;
|
|
101
|
+
rowIndicesCache = null;
|
|
92
102
|
await vp.scrollToColumn(ctx, colIndex);
|
|
93
103
|
}
|
|
94
104
|
: undefined, scrollToRow: vp.scrollToRow
|
|
95
105
|
? async (ctx, rowIndex) => {
|
|
96
106
|
rowRangeCache = null;
|
|
107
|
+
rowIndicesCache = null;
|
|
97
108
|
await vp.scrollToRow(ctx, rowIndex);
|
|
98
109
|
}
|
|
99
110
|
: undefined });
|
|
@@ -137,7 +148,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
137
148
|
const _makeSmart = (rowLocator, map, rowIndex, tablePageIndex, barrier) => {
|
|
138
149
|
return (0, smartRow_1.default)(rowLocator, map, rowIndex, config, rootLocator, resolve, finalTable, tablePageIndex, barrier);
|
|
139
150
|
};
|
|
140
|
-
const tableState = { currentPageIndex: 0 };
|
|
151
|
+
const tableState = { currentPageIndex: 0, empty: false };
|
|
141
152
|
const rowFinder = new rowFinder_1.RowFinder(rootLocator, config, resolve, filterEngine, tableMapper, _makeSmart, tableState, (useBulk) => _advancePage(useBulk));
|
|
142
153
|
/** Builds a full TableContext/StrategyContext with getHeaderCell, getHeaders, scrollToColumn. Set after result is created. */
|
|
143
154
|
let createStrategyContext = () => ({ root: rootLocator, config, page: rootLocator.page(), resolve });
|
|
@@ -215,7 +226,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
215
226
|
set currentPageIndex(v) { tableState.currentPageIndex = v; },
|
|
216
227
|
init: async (options) => {
|
|
217
228
|
var _a;
|
|
218
|
-
if (tableMapper.isInitialized())
|
|
229
|
+
if (tableMapper.isInitialized() || tableState.empty)
|
|
219
230
|
return result;
|
|
220
231
|
if (config.strategies.sorting)
|
|
221
232
|
(0, validation_1.validateSortingStrategy)(config.strategies.sorting);
|
|
@@ -223,7 +234,21 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
223
234
|
(0, validation_1.validateFillStrategy)(config.strategies.fill);
|
|
224
235
|
(0, debugUtils_1.warnIfDebugInCI)(config);
|
|
225
236
|
(0, debugUtils_1.logDebug)(config, 'info', 'Initializing table');
|
|
226
|
-
|
|
237
|
+
let map;
|
|
238
|
+
try {
|
|
239
|
+
map = await tableMapper.getMap(options === null || options === void 0 ? void 0 : options.timeout);
|
|
240
|
+
}
|
|
241
|
+
catch (headerError) {
|
|
242
|
+
if (config.emptyState) {
|
|
243
|
+
const visible = await config.emptyState.isVisible().catch(() => false);
|
|
244
|
+
if (visible) {
|
|
245
|
+
tableState.empty = true;
|
|
246
|
+
(0, debugUtils_1.logDebug)(config, 'info', 'init: header resolution failed but emptyState locator is visible — table is empty');
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
throw headerError;
|
|
251
|
+
}
|
|
227
252
|
(0, debugUtils_1.logDebug)(config, 'info', `Table initialized with ${map.size} columns`, Array.from(map.keys()));
|
|
228
253
|
if ((_a = config.strategies.pagination) === null || _a === void 0 ? void 0 : _a.detectCurrentPage) {
|
|
229
254
|
try {
|
|
@@ -269,6 +294,8 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
269
294
|
return resolve(config.headerSelector, rootLocator).nth(idx);
|
|
270
295
|
},
|
|
271
296
|
countRows: async () => {
|
|
297
|
+
if (tableState.empty)
|
|
298
|
+
return 0;
|
|
272
299
|
await _autoInit();
|
|
273
300
|
const pag = config.strategies.pagination;
|
|
274
301
|
const hasPagination = config.maxPages > 1 && !!((pag === null || pag === void 0 ? void 0 : pag.goNext) || (pag === null || pag === void 0 ? void 0 : pag.goNextBulk));
|
|
@@ -280,6 +307,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
280
307
|
const tracker = new elementTracker_1.ElementTracker('countRows');
|
|
281
308
|
let total = 0;
|
|
282
309
|
let pagesScanned = 1;
|
|
310
|
+
let paginationError;
|
|
283
311
|
try {
|
|
284
312
|
while (true) {
|
|
285
313
|
const newIndices = await tracker.getUnseenIndices(resolve(config.rowSelector, rootLocator));
|
|
@@ -292,11 +320,25 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
292
320
|
pagesScanned++;
|
|
293
321
|
}
|
|
294
322
|
}
|
|
323
|
+
catch (e) {
|
|
324
|
+
paginationError = e;
|
|
325
|
+
throw e;
|
|
326
|
+
}
|
|
295
327
|
finally {
|
|
296
328
|
await tracker.cleanup(rootLocator.page());
|
|
329
|
+
// Always restore page 1, even if pagination threw mid-count (#348), so the table
|
|
330
|
+
// stays usable for callers that catch the error. If reset() itself fails, only let
|
|
331
|
+
// it surface when pagination succeeded — otherwise preserve the original error.
|
|
332
|
+
log(`countRows: ${total} total row(s) across ${pagesScanned} page(s) — resetting`);
|
|
333
|
+
try {
|
|
334
|
+
await result.reset();
|
|
335
|
+
}
|
|
336
|
+
catch (resetError) {
|
|
337
|
+
if (paginationError === undefined)
|
|
338
|
+
throw resetError;
|
|
339
|
+
(0, debugUtils_1.logDebug)(config, 'error', 'countRows: reset() failed after pagination error', resetError);
|
|
340
|
+
}
|
|
297
341
|
}
|
|
298
|
-
log(`countRows: ${total} total row(s) across ${pagesScanned} page(s) — resetting`);
|
|
299
|
-
await result.reset();
|
|
300
342
|
return total;
|
|
301
343
|
},
|
|
302
344
|
mapColumn: async (columnName, options = {}) => {
|
|
@@ -311,7 +353,10 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
311
353
|
await cell.bringIntoView();
|
|
312
354
|
const columnOverride = (_a = config.columnOverrides) === null || _a === void 0 ? void 0 : _a[columnName];
|
|
313
355
|
if (columnOverride === null || columnOverride === void 0 ? void 0 : columnOverride.read) {
|
|
314
|
-
return await columnOverride.read(cell
|
|
356
|
+
return await columnOverride.read(cell, {
|
|
357
|
+
row, columnName, columnIndex: map.get(columnName),
|
|
358
|
+
getCell: (name) => row.getCell(name),
|
|
359
|
+
});
|
|
315
360
|
}
|
|
316
361
|
const text = await cell.innerText();
|
|
317
362
|
return (text || '').trim();
|
|
@@ -334,6 +379,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
334
379
|
log("No goToFirst strategy configured. Table may not be on page 1.");
|
|
335
380
|
}
|
|
336
381
|
tableState.currentPageIndex = 0;
|
|
382
|
+
tableState.empty = false;
|
|
337
383
|
tableMapper.clear();
|
|
338
384
|
log("Table reset complete. Calling autoInit to restore state.");
|
|
339
385
|
await _autoInit();
|
|
@@ -341,6 +387,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
341
387
|
revalidate: async () => {
|
|
342
388
|
log("Revalidating table structure...");
|
|
343
389
|
await tableMapper.remapHeaders();
|
|
390
|
+
tableState.empty = false;
|
|
344
391
|
log("Table revalidated.");
|
|
345
392
|
},
|
|
346
393
|
getRow: (filters, options = { exact: false }) => {
|
|
@@ -360,6 +407,55 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
360
407
|
const rowLocator = resolve(config.rowSelector, rootLocator).nth(index);
|
|
361
408
|
return _makeSmart(rowLocator, map, index);
|
|
362
409
|
},
|
|
410
|
+
findRowByIndex: async (index, options) => {
|
|
411
|
+
var _a, _b;
|
|
412
|
+
log(`findRowByIndex: index=${index} options=${safeStringify(options)}`);
|
|
413
|
+
await _autoInit();
|
|
414
|
+
const map = tableMapper.getMapSync();
|
|
415
|
+
const resolveRI = config.strategies.resolveRowIndex;
|
|
416
|
+
const viewport = config.strategies.viewport;
|
|
417
|
+
// Identifying a row by its logical index requires a resolveRowIndex strategy. Without one
|
|
418
|
+
// the library can only offer render-window-relative getRowByIndex, or filter-based findRow.
|
|
419
|
+
if (!resolveRI) {
|
|
420
|
+
throw new Error(`findRowByIndex(${index}) requires a strategies.resolveRowIndex to identify rows by their logical index. ` +
|
|
421
|
+
`Configure one (e.g. read the grid's row-index attribute), or use getRowByIndex() (current render window) / findRow(filters).`);
|
|
422
|
+
}
|
|
423
|
+
// The currently-mounted row whose logical index === index, if any.
|
|
424
|
+
const findMounted = async () => {
|
|
425
|
+
const rows = await resolve(config.rowSelector, rootLocator).all();
|
|
426
|
+
for (const r of rows) {
|
|
427
|
+
if ((await resolveRI(r)) === index)
|
|
428
|
+
return r;
|
|
429
|
+
}
|
|
430
|
+
return null;
|
|
431
|
+
};
|
|
432
|
+
// 1. Already mounted (no navigation needed)?
|
|
433
|
+
let loc = await findMounted();
|
|
434
|
+
// 2. Fast path: jump straight to it via the viewport's random-access scroll.
|
|
435
|
+
if (!loc && (viewport === null || viewport === void 0 ? void 0 : viewport.scrollToRow)) {
|
|
436
|
+
log(`findRowByIndex: scrolling to row ${index}`);
|
|
437
|
+
await viewport.scrollToRow(createStrategyContext(), index);
|
|
438
|
+
loc = await findMounted();
|
|
439
|
+
}
|
|
440
|
+
// 3. Fallback: advance pages (on infinite-scroll tables, a "page" is a scroll step) until
|
|
441
|
+
// the row is reached. Bounded by maxPages so an unreachable index can't loop forever.
|
|
442
|
+
if (!loc && config.strategies.pagination) {
|
|
443
|
+
const effectiveMaxPages = (_a = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages;
|
|
444
|
+
let pagesScanned = 1;
|
|
445
|
+
while (!loc && pagesScanned < effectiveMaxPages) {
|
|
446
|
+
if (!await _advancePage(false))
|
|
447
|
+
break;
|
|
448
|
+
pagesScanned++;
|
|
449
|
+
loc = await findMounted();
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (!loc) {
|
|
453
|
+
throw new Error(`findRowByIndex(${index}): could not reach row ${index}. ` +
|
|
454
|
+
`Ensure a viewport.scrollToRow or pagination can bring it into view (searched up to maxPages=${(_b = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _b !== void 0 ? _b : config.maxPages}).`);
|
|
455
|
+
}
|
|
456
|
+
await (0, debugUtils_1.debugDelay)(config, 'findRow');
|
|
457
|
+
return _makeSmart(loc, map, index, tableState.currentPageIndex);
|
|
458
|
+
},
|
|
363
459
|
findRow: async (filters, options) => {
|
|
364
460
|
log(`findRow: filters=${safeStringify(filters)} options=${safeStringify(options)}`);
|
|
365
461
|
return rowFinder.findRow(filters, options);
|
|
@@ -369,10 +465,13 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
369
465
|
return rowFinder.findRows(filters !== null && filters !== void 0 ? filters : {}, options);
|
|
370
466
|
},
|
|
371
467
|
isInitialized: () => {
|
|
372
|
-
const initialized = tableMapper.isInitialized();
|
|
468
|
+
const initialized = tableMapper.isInitialized() || tableState.empty;
|
|
373
469
|
log(`isInitialized: ${initialized}`);
|
|
374
470
|
return initialized;
|
|
375
471
|
},
|
|
472
|
+
isEmpty: () => {
|
|
473
|
+
return tableState.empty;
|
|
474
|
+
},
|
|
376
475
|
sorting: {
|
|
377
476
|
apply: async (columnName, direction) => {
|
|
378
477
|
var _a;
|
|
@@ -419,6 +518,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
419
518
|
// ─── Shared async row iterator ───────────────────────────────────────────
|
|
420
519
|
[Symbol.asyncIterator]() {
|
|
421
520
|
return __asyncGenerator(this, arguments, function* _a() {
|
|
521
|
+
var _b;
|
|
422
522
|
yield __await(_autoInit());
|
|
423
523
|
const map = tableMapper.getMapSync();
|
|
424
524
|
const effectiveMaxPages = config.maxPages;
|
|
@@ -434,7 +534,13 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
434
534
|
const pageRows = yield __await(rowLocators.all());
|
|
435
535
|
const barrier = new navigationBarrier_1.NavigationBarrier(newIndices.length);
|
|
436
536
|
for (const idx of newIndices) {
|
|
437
|
-
|
|
537
|
+
// index = visit-order counter; rowIndex = logical/data index (B-hybrid, #362).
|
|
538
|
+
const logical = (_b = yield __await((0, rowResolution_1.resolveLogicalRowIndex)(pageRows[idx], config, () => rowIndex))) !== null && _b !== void 0 ? _b : rowIndex;
|
|
539
|
+
const sr = _makeSmart(pageRows[idx], map, logical, tableState.currentPageIndex, barrier);
|
|
540
|
+
// Iterator rows share one per-page DOM snapshot (positional locators); disable
|
|
541
|
+
// toJSON's scroll-back recovery so reading one row can't invalidate the others (#366).
|
|
542
|
+
sr._inBatch = true;
|
|
543
|
+
yield yield __await({ row: sr, index: rowIndex, rowIndex: logical, pageIndex: tableState.currentPageIndex });
|
|
438
544
|
rowIndex++;
|
|
439
545
|
}
|
|
440
546
|
if (pagesScanned >= effectiveMaxPages)
|
|
@@ -462,6 +568,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
462
568
|
config,
|
|
463
569
|
getPage: () => rootLocator.page(),
|
|
464
570
|
getCurrentPageIndex: () => tableState.currentPageIndex,
|
|
571
|
+
getContext: () => createStrategyContext(),
|
|
465
572
|
}, callback, options);
|
|
466
573
|
},
|
|
467
574
|
map: async (callback, options = {}) => {
|
|
@@ -476,6 +583,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
476
583
|
config,
|
|
477
584
|
getPage: () => rootLocator.page(),
|
|
478
585
|
getCurrentPageIndex: () => tableState.currentPageIndex,
|
|
586
|
+
getContext: () => createStrategyContext(),
|
|
479
587
|
}, callback, options);
|
|
480
588
|
},
|
|
481
589
|
toArray: async (callback, options = {}) => {
|
|
@@ -500,6 +608,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
500
608
|
config,
|
|
501
609
|
getPage: () => rootLocator.page(),
|
|
502
610
|
getCurrentPageIndex: () => tableState.currentPageIndex,
|
|
611
|
+
getContext: () => createStrategyContext(),
|
|
503
612
|
}, predicate, options);
|
|
504
613
|
},
|
|
505
614
|
generateConfig: async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rickcedwhat/playwright-smart-table",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.19.0",
|
|
4
4
|
"description": "Smart, column-aware table interactions for Playwright",
|
|
5
5
|
"author": "Cedrick Catalan",
|
|
6
6
|
"license": "MIT",
|
|
@@ -76,11 +76,21 @@
|
|
|
76
76
|
},
|
|
77
77
|
"typesVersions": {
|
|
78
78
|
"*": {
|
|
79
|
-
"types": [
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"presets
|
|
83
|
-
|
|
79
|
+
"types": [
|
|
80
|
+
"./dist/types.d.ts"
|
|
81
|
+
],
|
|
82
|
+
"presets": [
|
|
83
|
+
"./dist/presets/index.d.ts"
|
|
84
|
+
],
|
|
85
|
+
"presets/mui": [
|
|
86
|
+
"./dist/presets/mui.d.ts"
|
|
87
|
+
],
|
|
88
|
+
"presets/rdg": [
|
|
89
|
+
"./dist/presets/rdg.d.ts"
|
|
90
|
+
],
|
|
91
|
+
"presets/glide": [
|
|
92
|
+
"./dist/presets/glide/index.d.ts"
|
|
93
|
+
]
|
|
84
94
|
}
|
|
85
95
|
},
|
|
86
96
|
"keywords": [
|