@rickcedwhat/playwright-smart-table 6.7.9 → 6.8.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/README.md +13 -9
- package/dist/engine/tableIteration.d.ts +7 -9
- package/dist/engine/tableIteration.js +92 -191
- package/dist/engine/tableMapper.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/packageVersion.d.ts +6 -0
- package/dist/packageVersion.js +9 -0
- package/dist/plugins/index.d.ts +3 -0
- package/dist/presets/glide/columns.d.ts +4 -1
- package/dist/presets/glide/columns.js +29 -18
- package/dist/presets/glide/index.d.ts +2 -0
- package/dist/presets/glide/index.js +26 -3
- package/dist/smartRow.d.ts +2 -1
- package/dist/smartRow.js +191 -63
- package/dist/strategies/columns.d.ts +11 -0
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +34 -7
- package/dist/types.d.ts +33 -7
- package/dist/useTable.js +8 -6
- package/dist/utils/mutex.d.ts +8 -0
- package/dist/utils/mutex.js +31 -0
- package/dist/utils/navigationBarrier.d.ts +21 -0
- package/dist/utils/navigationBarrier.js +82 -0
- package/package.json +17 -3
package/README.md
CHANGED
|
@@ -40,10 +40,10 @@ const email = await row.locator('td').nth(emailIndex).textContent();
|
|
|
40
40
|
|
|
41
41
|
```typescript
|
|
42
42
|
// ✅ Column-aware - survives column reordering
|
|
43
|
-
const row =
|
|
43
|
+
const row = table.getRow({ Name: 'John Doe' });
|
|
44
44
|
const email = await row.getCell('Email').textContent();
|
|
45
45
|
|
|
46
|
-
// ✅ Auto-pagination
|
|
46
|
+
// ✅ Auto-pagination across all pages
|
|
47
47
|
const allEngineers = await table.findRows({ Department: 'Engineering' });
|
|
48
48
|
|
|
49
49
|
// ✅ Type-safe
|
|
@@ -66,8 +66,8 @@ import { useTable } from '@rickcedwhat/playwright-smart-table';
|
|
|
66
66
|
|
|
67
67
|
const table = await useTable(page.locator('#my-table')).init();
|
|
68
68
|
|
|
69
|
-
//
|
|
70
|
-
const row =
|
|
69
|
+
// Get row by column values (current page)
|
|
70
|
+
const row = table.getRow({ Name: 'John Doe' });
|
|
71
71
|
|
|
72
72
|
// Access cells by column name
|
|
73
73
|
const email = await row.getCell('Email').innerText();
|
|
@@ -79,16 +79,16 @@ const allActive = await table.findRows({ Status: 'Active' });
|
|
|
79
79
|
### Iterating Across Pages
|
|
80
80
|
|
|
81
81
|
```typescript
|
|
82
|
-
// forEach — sequential
|
|
82
|
+
// forEach — sequential by default (concurrency: 'sequential') — safe for interactions
|
|
83
83
|
await table.forEach(async ({ row, rowIndex, stop }) => {
|
|
84
84
|
if (await row.getCell('Status').innerText() === 'Done') stop();
|
|
85
85
|
await row.getCell('Checkbox').click();
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
// map — parallel
|
|
88
|
+
// map — parallel by default (concurrency: 'parallel') — safe for reads
|
|
89
89
|
const emails = await table.map(({ row }) => row.getCell('Email').innerText());
|
|
90
90
|
|
|
91
|
-
// filter —
|
|
91
|
+
// filter — sequential by default; returns SmartRowArray
|
|
92
92
|
const active = await table.filter(async ({ row }) =>
|
|
93
93
|
await row.getCell('Status').innerText() === 'Active'
|
|
94
94
|
);
|
|
@@ -99,10 +99,14 @@ for await (const { row, rowIndex } of table) {
|
|
|
99
99
|
}
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
Set a default for all iteration calls with `useTable(..., { concurrency: 'sequential' })`, or per call: `table.map(fn, { concurrency: 'synchronized' })`. Modes: **`parallel`** (full parallelism), **`sequential`** (strictly one row at a time), **`synchronized`** (parallel navigation with serialized callbacks — useful for virtualized grids).
|
|
103
|
+
|
|
102
104
|
When your pagination strategy supports bulk jumps (`goNextBulk`), pass `{ useBulkPagination: true }` to `map`/`forEach`/`filter` to advance by multiple pages at once.
|
|
103
105
|
|
|
104
|
-
> **`map` + UI interactions:** `map` defaults to `
|
|
105
|
-
> fills inputs, or otherwise mutates UI state, pass `{
|
|
106
|
+
> **`map` + UI interactions:** `map` defaults to `concurrency: 'parallel'`. If your callback opens popovers,
|
|
107
|
+
> fills inputs, or otherwise mutates UI state, pass `{ concurrency: 'sequential' }` (or `'synchronized'` if you need lock-step navigation with serialized work).
|
|
108
|
+
|
|
109
|
+
The legacy `{ parallel: true | false }` option still works but is deprecated; prefer `concurrency`.
|
|
106
110
|
|
|
107
111
|
### `filter` vs `findRows`
|
|
108
112
|
|
|
@@ -2,24 +2,22 @@ import type { Locator, Page } from '@playwright/test';
|
|
|
2
2
|
import type { SmartRow, RowIterationContext, RowIterationOptions } from '../types';
|
|
3
3
|
import type { FinalTableConfig } from '../types';
|
|
4
4
|
import type { SmartRowArray } from '../utils/smartRowArray';
|
|
5
|
+
import { NavigationBarrier } from '../utils/navigationBarrier';
|
|
5
6
|
export interface TableIterationEnv<T = any> {
|
|
6
7
|
getRowLocators: () => Locator;
|
|
7
8
|
getMap: () => Map<string, number>;
|
|
8
9
|
advancePage: (useBulk: boolean) => Promise<boolean>;
|
|
9
|
-
makeSmartRow: (rowLocator: Locator, map: Map<string, number>, rowIndex: number, tablePageIndex?: number) => SmartRow<T>;
|
|
10
|
+
makeSmartRow: (rowLocator: Locator, map: Map<string, number>, rowIndex: number, tablePageIndex?: number, barrier?: NavigationBarrier) => SmartRow<T>;
|
|
10
11
|
createSmartRowArray: (rows: SmartRow<T>[]) => SmartRowArray<T>;
|
|
11
12
|
config: FinalTableConfig<T>;
|
|
12
13
|
getPage: () => Page;
|
|
13
14
|
}
|
|
14
|
-
/**
|
|
15
|
-
* Shared row-iteration loop used by forEach, map, and filter.
|
|
16
|
-
*/
|
|
15
|
+
/** Delegates to {@link runMap}; void results are discarded. */
|
|
17
16
|
export declare function runForEach<T>(env: TableIterationEnv<T>, callback: (ctx: RowIterationContext<T>) => void | Promise<void>, options?: RowIterationOptions): Promise<void>;
|
|
18
17
|
/**
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
export declare function runMap<T, R>(env: TableIterationEnv<T>, callback: (ctx: RowIterationContext<T>) => R | Promise<R>, options?: RowIterationOptions): Promise<R[]>;
|
|
22
|
-
/**
|
|
23
|
-
* Shared row-iteration loop for filter.
|
|
18
|
+
* Row iteration for map (and forEach/filter via label).
|
|
19
|
+
* Concurrency: `parallel` | `synchronized` | `sequential` (see RowIterationOptions).
|
|
24
20
|
*/
|
|
21
|
+
export declare function runMap<T, R>(env: TableIterationEnv<T>, callback: (ctx: RowIterationContext<T>) => R | Promise<R>, options?: RowIterationOptions, label?: string): Promise<R[]>;
|
|
22
|
+
/** Filter via map; returns matched rows as {@link SmartRowArray}. */
|
|
25
23
|
export declare function runFilter<T>(env: TableIterationEnv<T>, predicate: (ctx: RowIterationContext<T>) => boolean | Promise<boolean>, options?: RowIterationOptions): Promise<SmartRowArray<T>>;
|
|
@@ -14,160 +14,129 @@ exports.runMap = runMap;
|
|
|
14
14
|
exports.runFilter = runFilter;
|
|
15
15
|
const elementTracker_1 = require("../utils/elementTracker");
|
|
16
16
|
const debugUtils_1 = require("../utils/debugUtils");
|
|
17
|
+
const navigationBarrier_1 = require("../utils/navigationBarrier");
|
|
18
|
+
const mutex_1 = require("../utils/mutex");
|
|
17
19
|
function log(config, msg) {
|
|
18
20
|
(0, debugUtils_1.logDebug)(config, 'verbose', msg);
|
|
19
21
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*/
|
|
22
|
+
const SKIP = Symbol('skip');
|
|
23
|
+
/** Delegates to {@link runMap}; void results are discarded. */
|
|
23
24
|
function runForEach(env_1, callback_1) {
|
|
24
25
|
return __awaiter(this, arguments, void 0, function* (env, callback, options = {}) {
|
|
25
|
-
|
|
26
|
-
const map = env.getMap();
|
|
27
|
-
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : env.config.maxPages;
|
|
28
|
-
const dedupeStrategy = (_b = options.dedupe) !== null && _b !== void 0 ? _b : env.config.strategies.dedupe;
|
|
29
|
-
const dedupeKeys = dedupeStrategy ? new Set() : null;
|
|
30
|
-
const parallel = (_c = options.parallel) !== null && _c !== void 0 ? _c : false;
|
|
31
|
-
const useBulk = (_d = options.useBulkPagination) !== null && _d !== void 0 ? _d : false;
|
|
32
|
-
const tracker = new elementTracker_1.ElementTracker('forEach');
|
|
33
|
-
log(env.config, `forEach: starting (maxPages=${effectiveMaxPages}, parallel=${parallel}, dedupe=${!!dedupeStrategy})`);
|
|
34
|
-
try {
|
|
35
|
-
let rowIndex = 0;
|
|
36
|
-
let stopped = false;
|
|
37
|
-
let pagesScanned = 1;
|
|
38
|
-
let totalProcessed = 0;
|
|
39
|
-
const stop = () => {
|
|
40
|
-
log(env.config, `forEach: stop() called — halting after current page`);
|
|
41
|
-
stopped = true;
|
|
42
|
-
};
|
|
43
|
-
while (!stopped) {
|
|
44
|
-
const rowLocators = env.getRowLocators();
|
|
45
|
-
const newIndices = yield tracker.getUnseenIndices(rowLocators);
|
|
46
|
-
const pageRows = yield rowLocators.all();
|
|
47
|
-
const smartRows = newIndices.map((idx, i) => env.makeSmartRow(pageRows[idx], map, rowIndex + i));
|
|
48
|
-
log(env.config, `forEach: page ${pagesScanned} — ${newIndices.length} new row(s) found`);
|
|
49
|
-
if (parallel) {
|
|
50
|
-
yield Promise.all(smartRows.map((row) => __awaiter(this, void 0, void 0, function* () {
|
|
51
|
-
if (stopped)
|
|
52
|
-
return;
|
|
53
|
-
if (dedupeKeys && dedupeStrategy) {
|
|
54
|
-
const key = yield dedupeStrategy(row);
|
|
55
|
-
if (dedupeKeys.has(key)) {
|
|
56
|
-
log(env.config, `forEach: dedupe skip key="${key}"`);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
dedupeKeys.add(key);
|
|
60
|
-
}
|
|
61
|
-
yield callback({ row, rowIndex: row.rowIndex, stop });
|
|
62
|
-
totalProcessed++;
|
|
63
|
-
})));
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
for (const row of smartRows) {
|
|
67
|
-
if (stopped)
|
|
68
|
-
break;
|
|
69
|
-
if (dedupeKeys && dedupeStrategy) {
|
|
70
|
-
const key = yield dedupeStrategy(row);
|
|
71
|
-
if (dedupeKeys.has(key)) {
|
|
72
|
-
log(env.config, `forEach: dedupe skip key="${key}"`);
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
dedupeKeys.add(key);
|
|
76
|
-
}
|
|
77
|
-
yield callback({ row, rowIndex: row.rowIndex, stop });
|
|
78
|
-
totalProcessed++;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
rowIndex += smartRows.length;
|
|
82
|
-
if (stopped || pagesScanned >= effectiveMaxPages)
|
|
83
|
-
break;
|
|
84
|
-
log(env.config, `forEach: advancing to next page (${pagesScanned} → ${pagesScanned + 1})`);
|
|
85
|
-
if (!(yield env.advancePage(useBulk))) {
|
|
86
|
-
log(env.config, `forEach: no more pages — done`);
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
pagesScanned++;
|
|
90
|
-
}
|
|
91
|
-
log(env.config, `forEach: complete — ${totalProcessed} row(s) processed across ${pagesScanned} page(s)`);
|
|
92
|
-
}
|
|
93
|
-
finally {
|
|
94
|
-
yield tracker.cleanup(env.getPage());
|
|
95
|
-
}
|
|
26
|
+
yield runMap(env, callback, options, 'forEach');
|
|
96
27
|
});
|
|
97
28
|
}
|
|
98
29
|
/**
|
|
99
|
-
*
|
|
30
|
+
* Row iteration for map (and forEach/filter via label).
|
|
31
|
+
* Concurrency: `parallel` | `synchronized` | `sequential` (see RowIterationOptions).
|
|
100
32
|
*/
|
|
101
33
|
function runMap(env_1, callback_1) {
|
|
102
|
-
return __awaiter(this, arguments, void 0, function* (env, callback, options = {}) {
|
|
103
|
-
var _a, _b, _c
|
|
34
|
+
return __awaiter(this, arguments, void 0, function* (env, callback, options = {}, label = 'map') {
|
|
35
|
+
var _a, _b, _c;
|
|
104
36
|
const map = env.getMap();
|
|
105
37
|
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : env.config.maxPages;
|
|
106
38
|
const dedupeStrategy = (_b = options.dedupe) !== null && _b !== void 0 ? _b : env.config.strategies.dedupe;
|
|
107
39
|
const dedupeKeys = dedupeStrategy ? new Set() : null;
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
40
|
+
const defaultMode = label === 'map' ? 'parallel' : 'sequential';
|
|
41
|
+
const concurrency = options.concurrency ||
|
|
42
|
+
(options.parallel === true ? 'parallel' :
|
|
43
|
+
options.parallel === false ? 'sequential' :
|
|
44
|
+
env.config.concurrency || defaultMode);
|
|
45
|
+
const useBarrier = concurrency !== 'sequential';
|
|
46
|
+
// Mutex must not pair with the navigation barrier: synchronized mode needs every row
|
|
47
|
+
// to enter barrier.sync concurrently; serializing callbacks here deadlocks (first row waits
|
|
48
|
+
// for batchSize peers that never reach the barrier).
|
|
49
|
+
const useMutex = concurrency === 'sequential';
|
|
50
|
+
const useBulk = (_c = options.useBulkPagination) !== null && _c !== void 0 ? _c : false;
|
|
51
|
+
const tracker = new elementTracker_1.ElementTracker(label);
|
|
52
|
+
log(env.config, `${label}: starting (maxPages=${effectiveMaxPages}, mode=${concurrency}, dedupe=${!!dedupeStrategy})`);
|
|
112
53
|
const results = [];
|
|
113
|
-
const SKIP = Symbol('skip');
|
|
114
54
|
try {
|
|
115
55
|
let rowIndex = 0;
|
|
116
56
|
let stopped = false;
|
|
57
|
+
let stoppedIndex = Infinity;
|
|
117
58
|
let pagesScanned = 1;
|
|
118
|
-
const stop = () => {
|
|
119
|
-
|
|
120
|
-
|
|
59
|
+
const stop = (idx) => {
|
|
60
|
+
if (!stopped) {
|
|
61
|
+
log(env.config, `${label}: stop() called at row ${idx} — halting`);
|
|
62
|
+
stopped = true;
|
|
63
|
+
stoppedIndex = idx;
|
|
64
|
+
}
|
|
121
65
|
};
|
|
122
66
|
while (!stopped) {
|
|
123
67
|
const rowLocators = env.getRowLocators();
|
|
124
68
|
const newIndices = yield tracker.getUnseenIndices(rowLocators);
|
|
125
69
|
const pageRows = yield rowLocators.all();
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
70
|
+
const batchSize = newIndices.length;
|
|
71
|
+
if (batchSize === 0) {
|
|
72
|
+
log(env.config, `${label}: page ${pagesScanned} — no new row(s) found`);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
log(env.config, `${label}: scanning page ${pagesScanned} — ${batchSize} new row(s)`);
|
|
76
|
+
const barrier = useBarrier ? new navigationBarrier_1.NavigationBarrier(batchSize) : undefined;
|
|
77
|
+
const actionMutex = useMutex ? new mutex_1.Mutex() : null;
|
|
78
|
+
const smartRows = newIndices.map((idx, i) => env.makeSmartRow(pageRows[idx], map, rowIndex + i, pagesScanned - 1, barrier));
|
|
79
|
+
const processRow = (row) => __awaiter(this, void 0, void 0, function* () {
|
|
80
|
+
try {
|
|
81
|
+
if (stopped && row.rowIndex > stoppedIndex) {
|
|
134
82
|
return SKIP;
|
|
135
83
|
}
|
|
136
|
-
dedupeKeys
|
|
84
|
+
if (dedupeKeys && dedupeStrategy) {
|
|
85
|
+
const key = yield dedupeStrategy(row);
|
|
86
|
+
if (dedupeKeys.has(key)) {
|
|
87
|
+
log(env.config, `${label}: dedupe skip key="${key}"`);
|
|
88
|
+
return SKIP;
|
|
89
|
+
}
|
|
90
|
+
dedupeKeys.add(key);
|
|
91
|
+
}
|
|
92
|
+
// Execute callback (optionally serialized via mutex)
|
|
93
|
+
const runCallback = () => __awaiter(this, void 0, void 0, function* () {
|
|
94
|
+
if (stopped && row.rowIndex > stoppedIndex)
|
|
95
|
+
return SKIP;
|
|
96
|
+
log(env.config, `${label}: processing row ${row.rowIndex}`);
|
|
97
|
+
return yield callback({ row, rowIndex: row.rowIndex, stop: () => stop(row.rowIndex) });
|
|
98
|
+
});
|
|
99
|
+
if (actionMutex) {
|
|
100
|
+
return yield actionMutex.run(runCallback);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
return yield runCallback();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
// Ensure the barrier is notified once per row, even on error or skip.
|
|
108
|
+
barrier === null || barrier === void 0 ? void 0 : barrier.markFinished();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
const pageResults = [];
|
|
112
|
+
if (concurrency === 'sequential') {
|
|
113
|
+
for (const row of smartRows) {
|
|
114
|
+
pageResults.push(yield processRow(row));
|
|
137
115
|
}
|
|
138
|
-
return callback({ row, rowIndex: row.rowIndex, stop });
|
|
139
|
-
})));
|
|
140
|
-
for (const r of pageResults) {
|
|
141
|
-
if (r !== SKIP)
|
|
142
|
-
results.push(r);
|
|
143
116
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
117
|
+
else {
|
|
118
|
+
const results = yield Promise.all(smartRows.map(processRow));
|
|
119
|
+
pageResults.push(...results);
|
|
120
|
+
}
|
|
121
|
+
for (let i = 0; i < pageResults.length; i++) {
|
|
122
|
+
if (smartRows[i].rowIndex > stoppedIndex)
|
|
148
123
|
break;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
log(env.config, `map: dedupe skip key="${key}"`);
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
dedupeKeys.add(key);
|
|
156
|
-
}
|
|
157
|
-
results.push(yield callback({ row, rowIndex: row.rowIndex, stop }));
|
|
124
|
+
const r = pageResults[i];
|
|
125
|
+
if (r !== SKIP)
|
|
126
|
+
results.push(r);
|
|
158
127
|
}
|
|
128
|
+
rowIndex += batchSize;
|
|
159
129
|
}
|
|
160
|
-
rowIndex += smartRows.length;
|
|
161
130
|
if (stopped || pagesScanned >= effectiveMaxPages)
|
|
162
131
|
break;
|
|
163
|
-
log(env.config,
|
|
132
|
+
log(env.config, `${label}: advancing to next page (${pagesScanned} → ${pagesScanned + 1})`);
|
|
164
133
|
if (!(yield env.advancePage(useBulk))) {
|
|
165
|
-
log(env.config,
|
|
134
|
+
log(env.config, `${label}: no more pages — done`);
|
|
166
135
|
break;
|
|
167
136
|
}
|
|
168
137
|
pagesScanned++;
|
|
169
138
|
}
|
|
170
|
-
log(env.config,
|
|
139
|
+
log(env.config, `${label}: complete — ${results.length} result(s) across ${pagesScanned} page(s)`);
|
|
171
140
|
}
|
|
172
141
|
finally {
|
|
173
142
|
yield tracker.cleanup(env.getPage());
|
|
@@ -175,82 +144,14 @@ function runMap(env_1, callback_1) {
|
|
|
175
144
|
return results;
|
|
176
145
|
});
|
|
177
146
|
}
|
|
178
|
-
/**
|
|
179
|
-
* Shared row-iteration loop for filter.
|
|
180
|
-
*/
|
|
147
|
+
/** Filter via map; returns matched rows as {@link SmartRowArray}. */
|
|
181
148
|
function runFilter(env_1, predicate_1) {
|
|
182
149
|
return __awaiter(this, arguments, void 0, function* (env, predicate, options = {}) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
const useBulk = (_d = options.useBulkPagination) !== null && _d !== void 0 ? _d : false;
|
|
190
|
-
const tracker = new elementTracker_1.ElementTracker('filter');
|
|
191
|
-
log(env.config, `filter: starting (maxPages=${effectiveMaxPages}, parallel=${parallel}, dedupe=${!!dedupeStrategy})`);
|
|
192
|
-
const matched = [];
|
|
193
|
-
try {
|
|
194
|
-
let rowIndex = 0;
|
|
195
|
-
let stopped = false;
|
|
196
|
-
let pagesScanned = 1;
|
|
197
|
-
const stop = () => {
|
|
198
|
-
log(env.config, `filter: stop() called — halting after current page`);
|
|
199
|
-
stopped = true;
|
|
200
|
-
};
|
|
201
|
-
while (!stopped) {
|
|
202
|
-
const rowLocators = env.getRowLocators();
|
|
203
|
-
const newIndices = yield tracker.getUnseenIndices(rowLocators);
|
|
204
|
-
const pageRows = yield rowLocators.all();
|
|
205
|
-
const smartRows = newIndices.map((idx, i) => env.makeSmartRow(pageRows[idx], map, rowIndex + i, pagesScanned - 1));
|
|
206
|
-
log(env.config, `filter: scanning page ${pagesScanned} — ${newIndices.length} new row(s)`);
|
|
207
|
-
if (parallel) {
|
|
208
|
-
const flags = yield Promise.all(smartRows.map((row) => __awaiter(this, void 0, void 0, function* () {
|
|
209
|
-
if (dedupeKeys && dedupeStrategy) {
|
|
210
|
-
const key = yield dedupeStrategy(row);
|
|
211
|
-
if (dedupeKeys.has(key)) {
|
|
212
|
-
log(env.config, `filter: dedupe skip key="${key}"`);
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
dedupeKeys.add(key);
|
|
216
|
-
}
|
|
217
|
-
return predicate({ row, rowIndex: row.rowIndex, stop });
|
|
218
|
-
})));
|
|
219
|
-
smartRows.forEach((row, i) => { if (flags[i])
|
|
220
|
-
matched.push(row); });
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
for (const row of smartRows) {
|
|
224
|
-
if (stopped)
|
|
225
|
-
break;
|
|
226
|
-
if (dedupeKeys && dedupeStrategy) {
|
|
227
|
-
const key = yield dedupeStrategy(row);
|
|
228
|
-
if (dedupeKeys.has(key)) {
|
|
229
|
-
log(env.config, `filter: dedupe skip key="${key}"`);
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
dedupeKeys.add(key);
|
|
233
|
-
}
|
|
234
|
-
if (yield predicate({ row, rowIndex: row.rowIndex, stop })) {
|
|
235
|
-
matched.push(row);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
rowIndex += smartRows.length;
|
|
240
|
-
if (stopped || pagesScanned >= effectiveMaxPages)
|
|
241
|
-
break;
|
|
242
|
-
log(env.config, `filter: advancing to next page (${pagesScanned} → ${pagesScanned + 1})`);
|
|
243
|
-
if (!(yield env.advancePage(useBulk))) {
|
|
244
|
-
log(env.config, `filter: no more pages — done`);
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
pagesScanned++;
|
|
248
|
-
}
|
|
249
|
-
log(env.config, `filter: complete — ${matched.length} match(es) across ${pagesScanned} page(s)`);
|
|
250
|
-
}
|
|
251
|
-
finally {
|
|
252
|
-
yield tracker.cleanup(env.getPage());
|
|
253
|
-
}
|
|
254
|
-
return env.createSmartRowArray(matched);
|
|
150
|
+
const mapCallback = (ctx) => __awaiter(this, void 0, void 0, function* () {
|
|
151
|
+
const matched = yield predicate(ctx);
|
|
152
|
+
return matched ? ctx.row : SKIP;
|
|
153
|
+
});
|
|
154
|
+
const results = yield runMap(env, mapCallback, options, 'filter');
|
|
155
|
+
return env.createSmartRowArray(results);
|
|
255
156
|
});
|
|
256
157
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { PLAYWRIGHT_SMART_TABLE_VERSION } from './packageVersion';
|
|
1
2
|
export { useTable } from './useTable';
|
|
2
3
|
export type { TableConfig, TableResult, SmartRow, Selector, FilterValue, PaginationPrimitives, SortingStrategy, FillOptions, RowIterationContext, RowIterationOptions, TableContext, StrategyContext, BeforeCellReadFn, GetCellLocatorFn, GetActiveCellFn, DebugConfig, } from './types';
|
|
3
4
|
export { Strategies } from './strategies';
|
package/dist/index.js
CHANGED
|
@@ -33,7 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.Plugins = exports.presets = exports.Strategies = exports.useTable = void 0;
|
|
36
|
+
exports.Plugins = exports.presets = exports.Strategies = exports.useTable = exports.PLAYWRIGHT_SMART_TABLE_VERSION = void 0;
|
|
37
|
+
var packageVersion_1 = require("./packageVersion");
|
|
38
|
+
Object.defineProperty(exports, "PLAYWRIGHT_SMART_TABLE_VERSION", { enumerable: true, get: function () { return packageVersion_1.PLAYWRIGHT_SMART_TABLE_VERSION; } });
|
|
37
39
|
var useTable_1 = require("./useTable");
|
|
38
40
|
Object.defineProperty(exports, "useTable", { enumerable: true, get: function () { return useTable_1.useTable; } });
|
|
39
41
|
// Export namespace-like strategy collections
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PLAYWRIGHT_SMART_TABLE_VERSION = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* 🤖 AUTO-GENERATED FILE. DO NOT EDIT.
|
|
6
|
+
* Generated by scripts/embed-version.mjs from package.json
|
|
7
|
+
*/
|
|
8
|
+
/** Semver of this package. Use in logs or diagnostics to confirm the installed build. */
|
|
9
|
+
exports.PLAYWRIGHT_SMART_TABLE_VERSION = "6.8.1";
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare const Plugins: {
|
|
|
12
12
|
rowSelector?: string | undefined;
|
|
13
13
|
cellSelector?: string | ((row: import("playwright-core").Locator) => import("playwright-core").Locator) | undefined;
|
|
14
14
|
maxPages?: number | undefined;
|
|
15
|
+
concurrency?: import("../types").RowIterationMode | undefined;
|
|
15
16
|
headerTransformer?: ((args: {
|
|
16
17
|
text: string;
|
|
17
18
|
index: number;
|
|
@@ -31,6 +32,7 @@ export declare const Plugins: {
|
|
|
31
32
|
rowSelector?: string | undefined;
|
|
32
33
|
cellSelector?: string | ((row: import("playwright-core").Locator) => import("playwright-core").Locator) | undefined;
|
|
33
34
|
maxPages?: number | undefined;
|
|
35
|
+
concurrency?: import("../types").RowIterationMode | undefined;
|
|
34
36
|
headerTransformer?: ((args: {
|
|
35
37
|
text: string;
|
|
36
38
|
index: number;
|
|
@@ -50,6 +52,7 @@ export declare const Plugins: {
|
|
|
50
52
|
rowSelector?: string | undefined;
|
|
51
53
|
cellSelector?: string | ((row: import("playwright-core").Locator) => import("playwright-core").Locator) | undefined;
|
|
52
54
|
maxPages?: number | undefined;
|
|
55
|
+
concurrency?: import("../types").RowIterationMode | undefined;
|
|
53
56
|
headerTransformer?: ((args: {
|
|
54
57
|
text: string;
|
|
55
58
|
index: number;
|
|
@@ -8,4 +8,7 @@ export declare const glideGoUp: (context: StrategyContext) => Promise<void>;
|
|
|
8
8
|
export declare const glideGoDown: (context: StrategyContext) => Promise<void>;
|
|
9
9
|
export declare const glideGoLeft: (context: StrategyContext) => Promise<void>;
|
|
10
10
|
export declare const glideGoRight: (context: StrategyContext) => Promise<void>;
|
|
11
|
-
|
|
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>;
|
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.
|
|
12
|
+
exports.glideSnapFirstColumnIntoView = exports.glideSeekColumnIndex = exports.glideGoRight = exports.glideGoLeft = exports.glideGoDown = exports.glideGoUp = void 0;
|
|
13
13
|
/**
|
|
14
14
|
* Primitive navigation functions for Glide Data Grid.
|
|
15
15
|
* These define HOW to move, not WHEN to move.
|
|
@@ -23,29 +23,40 @@ const glideGoDown = (context) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
23
23
|
yield context.page.keyboard.press('ArrowDown');
|
|
24
24
|
});
|
|
25
25
|
exports.glideGoDown = glideGoDown;
|
|
26
|
+
// Horizontal: use page `.dvn-scroller` (canvas root is not its ancestor). Virtualized rows expose
|
|
27
|
+
// a small window of `td[aria-colindex]` that shifts with scrollLeft.
|
|
28
|
+
const nudgePageScroller = (page, delta) => __awaiter(void 0, void 0, void 0, function* () {
|
|
29
|
+
const scroller = page.locator('.dvn-scroller').first();
|
|
30
|
+
yield scroller.evaluate((el, d) => {
|
|
31
|
+
el.scrollLeft += d;
|
|
32
|
+
}, delta);
|
|
33
|
+
});
|
|
26
34
|
const glideGoLeft = (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
27
|
-
yield context.page
|
|
35
|
+
yield nudgePageScroller(context.page, -400);
|
|
28
36
|
});
|
|
29
37
|
exports.glideGoLeft = glideGoLeft;
|
|
30
38
|
const glideGoRight = (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
31
|
-
yield context.page
|
|
39
|
+
yield nudgePageScroller(context.page, 400);
|
|
32
40
|
});
|
|
33
41
|
exports.glideGoRight = glideGoRight;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
});
|
|
42
|
+
/** Coarse `scrollLeft` toward `columnIndex`; `goLeft`/`goRight` correct the remainder. */
|
|
43
|
+
const glideSeekColumnIndex = (context, columnIndex) => __awaiter(void 0, void 0, void 0, function* () {
|
|
44
|
+
const { page } = context;
|
|
45
|
+
const scroller = page.locator('.dvn-scroller').first();
|
|
46
|
+
yield scroller.evaluate((el, colIdx) => {
|
|
47
|
+
const max = Math.max(0, el.scrollWidth - el.clientWidth);
|
|
48
|
+
const ratio = Math.min(1, Math.max(0, (colIdx + 0.5) / 64));
|
|
49
|
+
el.scrollLeft = Math.floor(ratio * max);
|
|
50
|
+
}, columnIndex);
|
|
45
51
|
yield page.waitForTimeout(100);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
});
|
|
53
|
+
exports.glideSeekColumnIndex = glideSeekColumnIndex;
|
|
54
|
+
/** `scrollLeft = 0` so low `aria-colindex` cells exist again (see smartRow for optional `Home`). */
|
|
55
|
+
const glideSnapFirstColumnIntoView = (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
56
|
+
const { page } = context;
|
|
57
|
+
yield page.locator('.dvn-scroller').first().evaluate((el) => {
|
|
58
|
+
el.scrollLeft = 0;
|
|
59
|
+
});
|
|
49
60
|
yield page.waitForTimeout(150);
|
|
50
61
|
});
|
|
51
|
-
exports.
|
|
62
|
+
exports.glideSnapFirstColumnIntoView = glideSnapFirstColumnIntoView;
|
|
@@ -15,6 +15,8 @@ export declare const GlideStrategies: {
|
|
|
15
15
|
goLeft: (context: import("../../types").StrategyContext) => Promise<void>;
|
|
16
16
|
goRight: (context: import("../../types").StrategyContext) => Promise<void>;
|
|
17
17
|
goHome: (context: import("../../types").StrategyContext) => Promise<void>;
|
|
18
|
+
snapFirstColumnIntoView: (context: import("../../types").StrategyContext) => Promise<void>;
|
|
19
|
+
seekColumnIndex: (context: import("../../types").StrategyContext, columnIndex: number) => Promise<void>;
|
|
18
20
|
};
|
|
19
21
|
loading: {
|
|
20
22
|
isHeaderLoading: () => Promise<boolean>;
|