@rickcedwhat/playwright-smart-table 6.11.0 → 6.13.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/README.md +1 -0
- package/dist/engine/rowFinder.d.ts +3 -2
- package/dist/engine/rowFinder.js +41 -23
- package/dist/engine/tableIteration.d.ts +1 -0
- package/dist/engine/tableIteration.js +11 -4
- package/dist/index.d.ts +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/plugins/index.d.ts +9 -9
- package/dist/presets/glide/index.d.ts +14 -9
- package/dist/presets/glide/index.js +23 -11
- package/dist/presets/glide/viewport.d.ts +9 -0
- package/dist/presets/glide/viewport.js +113 -0
- package/dist/presets/index.d.ts +2 -1
- package/dist/presets/index.js +3 -1
- package/dist/presets/mui.js +15 -2
- package/dist/smartRow.js +59 -24
- package/dist/strategies/columns.d.ts +10 -0
- package/dist/strategies/index.d.ts +1 -1
- package/dist/strategies/stabilization.js +14 -1
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +8 -6
- package/dist/types.d.ts +8 -4
- package/dist/useTable.js +74 -6
- package/dist/utils/elementTracker.d.ts +10 -0
- package/dist/utils/elementTracker.js +31 -7
- package/dist/utils/navigationBarrier.d.ts +2 -0
- package/dist/utils/navigationBarrier.js +4 -0
- package/package.json +17 -19
- package/dist/presets/glide/columns.d.ts +0 -14
- package/dist/presets/glide/columns.js +0 -53
package/dist/useTable.js
CHANGED
|
@@ -31,6 +31,20 @@ const debugUtils_1 = require("./utils/debugUtils");
|
|
|
31
31
|
const smartRowArray_1 = require("./utils/smartRowArray");
|
|
32
32
|
const elementTracker_1 = require("./utils/elementTracker");
|
|
33
33
|
const navigationBarrier_1 = require("./utils/navigationBarrier");
|
|
34
|
+
// Helper to safely serialize objects containing functions for logging
|
|
35
|
+
const safeStringify = (obj) => {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.stringify(obj, (key, value) => {
|
|
38
|
+
if (typeof value === 'function') {
|
|
39
|
+
return '[Function]';
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
return '[unserializable]';
|
|
46
|
+
}
|
|
47
|
+
};
|
|
34
48
|
/**
|
|
35
49
|
* Main hook to interact with a table.
|
|
36
50
|
*/
|
|
@@ -182,6 +196,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
182
196
|
if (pagesJumped > 0) {
|
|
183
197
|
tableState.currentPageIndex += pagesJumped;
|
|
184
198
|
log(`_advancePage: ${primitive} advanced ${pagesJumped} page(s) — now at page ${tableState.currentPageIndex}`);
|
|
199
|
+
await (0, debugUtils_1.debugDelay)(config, 'pagination');
|
|
185
200
|
}
|
|
186
201
|
else {
|
|
187
202
|
log(`_advancePage: ${primitive} returned false — end of data`);
|
|
@@ -195,6 +210,10 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
195
210
|
var _a;
|
|
196
211
|
if (tableMapper.isInitialized())
|
|
197
212
|
return result;
|
|
213
|
+
if (config.strategies.sorting)
|
|
214
|
+
(0, validation_1.validateSortingStrategy)(config.strategies.sorting);
|
|
215
|
+
if (config.strategies.fill)
|
|
216
|
+
(0, validation_1.validateFillStrategy)(config.strategies.fill);
|
|
198
217
|
(0, debugUtils_1.warnIfDebugInCI)(config);
|
|
199
218
|
(0, debugUtils_1.logDebug)(config, 'info', 'Initializing table');
|
|
200
219
|
const map = await tableMapper.getMap(options === null || options === void 0 ? void 0 : options.timeout);
|
|
@@ -220,6 +239,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
220
239
|
return result;
|
|
221
240
|
},
|
|
222
241
|
scrollToColumn: async (columnName) => {
|
|
242
|
+
log(`scrollToColumn: column="${columnName}"`);
|
|
223
243
|
const map = await tableMapper.getMap();
|
|
224
244
|
const idx = map.get(columnName);
|
|
225
245
|
if (idx === undefined)
|
|
@@ -229,10 +249,12 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
229
249
|
await headerCell.scrollIntoViewIfNeeded();
|
|
230
250
|
},
|
|
231
251
|
getHeaders: async () => {
|
|
252
|
+
log("getHeaders: fetching available columns");
|
|
232
253
|
const map = await tableMapper.getMap();
|
|
233
254
|
return Array.from(map.keys());
|
|
234
255
|
},
|
|
235
256
|
getHeaderCell: async (columnName) => {
|
|
257
|
+
log(`getHeaderCell: column="${columnName}"`);
|
|
236
258
|
const map = await tableMapper.getMap();
|
|
237
259
|
const idx = map.get(columnName);
|
|
238
260
|
if (idx === undefined)
|
|
@@ -241,10 +263,37 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
241
263
|
},
|
|
242
264
|
countRows: async () => {
|
|
243
265
|
await _autoInit();
|
|
244
|
-
const
|
|
245
|
-
|
|
266
|
+
const pag = config.strategies.pagination;
|
|
267
|
+
const hasPagination = config.maxPages > 1 && !!((pag === null || pag === void 0 ? void 0 : pag.goNext) || (pag === null || pag === void 0 ? void 0 : pag.goNextBulk));
|
|
268
|
+
if (!hasPagination) {
|
|
269
|
+
log("countRows: counting rows in current viewport (no pagination)");
|
|
270
|
+
return resolve(config.rowSelector, rootLocator).count();
|
|
271
|
+
}
|
|
272
|
+
log(`countRows: paginating up to ${config.maxPages} page(s)`);
|
|
273
|
+
const tracker = new elementTracker_1.ElementTracker('countRows');
|
|
274
|
+
let total = 0;
|
|
275
|
+
let pagesScanned = 1;
|
|
276
|
+
try {
|
|
277
|
+
while (true) {
|
|
278
|
+
const newIndices = await tracker.getUnseenIndices(resolve(config.rowSelector, rootLocator));
|
|
279
|
+
total += newIndices.length;
|
|
280
|
+
log(`countRows: page ${pagesScanned} — ${newIndices.length} row(s) (running total: ${total})`);
|
|
281
|
+
if (pagesScanned >= config.maxPages)
|
|
282
|
+
break;
|
|
283
|
+
if (!await _advancePage(false))
|
|
284
|
+
break;
|
|
285
|
+
pagesScanned++;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
finally {
|
|
289
|
+
await tracker.cleanup(rootLocator.page());
|
|
290
|
+
}
|
|
291
|
+
log(`countRows: ${total} total row(s) across ${pagesScanned} page(s) — resetting`);
|
|
292
|
+
await result.reset();
|
|
293
|
+
return total;
|
|
246
294
|
},
|
|
247
295
|
mapColumn: async (columnName, options = {}) => {
|
|
296
|
+
log(`mapColumn: column="${columnName}" options=${safeStringify(options)}`);
|
|
248
297
|
await _autoInit();
|
|
249
298
|
const map = await tableMapper.getMap();
|
|
250
299
|
if (!map.has(columnName))
|
|
@@ -262,6 +311,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
262
311
|
}, options);
|
|
263
312
|
},
|
|
264
313
|
getColumnValues: async (columnName, options = {}) => {
|
|
314
|
+
log(`getColumnValues: column="${columnName}" options=${safeStringify(options)}`);
|
|
265
315
|
const values = await result.mapColumn(columnName, options);
|
|
266
316
|
return values.map(v => String(v));
|
|
267
317
|
},
|
|
@@ -288,15 +338,17 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
288
338
|
log("Table revalidated.");
|
|
289
339
|
},
|
|
290
340
|
getRow: (filters, options = { exact: false }) => {
|
|
341
|
+
log(`getRow: filters=${safeStringify(filters)} exact=${options.exact}`);
|
|
291
342
|
const map = tableMapper.getMapSync();
|
|
292
343
|
if (!map)
|
|
293
344
|
throw new Error('Initialization Error: You attempted to access a row before the table structure was mapped. Please call "await table.init()" once before using synchronous row access.');
|
|
294
345
|
const allRows = resolve(config.rowSelector, rootLocator);
|
|
295
346
|
const matchedRows = filterEngine.applyFilters(allRows, filters, map, options.exact || false, rootLocator.page(), rootLocator);
|
|
296
347
|
const rowLocator = matchedRows.first();
|
|
297
|
-
return _makeSmart(rowLocator, map,
|
|
348
|
+
return _makeSmart(rowLocator, map, undefined); // sync path cannot compute real index
|
|
298
349
|
},
|
|
299
350
|
getRowByIndex: (index) => {
|
|
351
|
+
log(`getRowByIndex: index=${index}`);
|
|
300
352
|
const map = tableMapper.getMapSync();
|
|
301
353
|
if (!map)
|
|
302
354
|
throw new Error('Initialization Error: You attempted to access a row before the table structure was mapped. Please call "await table.init()" once before using synchronous row access.');
|
|
@@ -304,13 +356,17 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
304
356
|
return _makeSmart(rowLocator, map, index);
|
|
305
357
|
},
|
|
306
358
|
findRow: async (filters, options) => {
|
|
359
|
+
log(`findRow: filters=${safeStringify(filters)} options=${safeStringify(options)}`);
|
|
307
360
|
return rowFinder.findRow(filters, options);
|
|
308
361
|
},
|
|
309
362
|
findRows: async (filters, options) => {
|
|
363
|
+
log(`findRows: filters=${safeStringify(filters !== null && filters !== void 0 ? filters : {})} options=${safeStringify(options)}`);
|
|
310
364
|
return rowFinder.findRows(filters !== null && filters !== void 0 ? filters : {}, options);
|
|
311
365
|
},
|
|
312
366
|
isInitialized: () => {
|
|
313
|
-
|
|
367
|
+
const initialized = tableMapper.isInitialized();
|
|
368
|
+
log(`isInitialized: ${initialized}`);
|
|
369
|
+
return initialized;
|
|
314
370
|
},
|
|
315
371
|
sorting: {
|
|
316
372
|
apply: async (columnName, direction) => {
|
|
@@ -329,7 +385,10 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
329
385
|
}
|
|
330
386
|
await config.strategies.sorting.doSort({ columnName, direction, context });
|
|
331
387
|
if ((_a = config.strategies.loading) === null || _a === void 0 ? void 0 : _a.isTableLoading) {
|
|
332
|
-
|
|
388
|
+
const deadline = Date.now() + 10000;
|
|
389
|
+
while (Date.now() < deadline && await config.strategies.loading.isTableLoading(context)) {
|
|
390
|
+
await rootLocator.page().waitForTimeout(100);
|
|
391
|
+
}
|
|
333
392
|
}
|
|
334
393
|
else {
|
|
335
394
|
await rootLocator.page().waitForTimeout(200);
|
|
@@ -344,6 +403,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
344
403
|
throw new Error(`Failed to sort column "${columnName}" to "${direction}" after ${maxRetries} attempts.`);
|
|
345
404
|
},
|
|
346
405
|
getState: async (columnName) => {
|
|
406
|
+
log(`sorting.getState: column="${columnName}"`);
|
|
347
407
|
await _autoInit();
|
|
348
408
|
if (!config.strategies.sorting)
|
|
349
409
|
throw new Error('No sorting strategy has been configured.');
|
|
@@ -359,6 +419,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
359
419
|
const effectiveMaxPages = config.maxPages;
|
|
360
420
|
const tracker = new elementTracker_1.ElementTracker('iterator');
|
|
361
421
|
const useBulk = false; // iterator has no options; default goNext
|
|
422
|
+
log(`iterator: starting (maxPages=${effectiveMaxPages})`);
|
|
362
423
|
try {
|
|
363
424
|
let rowIndex = 0;
|
|
364
425
|
let pagesScanned = 1;
|
|
@@ -368,7 +429,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
368
429
|
const pageRows = yield __await(rowLocators.all());
|
|
369
430
|
const barrier = new navigationBarrier_1.NavigationBarrier(newIndices.length);
|
|
370
431
|
for (const idx of newIndices) {
|
|
371
|
-
yield yield __await({ row: _makeSmart(pageRows[idx], map, rowIndex,
|
|
432
|
+
yield yield __await({ row: _makeSmart(pageRows[idx], map, rowIndex, tableState.currentPageIndex, barrier), index: rowIndex, rowIndex, pageIndex: tableState.currentPageIndex });
|
|
372
433
|
rowIndex++;
|
|
373
434
|
}
|
|
374
435
|
if (pagesScanned >= effectiveMaxPages)
|
|
@@ -385,6 +446,7 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
385
446
|
},
|
|
386
447
|
// ─── Row iteration (delegated to engine/tableIteration) ──────────────────
|
|
387
448
|
forEach: async (callback, options = {}) => {
|
|
449
|
+
log(`forEach: options=${safeStringify(options)}`);
|
|
388
450
|
await _autoInit();
|
|
389
451
|
await (0, tableIteration_1.runForEach)({
|
|
390
452
|
getRowLocators: () => resolve(config.rowSelector, rootLocator),
|
|
@@ -394,9 +456,11 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
394
456
|
createSmartRowArray: smartRowArray_1.createSmartRowArray,
|
|
395
457
|
config,
|
|
396
458
|
getPage: () => rootLocator.page(),
|
|
459
|
+
getCurrentPageIndex: () => tableState.currentPageIndex,
|
|
397
460
|
}, callback, options);
|
|
398
461
|
},
|
|
399
462
|
map: async (callback, options = {}) => {
|
|
463
|
+
log(`map: options=${safeStringify(options)}`);
|
|
400
464
|
await _autoInit();
|
|
401
465
|
return (0, tableIteration_1.runMap)({
|
|
402
466
|
getRowLocators: () => resolve(config.rowSelector, rootLocator),
|
|
@@ -406,9 +470,11 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
406
470
|
createSmartRowArray: smartRowArray_1.createSmartRowArray,
|
|
407
471
|
config,
|
|
408
472
|
getPage: () => rootLocator.page(),
|
|
473
|
+
getCurrentPageIndex: () => tableState.currentPageIndex,
|
|
409
474
|
}, callback, options);
|
|
410
475
|
},
|
|
411
476
|
filter: async (predicate, options = {}) => {
|
|
477
|
+
log(`filter: options=${safeStringify(options)}`);
|
|
412
478
|
await _autoInit();
|
|
413
479
|
return (0, tableIteration_1.runFilter)({
|
|
414
480
|
getRowLocators: () => resolve(config.rowSelector, rootLocator),
|
|
@@ -418,9 +484,11 @@ const useTable = (rootLocator, configOptions = {}) => {
|
|
|
418
484
|
createSmartRowArray: smartRowArray_1.createSmartRowArray,
|
|
419
485
|
config,
|
|
420
486
|
getPage: () => rootLocator.page(),
|
|
487
|
+
getCurrentPageIndex: () => tableState.currentPageIndex,
|
|
421
488
|
}, predicate, options);
|
|
422
489
|
},
|
|
423
490
|
generateConfig: async () => {
|
|
491
|
+
log("Generating table config prompt...");
|
|
424
492
|
const html = await _getCleanHtml(rootLocator);
|
|
425
493
|
const separator = "=".repeat(50);
|
|
426
494
|
const content = `\n${separator} \n🤖 COPY INTO GEMINI / ChatGPT 🤖\n${separator} \nI am using 'playwright-smart-table'.\nTarget Table Locator: ${rootLocator.toString()} \nGenerate config for: \n\`\`\`html\n${html.substring(0, 10000)} ...\n\`\`\`\n${separator}\n`;
|
|
@@ -2,6 +2,16 @@ import { Locator } from '@playwright/test';
|
|
|
2
2
|
export declare class ElementTracker {
|
|
3
3
|
readonly id: string;
|
|
4
4
|
constructor(prefix?: string);
|
|
5
|
+
/**
|
|
6
|
+
* Returns indices of newly seen or recycled elements without marking them as seen.
|
|
7
|
+
* Call {@link commitIndices} to persist the subset you intend to process.
|
|
8
|
+
*/
|
|
9
|
+
peekUnseenIndices(locators: Locator): Promise<number[]>;
|
|
10
|
+
/**
|
|
11
|
+
* Marks the given indices as seen. Only call this for the subset you intend to process;
|
|
12
|
+
* uncommitted indices will be returned again by the next peek/getUnseenIndices call.
|
|
13
|
+
*/
|
|
14
|
+
commitIndices(locators: Locator, indices: number[]): Promise<void>;
|
|
5
15
|
/**
|
|
6
16
|
* Finds the indices of newly seen elements in the browser, storing their text signature
|
|
7
17
|
* in a WeakMap. This gracefully handles both append-only DOMs (by identity) and
|
|
@@ -6,11 +6,10 @@ class ElementTracker {
|
|
|
6
6
|
this.id = `__smartTable_${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* virtualized DOMs (by text signature if nodes are recycled).
|
|
9
|
+
* Returns indices of newly seen or recycled elements without marking them as seen.
|
|
10
|
+
* Call {@link commitIndices} to persist the subset you intend to process.
|
|
12
11
|
*/
|
|
13
|
-
async
|
|
12
|
+
async peekUnseenIndices(locators) {
|
|
14
13
|
return await locators.evaluateAll((elements, trackerId) => {
|
|
15
14
|
const win = window;
|
|
16
15
|
if (!win[trackerId]) {
|
|
@@ -19,17 +18,42 @@ class ElementTracker {
|
|
|
19
18
|
const seenMap = win[trackerId];
|
|
20
19
|
const newIndices = [];
|
|
21
20
|
elements.forEach((el, index) => {
|
|
22
|
-
// Determine a lightweight signature for the row (textContent strips HTML, fast)
|
|
23
21
|
const signature = el.textContent || '';
|
|
24
|
-
// If it's a new element, OR a recycled element with new data
|
|
25
22
|
if (seenMap.get(el) !== signature) {
|
|
26
|
-
seenMap.set(el, signature);
|
|
27
23
|
newIndices.push(index);
|
|
28
24
|
}
|
|
29
25
|
});
|
|
30
26
|
return newIndices;
|
|
31
27
|
}, this.id);
|
|
32
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Marks the given indices as seen. Only call this for the subset you intend to process;
|
|
31
|
+
* uncommitted indices will be returned again by the next peek/getUnseenIndices call.
|
|
32
|
+
*/
|
|
33
|
+
async commitIndices(locators, indices) {
|
|
34
|
+
await locators.evaluateAll((elements, [trackerId, indicesToCommit]) => {
|
|
35
|
+
const win = window;
|
|
36
|
+
if (!win[trackerId]) {
|
|
37
|
+
win[trackerId] = new WeakMap();
|
|
38
|
+
}
|
|
39
|
+
const seenMap = win[trackerId];
|
|
40
|
+
for (const index of indicesToCommit) {
|
|
41
|
+
const el = elements[index];
|
|
42
|
+
if (el)
|
|
43
|
+
seenMap.set(el, el.textContent || '');
|
|
44
|
+
}
|
|
45
|
+
}, [this.id, indices]);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Finds the indices of newly seen elements in the browser, storing their text signature
|
|
49
|
+
* in a WeakMap. This gracefully handles both append-only DOMs (by identity) and
|
|
50
|
+
* virtualized DOMs (by text signature if nodes are recycled).
|
|
51
|
+
*/
|
|
52
|
+
async getUnseenIndices(locators) {
|
|
53
|
+
const indices = await this.peekUnseenIndices(locators);
|
|
54
|
+
await this.commitIndices(locators, indices);
|
|
55
|
+
return indices;
|
|
56
|
+
}
|
|
33
57
|
/**
|
|
34
58
|
* Cleans up the tracking map from the browser window object.
|
|
35
59
|
*/
|
|
@@ -7,6 +7,8 @@ export declare class NavigationBarrier {
|
|
|
7
7
|
private finishedCount;
|
|
8
8
|
private buckets;
|
|
9
9
|
constructor(total: number);
|
|
10
|
+
/** True when more than one row shares this barrier (scrollToRow would evict peers). */
|
|
11
|
+
isParallel(): boolean;
|
|
10
12
|
/**
|
|
11
13
|
* Synchronize all active rows at a specific column index.
|
|
12
14
|
* The last row to arrive will trigger the `moveAction`.
|
|
@@ -12,6 +12,10 @@ class NavigationBarrier {
|
|
|
12
12
|
this.buckets = new Map();
|
|
13
13
|
this.total = total;
|
|
14
14
|
}
|
|
15
|
+
/** True when more than one row shares this barrier (scrollToRow would evict peers). */
|
|
16
|
+
isParallel() {
|
|
17
|
+
return this.total > 1;
|
|
18
|
+
}
|
|
15
19
|
/**
|
|
16
20
|
* Synchronize all active rows at a specific column index.
|
|
17
21
|
* The last row to arrive will trigger the `moveAction`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rickcedwhat/playwright-smart-table",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.13.0",
|
|
4
4
|
"description": "Smart, column-aware table interactions for Playwright",
|
|
5
5
|
"author": "Cedrick Catalan",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,19 +28,19 @@
|
|
|
28
28
|
"docs:build:all": "vitepress build docs",
|
|
29
29
|
"docs:verify-links": "node scripts/verify-vitepress-links.mjs",
|
|
30
30
|
"embed-version": "node scripts/embed-version.mjs",
|
|
31
|
-
"build": "
|
|
32
|
-
"prepublishOnly": "
|
|
31
|
+
"build": "pnpm run embed-version && pnpm run generate-types && pnpm run generate-config-types && pnpm run generate-docs && pnpm run generate-all-api-docs && pnpm run update-all-api-signatures && tsc",
|
|
32
|
+
"prepublishOnly": "pnpm run build",
|
|
33
33
|
"clean-port": "lsof -ti:3000 | xargs kill -9 || true",
|
|
34
|
-
"pretest": "
|
|
35
|
-
"posttest": "
|
|
36
|
-
"test": "
|
|
34
|
+
"pretest": "pnpm run clean-port",
|
|
35
|
+
"posttest": "pnpm run clean-port",
|
|
36
|
+
"test": "pnpm run test:unit && pnpm exec playwright test",
|
|
37
37
|
"test:unit": "vitest run --coverage --reporter=verbose --reporter=html",
|
|
38
38
|
"test:unit:ui": "vitest --ui",
|
|
39
|
-
"test:mutate": "
|
|
40
|
-
"pretest:e2e": "
|
|
41
|
-
"posttest:e2e": "
|
|
42
|
-
"test:e2e": "
|
|
43
|
-
"test:playground": "
|
|
39
|
+
"test:mutate": "pnpm exec stryker run",
|
|
40
|
+
"pretest:e2e": "pnpm run clean-port",
|
|
41
|
+
"posttest:e2e": "pnpm run clean-port",
|
|
42
|
+
"test:e2e": "pnpm exec playwright test",
|
|
43
|
+
"test:playground": "pnpm exec playwright test --config tests-external/playground/playwright.config.ts 2>&1 | tee \"tests-external/playground/logs/run-$(date +%Y%m%d-%H%M%S).log\"",
|
|
44
44
|
"dev:playground": "cd playground && npm run dev",
|
|
45
45
|
"dev:mui": "cd tests/apps/mui-datagrid && npm run dev",
|
|
46
46
|
"prepare": "husky"
|
|
@@ -87,17 +87,15 @@
|
|
|
87
87
|
"@stryker-mutator/core": "^9.6.0",
|
|
88
88
|
"@stryker-mutator/vitest-runner": "^9.6.0",
|
|
89
89
|
"@types/node": "^25.5.2",
|
|
90
|
-
"@vitest/coverage-v8": "^
|
|
91
|
-
"@vitest/ui": "^
|
|
90
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
91
|
+
"@vitest/ui": "^3.2.4",
|
|
92
92
|
"happy-dom": "^20.8.3",
|
|
93
|
-
"@commitlint/cli": "^
|
|
94
|
-
"@commitlint/config-conventional": "^
|
|
93
|
+
"@commitlint/cli": "^21.0.0",
|
|
94
|
+
"@commitlint/config-conventional": "^21.0.0",
|
|
95
95
|
"husky": "^9.1.7",
|
|
96
96
|
"typescript": "^6.0.2",
|
|
97
97
|
"vitepress": "^1.6.4",
|
|
98
|
-
"vitest": "^
|
|
98
|
+
"vitest": "^3.2.4"
|
|
99
99
|
},
|
|
100
|
-
"
|
|
101
|
-
"@scarf/scarf": "^1.4.0"
|
|
102
|
-
}
|
|
100
|
+
"packageManager": "pnpm@10.33.2"
|
|
103
101
|
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { StrategyContext } from '../../types';
|
|
2
|
-
/**
|
|
3
|
-
* Primitive navigation functions for Glide Data Grid.
|
|
4
|
-
* These define HOW to move, not WHEN to move.
|
|
5
|
-
* The orchestration logic lives in _navigateToCell.
|
|
6
|
-
*/
|
|
7
|
-
export declare const glideGoUp: (context: StrategyContext) => Promise<void>;
|
|
8
|
-
export declare const glideGoDown: (context: StrategyContext) => Promise<void>;
|
|
9
|
-
export declare const glideGoLeft: (context: StrategyContext) => Promise<void>;
|
|
10
|
-
export declare const glideGoRight: (context: StrategyContext) => Promise<void>;
|
|
11
|
-
/** Coarse `scrollLeft` toward `columnIndex`; `goLeft`/`goRight` correct the remainder. */
|
|
12
|
-
export declare const glideSeekColumnIndex: (context: StrategyContext, columnIndex: number) => Promise<void>;
|
|
13
|
-
/** `scrollLeft = 0` so low `aria-colindex` cells exist again (see smartRow for optional `Home`). */
|
|
14
|
-
export declare const glideSnapFirstColumnIntoView: (context: StrategyContext) => Promise<void>;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.glideSnapFirstColumnIntoView = exports.glideSeekColumnIndex = exports.glideGoRight = exports.glideGoLeft = exports.glideGoDown = exports.glideGoUp = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Primitive navigation functions for Glide Data Grid.
|
|
6
|
-
* These define HOW to move, not WHEN to move.
|
|
7
|
-
* The orchestration logic lives in _navigateToCell.
|
|
8
|
-
*/
|
|
9
|
-
const glideGoUp = async (context) => {
|
|
10
|
-
await context.page.keyboard.press('ArrowUp');
|
|
11
|
-
};
|
|
12
|
-
exports.glideGoUp = glideGoUp;
|
|
13
|
-
const glideGoDown = async (context) => {
|
|
14
|
-
await context.page.keyboard.press('ArrowDown');
|
|
15
|
-
};
|
|
16
|
-
exports.glideGoDown = glideGoDown;
|
|
17
|
-
// Horizontal: use page `.dvn-scroller` (canvas root is not its ancestor). Virtualized rows expose
|
|
18
|
-
// a small window of `td[aria-colindex]` that shifts with scrollLeft.
|
|
19
|
-
const nudgePageScroller = async (page, delta) => {
|
|
20
|
-
const scroller = page.locator('.dvn-scroller').first();
|
|
21
|
-
await scroller.evaluate((el, d) => {
|
|
22
|
-
el.scrollLeft += d;
|
|
23
|
-
}, delta);
|
|
24
|
-
};
|
|
25
|
-
const glideGoLeft = async (context) => {
|
|
26
|
-
await nudgePageScroller(context.page, -400);
|
|
27
|
-
};
|
|
28
|
-
exports.glideGoLeft = glideGoLeft;
|
|
29
|
-
const glideGoRight = async (context) => {
|
|
30
|
-
await nudgePageScroller(context.page, 400);
|
|
31
|
-
};
|
|
32
|
-
exports.glideGoRight = glideGoRight;
|
|
33
|
-
/** Coarse `scrollLeft` toward `columnIndex`; `goLeft`/`goRight` correct the remainder. */
|
|
34
|
-
const glideSeekColumnIndex = async (context, columnIndex) => {
|
|
35
|
-
const { page } = context;
|
|
36
|
-
const scroller = page.locator('.dvn-scroller').first();
|
|
37
|
-
await scroller.evaluate((el, colIdx) => {
|
|
38
|
-
const max = Math.max(0, el.scrollWidth - el.clientWidth);
|
|
39
|
-
const ratio = Math.min(1, Math.max(0, (colIdx + 0.5) / 64));
|
|
40
|
-
el.scrollLeft = Math.floor(ratio * max);
|
|
41
|
-
}, columnIndex);
|
|
42
|
-
await page.waitForTimeout(100);
|
|
43
|
-
};
|
|
44
|
-
exports.glideSeekColumnIndex = glideSeekColumnIndex;
|
|
45
|
-
/** `scrollLeft = 0` so low `aria-colindex` cells exist again (see smartRow for optional `Home`). */
|
|
46
|
-
const glideSnapFirstColumnIntoView = async (context) => {
|
|
47
|
-
const { page } = context;
|
|
48
|
-
await page.locator('.dvn-scroller').first().evaluate((el) => {
|
|
49
|
-
el.scrollLeft = 0;
|
|
50
|
-
});
|
|
51
|
-
await page.waitForTimeout(150);
|
|
52
|
-
};
|
|
53
|
-
exports.glideSnapFirstColumnIntoView = glideSnapFirstColumnIntoView;
|