@rickcedwhat/playwright-smart-table 6.7.4 → 6.7.6
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 +7 -3
- package/dist/engine/rowFinder.d.ts +1 -1
- package/dist/engine/rowFinder.js +7 -6
- package/dist/engine/tableIteration.d.ts +25 -0
- package/dist/engine/tableIteration.js +210 -0
- package/dist/filterEngine.d.ts +5 -1
- package/dist/filterEngine.js +23 -13
- package/dist/index.d.ts +1 -1
- package/dist/{strategies → plugins}/glide/columns.js +2 -5
- package/dist/{strategies → plugins}/glide/headers.js +0 -3
- package/dist/plugins/glide/index.d.ts +31 -0
- package/dist/{strategies/glide.js → plugins/glide/index.js} +29 -48
- package/dist/plugins/index.d.ts +16 -0
- package/dist/plugins/index.js +16 -0
- package/dist/plugins/mui/index.d.ts +8 -0
- package/dist/plugins/mui/index.js +25 -0
- package/dist/plugins/rdg/index.d.ts +17 -0
- package/dist/{strategies/rdg.js → plugins/rdg/index.js} +25 -26
- package/dist/smartRow.d.ts +6 -1
- package/dist/smartRow.js +27 -9
- package/dist/strategies/filter.d.ts +13 -0
- package/dist/strategies/filter.js +38 -0
- package/dist/strategies/index.d.ts +21 -0
- package/dist/strategies/index.js +6 -0
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +19 -13
- package/dist/types.d.ts +19 -20
- package/dist/useTable.js +52 -186
- package/dist/utils/sentinel.d.ts +5 -0
- package/dist/utils/sentinel.js +8 -0
- package/package.json +4 -4
- package/dist/plugins.d.ts +0 -44
- package/dist/plugins.js +0 -13
- package/dist/strategies/glide.d.ts +0 -45
- package/dist/strategies/rdg.d.ts +0 -34
- /package/dist/{strategies → plugins}/glide/columns.d.ts +0 -0
- /package/dist/{strategies → plugins}/glide/headers.d.ts +0 -0
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ const table = await useTable(page.locator('#my-table')).init();
|
|
|
70
70
|
const row = await table.findRow({ Name: 'John Doe' });
|
|
71
71
|
|
|
72
72
|
// Access cells by column name
|
|
73
|
-
const email = await row.getCell('Email').
|
|
73
|
+
const email = await row.getCell('Email').innerText();
|
|
74
74
|
|
|
75
75
|
// Search across paginated tables
|
|
76
76
|
const allActive = await table.findRows({ Status: 'Active' });
|
|
@@ -146,9 +146,9 @@ const table = useTable(page.locator('#table'), {
|
|
|
146
146
|
},
|
|
147
147
|
// Override how data is written to the 'Tags' column (for .smartFill())
|
|
148
148
|
Tags: {
|
|
149
|
-
write: async (cell,
|
|
149
|
+
write: async ({ cell, targetValue }) => {
|
|
150
150
|
await cell.click();
|
|
151
|
-
await page.keyboard.type(
|
|
151
|
+
await page.keyboard.type(targetValue);
|
|
152
152
|
await page.keyboard.press('Enter');
|
|
153
153
|
}
|
|
154
154
|
}
|
|
@@ -219,6 +219,10 @@ for (const row of active) {
|
|
|
219
219
|
|
|
220
220
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
221
221
|
|
|
222
|
+
## Deprecations
|
|
223
|
+
|
|
224
|
+
- `generateConfigPrompt()` — deprecated. Use `generateConfig()` instead. `generateConfigPrompt()` will be removed in v7.0.0.
|
|
225
|
+
|
|
222
226
|
## License
|
|
223
227
|
|
|
224
228
|
MIT © Cedrick Catalan
|
|
@@ -19,7 +19,7 @@ export declare class RowFinder<T = any> {
|
|
|
19
19
|
exact?: boolean;
|
|
20
20
|
maxPages?: number;
|
|
21
21
|
}): Promise<SmartRow<T>>;
|
|
22
|
-
findRows(filters?:
|
|
22
|
+
findRows(filters?: Record<string, FilterValue>, options?: {
|
|
23
23
|
exact?: boolean;
|
|
24
24
|
maxPages?: number;
|
|
25
25
|
}): Promise<SmartRowArray<T>>;
|
package/dist/engine/rowFinder.js
CHANGED
|
@@ -14,6 +14,7 @@ const debugUtils_1 = require("../utils/debugUtils");
|
|
|
14
14
|
const smartRowArray_1 = require("../utils/smartRowArray");
|
|
15
15
|
const validation_1 = require("../strategies/validation");
|
|
16
16
|
const elementTracker_1 = require("../utils/elementTracker");
|
|
17
|
+
const sentinel_1 = require("../utils/sentinel");
|
|
17
18
|
class RowFinder {
|
|
18
19
|
constructor(rootLocator, config, resolve, filterEngine, tableMapper, makeSmartRow, tableState = { currentPageIndex: 0 }) {
|
|
19
20
|
this.rootLocator = rootLocator;
|
|
@@ -42,14 +43,14 @@ class RowFinder {
|
|
|
42
43
|
const sentinel = this.resolve(this.config.rowSelector, this.rootLocator)
|
|
43
44
|
.filter({ hasText: "___SENTINEL_ROW_NOT_FOUND___" + Date.now() });
|
|
44
45
|
const smartRow = this.makeSmartRow(sentinel, yield this.tableMapper.getMap(), 0);
|
|
45
|
-
smartRow.
|
|
46
|
+
smartRow[sentinel_1.SENTINEL_ROW] = true;
|
|
46
47
|
return smartRow;
|
|
47
48
|
});
|
|
48
49
|
}
|
|
49
|
-
findRows(
|
|
50
|
-
return __awaiter(this,
|
|
50
|
+
findRows() {
|
|
51
|
+
return __awaiter(this, arguments, void 0, function* (filters = {}, options) {
|
|
51
52
|
var _a, _b, _c, _d;
|
|
52
|
-
const filtersRecord = filters
|
|
53
|
+
const filtersRecord = filters;
|
|
53
54
|
const map = yield this.tableMapper.getMap();
|
|
54
55
|
const allRows = [];
|
|
55
56
|
const effectiveMaxPages = (_b = (_a = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _a !== void 0 ? _a : this.config.maxPages) !== null && _b !== void 0 ? _b : Infinity;
|
|
@@ -61,7 +62,7 @@ class RowFinder {
|
|
|
61
62
|
let rowLocators = this.resolve(this.config.rowSelector, this.rootLocator);
|
|
62
63
|
// Only apply filters if we have them
|
|
63
64
|
if (Object.keys(filtersRecord).length > 0) {
|
|
64
|
-
rowLocators = this.filterEngine.applyFilters(rowLocators, filtersRecord, map, (_a = options === null || options === void 0 ? void 0 : options.exact) !== null && _a !== void 0 ? _a : false, this.rootLocator.page());
|
|
65
|
+
rowLocators = this.filterEngine.applyFilters(rowLocators, filtersRecord, map, (_a = options === null || options === void 0 ? void 0 : options.exact) !== null && _a !== void 0 ? _a : false, this.rootLocator.page(), this.rootLocator);
|
|
65
66
|
}
|
|
66
67
|
// Get only newly seen matched rows
|
|
67
68
|
const newIndices = yield tracker.getUnseenIndices(rowLocators);
|
|
@@ -132,7 +133,7 @@ class RowFinder {
|
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
const allRows = this.resolve(this.config.rowSelector, this.rootLocator);
|
|
135
|
-
const matchedRows = this.filterEngine.applyFilters(allRows, filters, map, options.exact || false, this.rootLocator.page());
|
|
136
|
+
const matchedRows = this.filterEngine.applyFilters(allRows, filters, map, options.exact || false, this.rootLocator.page(), this.rootLocator);
|
|
136
137
|
const count = yield matchedRows.count();
|
|
137
138
|
this.log(`Page ${this.tableState.currentPageIndex}: Found ${count} matches.`);
|
|
138
139
|
if (count > 1) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Locator, Page } from '@playwright/test';
|
|
2
|
+
import type { SmartRow, RowIterationContext, RowIterationOptions } from '../types';
|
|
3
|
+
import type { FinalTableConfig } from '../types';
|
|
4
|
+
import type { SmartRowArray } from '../utils/smartRowArray';
|
|
5
|
+
export interface TableIterationEnv<T = any> {
|
|
6
|
+
getRowLocators: () => Locator;
|
|
7
|
+
getMap: () => Map<string, number>;
|
|
8
|
+
advancePage: (useBulk: boolean) => Promise<boolean>;
|
|
9
|
+
makeSmartRow: (rowLocator: Locator, map: Map<string, number>, rowIndex: number, tablePageIndex?: number) => SmartRow<T>;
|
|
10
|
+
createSmartRowArray: (rows: SmartRow<T>[]) => SmartRowArray<T>;
|
|
11
|
+
config: FinalTableConfig<T>;
|
|
12
|
+
getPage: () => Page;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Shared row-iteration loop used by forEach, map, and filter.
|
|
16
|
+
*/
|
|
17
|
+
export declare function runForEach<T>(env: TableIterationEnv<T>, callback: (ctx: RowIterationContext<T>) => void | Promise<void>, options?: RowIterationOptions): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Shared row-iteration loop for map.
|
|
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.
|
|
24
|
+
*/
|
|
25
|
+
export declare function runFilter<T>(env: TableIterationEnv<T>, predicate: (ctx: RowIterationContext<T>) => boolean | Promise<boolean>, options?: RowIterationOptions): Promise<SmartRowArray<T>>;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.runForEach = runForEach;
|
|
13
|
+
exports.runMap = runMap;
|
|
14
|
+
exports.runFilter = runFilter;
|
|
15
|
+
const elementTracker_1 = require("../utils/elementTracker");
|
|
16
|
+
/**
|
|
17
|
+
* Shared row-iteration loop used by forEach, map, and filter.
|
|
18
|
+
*/
|
|
19
|
+
function runForEach(env_1, callback_1) {
|
|
20
|
+
return __awaiter(this, arguments, void 0, function* (env, callback, options = {}) {
|
|
21
|
+
var _a, _b, _c, _d;
|
|
22
|
+
const map = env.getMap();
|
|
23
|
+
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : env.config.maxPages;
|
|
24
|
+
const dedupeStrategy = (_b = options.dedupe) !== null && _b !== void 0 ? _b : env.config.strategies.dedupe;
|
|
25
|
+
const dedupeKeys = dedupeStrategy ? new Set() : null;
|
|
26
|
+
const parallel = (_c = options.parallel) !== null && _c !== void 0 ? _c : false;
|
|
27
|
+
const useBulk = (_d = options.useBulkPagination) !== null && _d !== void 0 ? _d : false;
|
|
28
|
+
const tracker = new elementTracker_1.ElementTracker('forEach');
|
|
29
|
+
try {
|
|
30
|
+
let rowIndex = 0;
|
|
31
|
+
let stopped = false;
|
|
32
|
+
let pagesScanned = 1;
|
|
33
|
+
const stop = () => { stopped = true; };
|
|
34
|
+
while (!stopped) {
|
|
35
|
+
const rowLocators = env.getRowLocators();
|
|
36
|
+
const newIndices = yield tracker.getUnseenIndices(rowLocators);
|
|
37
|
+
const pageRows = yield rowLocators.all();
|
|
38
|
+
const smartRows = newIndices.map((idx, i) => env.makeSmartRow(pageRows[idx], map, rowIndex + i));
|
|
39
|
+
if (parallel) {
|
|
40
|
+
yield Promise.all(smartRows.map((row) => __awaiter(this, void 0, void 0, function* () {
|
|
41
|
+
if (stopped)
|
|
42
|
+
return;
|
|
43
|
+
if (dedupeKeys && dedupeStrategy) {
|
|
44
|
+
const key = yield dedupeStrategy(row);
|
|
45
|
+
if (dedupeKeys.has(key))
|
|
46
|
+
return;
|
|
47
|
+
dedupeKeys.add(key);
|
|
48
|
+
}
|
|
49
|
+
yield callback({ row, rowIndex: row.rowIndex, stop });
|
|
50
|
+
})));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
for (const row of smartRows) {
|
|
54
|
+
if (stopped)
|
|
55
|
+
break;
|
|
56
|
+
if (dedupeKeys && dedupeStrategy) {
|
|
57
|
+
const key = yield dedupeStrategy(row);
|
|
58
|
+
if (dedupeKeys.has(key))
|
|
59
|
+
continue;
|
|
60
|
+
dedupeKeys.add(key);
|
|
61
|
+
}
|
|
62
|
+
yield callback({ row, rowIndex: row.rowIndex, stop });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
rowIndex += smartRows.length;
|
|
66
|
+
if (stopped || pagesScanned >= effectiveMaxPages)
|
|
67
|
+
break;
|
|
68
|
+
if (!(yield env.advancePage(useBulk)))
|
|
69
|
+
break;
|
|
70
|
+
pagesScanned++;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
yield tracker.cleanup(env.getPage());
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Shared row-iteration loop for map.
|
|
80
|
+
*/
|
|
81
|
+
function runMap(env_1, callback_1) {
|
|
82
|
+
return __awaiter(this, arguments, void 0, function* (env, callback, options = {}) {
|
|
83
|
+
var _a, _b, _c, _d;
|
|
84
|
+
const map = env.getMap();
|
|
85
|
+
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : env.config.maxPages;
|
|
86
|
+
const dedupeStrategy = (_b = options.dedupe) !== null && _b !== void 0 ? _b : env.config.strategies.dedupe;
|
|
87
|
+
const dedupeKeys = dedupeStrategy ? new Set() : null;
|
|
88
|
+
const parallel = (_c = options.parallel) !== null && _c !== void 0 ? _c : true;
|
|
89
|
+
const useBulk = (_d = options.useBulkPagination) !== null && _d !== void 0 ? _d : false;
|
|
90
|
+
const tracker = new elementTracker_1.ElementTracker('map');
|
|
91
|
+
const results = [];
|
|
92
|
+
const SKIP = Symbol('skip');
|
|
93
|
+
try {
|
|
94
|
+
let rowIndex = 0;
|
|
95
|
+
let stopped = false;
|
|
96
|
+
let pagesScanned = 1;
|
|
97
|
+
const stop = () => { stopped = true; };
|
|
98
|
+
while (!stopped) {
|
|
99
|
+
const rowLocators = env.getRowLocators();
|
|
100
|
+
const newIndices = yield tracker.getUnseenIndices(rowLocators);
|
|
101
|
+
const pageRows = yield rowLocators.all();
|
|
102
|
+
const smartRows = newIndices.map((idx, i) => env.makeSmartRow(pageRows[idx], map, rowIndex + i));
|
|
103
|
+
if (parallel) {
|
|
104
|
+
const pageResults = yield Promise.all(smartRows.map((row) => __awaiter(this, void 0, void 0, function* () {
|
|
105
|
+
if (dedupeKeys && dedupeStrategy) {
|
|
106
|
+
const key = yield dedupeStrategy(row);
|
|
107
|
+
if (dedupeKeys.has(key))
|
|
108
|
+
return SKIP;
|
|
109
|
+
dedupeKeys.add(key);
|
|
110
|
+
}
|
|
111
|
+
return callback({ row, rowIndex: row.rowIndex, stop });
|
|
112
|
+
})));
|
|
113
|
+
for (const r of pageResults) {
|
|
114
|
+
if (r !== SKIP)
|
|
115
|
+
results.push(r);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
for (const row of smartRows) {
|
|
120
|
+
if (stopped)
|
|
121
|
+
break;
|
|
122
|
+
if (dedupeKeys && dedupeStrategy) {
|
|
123
|
+
const key = yield dedupeStrategy(row);
|
|
124
|
+
if (dedupeKeys.has(key))
|
|
125
|
+
continue;
|
|
126
|
+
dedupeKeys.add(key);
|
|
127
|
+
}
|
|
128
|
+
results.push(yield callback({ row, rowIndex: row.rowIndex, stop }));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
rowIndex += smartRows.length;
|
|
132
|
+
if (stopped || pagesScanned >= effectiveMaxPages)
|
|
133
|
+
break;
|
|
134
|
+
if (!(yield env.advancePage(useBulk)))
|
|
135
|
+
break;
|
|
136
|
+
pagesScanned++;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
yield tracker.cleanup(env.getPage());
|
|
141
|
+
}
|
|
142
|
+
return results;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Shared row-iteration loop for filter.
|
|
147
|
+
*/
|
|
148
|
+
function runFilter(env_1, predicate_1) {
|
|
149
|
+
return __awaiter(this, arguments, void 0, function* (env, predicate, options = {}) {
|
|
150
|
+
var _a, _b, _c, _d;
|
|
151
|
+
const map = env.getMap();
|
|
152
|
+
const effectiveMaxPages = (_a = options.maxPages) !== null && _a !== void 0 ? _a : env.config.maxPages;
|
|
153
|
+
const dedupeStrategy = (_b = options.dedupe) !== null && _b !== void 0 ? _b : env.config.strategies.dedupe;
|
|
154
|
+
const dedupeKeys = dedupeStrategy ? new Set() : null;
|
|
155
|
+
const parallel = (_c = options.parallel) !== null && _c !== void 0 ? _c : false;
|
|
156
|
+
const useBulk = (_d = options.useBulkPagination) !== null && _d !== void 0 ? _d : false;
|
|
157
|
+
const tracker = new elementTracker_1.ElementTracker('filter');
|
|
158
|
+
const matched = [];
|
|
159
|
+
try {
|
|
160
|
+
let rowIndex = 0;
|
|
161
|
+
let stopped = false;
|
|
162
|
+
let pagesScanned = 1;
|
|
163
|
+
const stop = () => { stopped = true; };
|
|
164
|
+
while (!stopped) {
|
|
165
|
+
const rowLocators = env.getRowLocators();
|
|
166
|
+
const newIndices = yield tracker.getUnseenIndices(rowLocators);
|
|
167
|
+
const pageRows = yield rowLocators.all();
|
|
168
|
+
const smartRows = newIndices.map((idx, i) => env.makeSmartRow(pageRows[idx], map, rowIndex + i, pagesScanned - 1));
|
|
169
|
+
if (parallel) {
|
|
170
|
+
const flags = yield Promise.all(smartRows.map((row) => __awaiter(this, void 0, void 0, function* () {
|
|
171
|
+
if (dedupeKeys && dedupeStrategy) {
|
|
172
|
+
const key = yield dedupeStrategy(row);
|
|
173
|
+
if (dedupeKeys.has(key))
|
|
174
|
+
return false;
|
|
175
|
+
dedupeKeys.add(key);
|
|
176
|
+
}
|
|
177
|
+
return predicate({ row, rowIndex: row.rowIndex, stop });
|
|
178
|
+
})));
|
|
179
|
+
smartRows.forEach((row, i) => { if (flags[i])
|
|
180
|
+
matched.push(row); });
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
for (const row of smartRows) {
|
|
184
|
+
if (stopped)
|
|
185
|
+
break;
|
|
186
|
+
if (dedupeKeys && dedupeStrategy) {
|
|
187
|
+
const key = yield dedupeStrategy(row);
|
|
188
|
+
if (dedupeKeys.has(key))
|
|
189
|
+
continue;
|
|
190
|
+
dedupeKeys.add(key);
|
|
191
|
+
}
|
|
192
|
+
if (yield predicate({ row, rowIndex: row.rowIndex, stop })) {
|
|
193
|
+
matched.push(row);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
rowIndex += smartRows.length;
|
|
198
|
+
if (stopped || pagesScanned >= effectiveMaxPages)
|
|
199
|
+
break;
|
|
200
|
+
if (!(yield env.advancePage(useBulk)))
|
|
201
|
+
break;
|
|
202
|
+
pagesScanned++;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
yield tracker.cleanup(env.getPage());
|
|
207
|
+
}
|
|
208
|
+
return env.createSmartRowArray(matched);
|
|
209
|
+
});
|
|
210
|
+
}
|
package/dist/filterEngine.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export declare class FilterEngine {
|
|
|
6
6
|
constructor(config: FinalTableConfig, resolve: (selector: any, parent: Locator | Page) => Locator);
|
|
7
7
|
/**
|
|
8
8
|
* Applies filters to a set of rows.
|
|
9
|
+
*
|
|
10
|
+
* Note: `rootLocator` is optional for backward compatibility in call sites that already
|
|
11
|
+
* pass only the page. When strategies.filter is present we construct a TableContext
|
|
12
|
+
* using the provided `rootLocator`.
|
|
9
13
|
*/
|
|
10
|
-
applyFilters(baseRows: Locator, filters: Record<string, FilterValue>, map: Map<string, number>, exact: boolean, page: Page): Locator;
|
|
14
|
+
applyFilters(baseRows: Locator, filters: Record<string, FilterValue>, map: Map<string, number>, exact: boolean, page: Page, rootLocator?: Locator): Locator;
|
|
11
15
|
}
|
package/dist/filterEngine.js
CHANGED
|
@@ -9,31 +9,41 @@ class FilterEngine {
|
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* Applies filters to a set of rows.
|
|
12
|
+
*
|
|
13
|
+
* Note: `rootLocator` is optional for backward compatibility in call sites that already
|
|
14
|
+
* pass only the page. When strategies.filter is present we construct a TableContext
|
|
15
|
+
* using the provided `rootLocator`.
|
|
12
16
|
*/
|
|
13
|
-
applyFilters(baseRows, filters, map, exact, page) {
|
|
17
|
+
applyFilters(baseRows, filters, map, exact, page, rootLocator) {
|
|
18
|
+
var _a;
|
|
14
19
|
let filtered = baseRows;
|
|
15
20
|
// Iterate through each filter criteria
|
|
16
21
|
for (const [colName, value] of Object.entries(filters)) {
|
|
17
22
|
// Find column index
|
|
18
23
|
const colIndex = map.get(colName);
|
|
19
|
-
// TODO: Use ColumnStrategy for better resolution error handling
|
|
20
24
|
if (colIndex === undefined) {
|
|
21
25
|
throw new Error((0, stringUtils_1.buildColumnNotFoundError)(colName, Array.from(map.keys())));
|
|
22
26
|
}
|
|
23
27
|
const filterVal = value;
|
|
24
|
-
//
|
|
25
|
-
|
|
28
|
+
// If a pluggable FilterStrategy is provided, prefer it.
|
|
29
|
+
if (((_a = this.config.strategies) === null || _a === void 0 ? void 0 : _a.filter) && typeof this.config.strategies.filter.apply === 'function') {
|
|
30
|
+
const tableContext = {
|
|
31
|
+
root: rootLocator,
|
|
32
|
+
config: this.config,
|
|
33
|
+
page,
|
|
34
|
+
resolve: this.resolve
|
|
35
|
+
};
|
|
36
|
+
filtered = this.config.strategies.filter.apply({
|
|
37
|
+
rows: filtered,
|
|
38
|
+
filter: { column: colName, value: filterVal },
|
|
39
|
+
colIndex,
|
|
40
|
+
tableContext
|
|
41
|
+
});
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
26
44
|
// Default Filter Logic
|
|
27
45
|
const cellTemplate = this.resolve(this.config.cellSelector, page);
|
|
28
|
-
//
|
|
29
|
-
// At first glance, `cellTemplate.nth(colIndex)` looks like a global page selector
|
|
30
|
-
// that will return the Nth cell on the entire page, rather than the Nth cell in the row.
|
|
31
|
-
// THIS IS INTENTIONAL AND CORRECT.
|
|
32
|
-
// Playwright deeply understands nested locator scoping. When this global-looking locator
|
|
33
|
-
// is passed into `filtered.filter({ has: ... })` below, Playwright magically and
|
|
34
|
-
// automatically re-bases the `nth()` selector to be strictly relative to the ROW being evaluated.
|
|
35
|
-
// Attempting to manually force generic relative locators here will break complex function
|
|
36
|
-
// selectors and introduce regressions. Leave it as is.
|
|
46
|
+
// Playwright scoping: `cellTemplate.nth(colIndex)` will be re-based when used in filtered.filter({ has: ... })
|
|
37
47
|
const targetCell = cellTemplate.nth(colIndex);
|
|
38
48
|
if (typeof filterVal === 'function') {
|
|
39
49
|
// Locator-based filter: (cell) => cell.locator(...)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { useTable } from './useTable';
|
|
2
|
-
export type { TableConfig, TableResult, SmartRow, Selector, FilterValue, PaginationPrimitives, SortingStrategy, FillOptions, RowIterationContext, RowIterationOptions, TableContext, StrategyContext, BeforeCellReadFn, GetCellLocatorFn, GetActiveCellFn, } from './types';
|
|
2
|
+
export type { TableConfig, TableResult, SmartRow, Selector, FilterValue, PaginationPrimitives, SortingStrategy, FillOptions, RowIterationContext, RowIterationOptions, TableContext, StrategyContext, BeforeCellReadFn, GetCellLocatorFn, GetActiveCellFn, DebugConfig, } from './types';
|
|
3
3
|
export { Strategies } from './strategies';
|
|
4
4
|
export { Plugins } from './plugins';
|
|
@@ -34,10 +34,8 @@ exports.glideGoRight = glideGoRight;
|
|
|
34
34
|
const glideGoHome = (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
35
35
|
const { root, page } = context;
|
|
36
36
|
// Glide renders to canvas - the accessibility table (root) is inside the canvas
|
|
37
|
-
// We need to find and focus the canvas element that contains our root
|
|
38
37
|
yield root.evaluate((el) => {
|
|
39
38
|
var _a;
|
|
40
|
-
// Find the closest canvas ancestor
|
|
41
39
|
const canvas = el.closest('canvas') || ((_a = el.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector('canvas'));
|
|
42
40
|
if (canvas instanceof HTMLCanvasElement) {
|
|
43
41
|
canvas.tabIndex = 0;
|
|
@@ -45,10 +43,9 @@ const glideGoHome = (context) => __awaiter(void 0, void 0, void 0, function* ()
|
|
|
45
43
|
}
|
|
46
44
|
});
|
|
47
45
|
yield page.waitForTimeout(100);
|
|
48
|
-
// Reset to top-left - Cross-OS sequence (Mac/Windows)
|
|
49
46
|
yield page.keyboard.press('Control+Home');
|
|
50
|
-
yield page.keyboard.press('Meta+ArrowUp');
|
|
51
|
-
yield page.keyboard.press('Home');
|
|
47
|
+
yield page.keyboard.press('Meta+ArrowUp');
|
|
48
|
+
yield page.keyboard.press('Home');
|
|
52
49
|
yield page.waitForTimeout(150);
|
|
53
50
|
});
|
|
54
51
|
exports.glideGoHome = glideGoHome;
|
|
@@ -24,10 +24,8 @@ const scrollRightHeader = (context, options) => __awaiter(void 0, void 0, void 0
|
|
|
24
24
|
const texts = yield headerLoc.allInnerTexts();
|
|
25
25
|
return texts.map(t => t.trim());
|
|
26
26
|
});
|
|
27
|
-
// Initial capture
|
|
28
27
|
let currentHeaders = yield getVisible();
|
|
29
28
|
currentHeaders.forEach(h => collectedHeaders.add(h));
|
|
30
|
-
// Find scroller using JS for better iframe/shadow support
|
|
31
29
|
const scrollerHandle = yield root.evaluateHandle((el, selector) => {
|
|
32
30
|
if (selector && el.matches(selector))
|
|
33
31
|
return el;
|
|
@@ -60,7 +58,6 @@ const scrollRightHeader = (context, options) => __awaiter(void 0, void 0, void 0
|
|
|
60
58
|
else {
|
|
61
59
|
console.warn("HeaderStrategies.scrollRight: Could not find scroller. Returning visible headers.");
|
|
62
60
|
}
|
|
63
|
-
// Scroll back to start
|
|
64
61
|
yield scrollerHandle.evaluate(el => el.scrollLeft = 0);
|
|
65
62
|
yield page.waitForTimeout(200);
|
|
66
63
|
return Array.from(collectedHeaders);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { FillStrategy, TableConfig } from '../../types';
|
|
2
|
+
/** Strategies only for Glide Data Grid. Includes fillSimple; use when you want to supply your own selectors or override fill. */
|
|
3
|
+
export declare const GlideStrategies: {
|
|
4
|
+
fillSimple: FillStrategy;
|
|
5
|
+
fill: FillStrategy;
|
|
6
|
+
pagination: import("../../types").PaginationPrimitives;
|
|
7
|
+
header: (context: import("../../types").StrategyContext, options?: {
|
|
8
|
+
limit?: number;
|
|
9
|
+
selector?: string;
|
|
10
|
+
scrollAmount?: number;
|
|
11
|
+
}) => Promise<string[]>;
|
|
12
|
+
navigation: {
|
|
13
|
+
goUp: (context: import("../../types").StrategyContext) => Promise<void>;
|
|
14
|
+
goDown: (context: import("../../types").StrategyContext) => Promise<void>;
|
|
15
|
+
goLeft: (context: import("../../types").StrategyContext) => Promise<void>;
|
|
16
|
+
goRight: (context: import("../../types").StrategyContext) => Promise<void>;
|
|
17
|
+
goHome: (context: import("../../types").StrategyContext) => Promise<void>;
|
|
18
|
+
};
|
|
19
|
+
loading: {
|
|
20
|
+
isHeaderLoading: () => Promise<boolean>;
|
|
21
|
+
};
|
|
22
|
+
getCellLocator: ({ row, columnIndex }: any) => any;
|
|
23
|
+
getActiveCell: ({ page }: any) => Promise<{
|
|
24
|
+
rowIndex: number;
|
|
25
|
+
columnIndex: number;
|
|
26
|
+
locator: any;
|
|
27
|
+
} | null>;
|
|
28
|
+
};
|
|
29
|
+
export declare const Glide: Partial<TableConfig> & {
|
|
30
|
+
Strategies: typeof GlideStrategies;
|
|
31
|
+
};
|
|
@@ -9,23 +9,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.
|
|
13
|
-
const columns_1 = require("./
|
|
14
|
-
const headers_1 = require("./
|
|
15
|
-
const pagination_1 = require("
|
|
16
|
-
const stabilization_1 = require("
|
|
17
|
-
/**
|
|
18
|
-
* Fill strategy for Glide Data Grid with textarea validation.
|
|
19
|
-
* This is the default strategy that works with the standard Glide Data Grid editor.
|
|
20
|
-
*/
|
|
12
|
+
exports.Glide = exports.GlideStrategies = void 0;
|
|
13
|
+
const columns_1 = require("./columns");
|
|
14
|
+
const headers_1 = require("./headers");
|
|
15
|
+
const pagination_1 = require("../../strategies/pagination");
|
|
16
|
+
const stabilization_1 = require("../../strategies/stabilization");
|
|
21
17
|
const glideFillStrategy = (_a) => __awaiter(void 0, [_a], void 0, function* ({ value, page }) {
|
|
22
|
-
// Edit Cell
|
|
23
18
|
yield page.keyboard.press('Enter');
|
|
24
|
-
// Wait for editor to appear
|
|
25
19
|
const textarea = page.locator('textarea.gdg-input');
|
|
26
20
|
yield textarea.waitFor({ state: 'visible', timeout: 2000 });
|
|
27
21
|
yield page.keyboard.type(String(value));
|
|
28
|
-
// Wait for textarea value to match what we typed
|
|
29
22
|
yield textarea.evaluate((el, expectedValue) => {
|
|
30
23
|
return new Promise((resolve) => {
|
|
31
24
|
const checkValue = () => {
|
|
@@ -39,74 +32,48 @@ const glideFillStrategy = (_a) => __awaiter(void 0, [_a], void 0, function* ({ v
|
|
|
39
32
|
checkValue();
|
|
40
33
|
});
|
|
41
34
|
}, String(value));
|
|
42
|
-
// Small delay to let the grid process the value
|
|
43
35
|
yield page.waitForTimeout(50);
|
|
44
36
|
yield page.keyboard.press('Enter');
|
|
45
|
-
// Wait for editor to close (commit completed)
|
|
46
37
|
yield textarea.waitFor({ state: 'detached', timeout: 2000 });
|
|
47
|
-
// Wait for accessibility layer to sync with canvas state
|
|
48
38
|
yield page.waitForTimeout(300);
|
|
49
39
|
});
|
|
50
|
-
exports.glideFillStrategy = glideFillStrategy;
|
|
51
|
-
/**
|
|
52
|
-
* Simple fill strategy for Glide Data Grid.
|
|
53
|
-
* Use this if your Glide implementation doesn't use the standard textarea editor.
|
|
54
|
-
* This is faster but may not work for all Glide configurations.
|
|
55
|
-
*/
|
|
56
40
|
const glideFillSimple = (_a) => __awaiter(void 0, [_a], void 0, function* ({ value, page }) {
|
|
57
41
|
yield page.keyboard.press('Enter');
|
|
58
42
|
yield page.keyboard.type(String(value));
|
|
59
43
|
yield page.keyboard.press('Enter');
|
|
60
44
|
});
|
|
61
|
-
|
|
62
|
-
exports.glidePaginationStrategy = pagination_1.PaginationStrategies.infiniteScroll({
|
|
45
|
+
const glidePaginationStrategy = pagination_1.PaginationStrategies.infiniteScroll({
|
|
63
46
|
scrollTarget: 'xpath=//ancestor::body//div[contains(@class, "dvn-scroller")]',
|
|
64
47
|
scrollAmount: 500,
|
|
65
48
|
action: 'js-scroll',
|
|
66
49
|
stabilization: stabilization_1.StabilizationStrategies.contentChanged({ timeout: 5000 }),
|
|
67
|
-
timeout: 5000
|
|
50
|
+
timeout: 5000
|
|
68
51
|
});
|
|
69
52
|
const glideGetCellLocator = ({ row, columnIndex }) => {
|
|
70
|
-
// Use relative locator to support virtualization (where rowIndex resets or is offsets)
|
|
71
|
-
// The accessibility DOM usually contains 'td' elements with the data.
|
|
72
53
|
return row.locator('td').nth(columnIndex);
|
|
73
54
|
};
|
|
74
|
-
exports.glideGetCellLocator = glideGetCellLocator;
|
|
75
55
|
const glideGetActiveCell = (_a) => __awaiter(void 0, [_a], void 0, function* ({ page }) {
|
|
76
|
-
// Find the focused cell/element
|
|
77
|
-
// Use broad selector for focused element
|
|
78
56
|
const focused = page.locator('*:focus').first();
|
|
79
57
|
if ((yield focused.count()) === 0)
|
|
80
58
|
return null;
|
|
81
|
-
// Debug log
|
|
82
|
-
if (process.env.DEBUG)
|
|
83
|
-
console.log('Found focused element:', yield focused.evaluate((e) => e.outerHTML));
|
|
84
|
-
// Try to extract position from ID if possible
|
|
85
59
|
const id = (yield focused.getAttribute('id')) || '';
|
|
86
|
-
// Expected format: glide-cell-COL-ROW
|
|
87
60
|
const parts = id.split('-');
|
|
88
61
|
let rowIndex = -1;
|
|
89
62
|
let columnIndex = -1;
|
|
90
63
|
if (parts.length >= 4 && parts[0] === 'glide' && parts[1] === 'cell') {
|
|
91
|
-
columnIndex = parseInt(parts[2]) - 1;
|
|
64
|
+
columnIndex = parseInt(parts[2]) - 1;
|
|
92
65
|
rowIndex = parseInt(parts[3]);
|
|
93
66
|
}
|
|
94
|
-
else {
|
|
95
|
-
// Fallback: If we can't parse ID, we assume it's the correct cell
|
|
96
|
-
// because we just navigated to it.
|
|
97
|
-
// Returning -1 indices might be confusing but won't stop smartRow from using the locator.
|
|
98
|
-
}
|
|
99
67
|
return {
|
|
100
68
|
rowIndex,
|
|
101
69
|
columnIndex,
|
|
102
70
|
locator: focused
|
|
103
71
|
};
|
|
104
72
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
fill:
|
|
108
|
-
|
|
109
|
-
pagination: exports.glidePaginationStrategy,
|
|
73
|
+
/** Default strategies for the Glide preset (fill only; no fillSimple). */
|
|
74
|
+
const GlideDefaultStrategies = {
|
|
75
|
+
fill: glideFillStrategy,
|
|
76
|
+
pagination: glidePaginationStrategy,
|
|
110
77
|
header: headers_1.scrollRightHeader,
|
|
111
78
|
navigation: {
|
|
112
79
|
goUp: columns_1.glideGoUp,
|
|
@@ -116,8 +83,22 @@ exports.GlideStrategies = {
|
|
|
116
83
|
goHome: columns_1.glideGoHome
|
|
117
84
|
},
|
|
118
85
|
loading: {
|
|
119
|
-
isHeaderLoading: () => __awaiter(void 0, void 0, void 0, function* () { return false; })
|
|
86
|
+
isHeaderLoading: () => __awaiter(void 0, void 0, void 0, function* () { return false; })
|
|
120
87
|
},
|
|
121
|
-
getCellLocator:
|
|
122
|
-
getActiveCell:
|
|
88
|
+
getCellLocator: glideGetCellLocator,
|
|
89
|
+
getActiveCell: glideGetActiveCell
|
|
90
|
+
};
|
|
91
|
+
/** Strategies only for Glide Data Grid. Includes fillSimple; use when you want to supply your own selectors or override fill. */
|
|
92
|
+
exports.GlideStrategies = Object.assign(Object.assign({}, GlideDefaultStrategies), { fillSimple: glideFillSimple });
|
|
93
|
+
/**
|
|
94
|
+
* Full preset for Glide Data Grid (selectors + default strategies only).
|
|
95
|
+
* Spread: useTable(loc, { ...Plugins.Glide, maxPages: 5 }).
|
|
96
|
+
* Strategies only (including fillSimple): useTable(loc, { rowSelector: '...', strategies: Plugins.Glide.Strategies }).
|
|
97
|
+
*/
|
|
98
|
+
const GlidePreset = {
|
|
99
|
+
headerSelector: 'table[role="grid"] thead tr th',
|
|
100
|
+
rowSelector: 'table[role="grid"] tbody tr',
|
|
101
|
+
cellSelector: 'td',
|
|
102
|
+
strategies: GlideDefaultStrategies
|
|
123
103
|
};
|
|
104
|
+
exports.Glide = Object.defineProperty(GlidePreset, 'Strategies', { get: () => exports.GlideStrategies, enumerable: false });
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presets for specific table/grid libraries. Each plugin exposes:
|
|
3
|
+
* - Plugins.X — full preset (selectors + headerTransformer if any + strategies). Spread: useTable(loc, { ...Plugins.MUI, maxPages: 5 }).
|
|
4
|
+
* - Plugins.X.Strategies — strategies only. Use with your own selectors: useTable(loc, { rowSelector: '...', strategies: Plugins.MUI.Strategies }).
|
|
5
|
+
*/
|
|
6
|
+
export declare const Plugins: {
|
|
7
|
+
RDG: Partial<import("..").TableConfig<any>> & {
|
|
8
|
+
Strategies: typeof import("./rdg").RDGStrategies;
|
|
9
|
+
};
|
|
10
|
+
Glide: Partial<import("..").TableConfig<any>> & {
|
|
11
|
+
Strategies: typeof import("./glide").GlideStrategies;
|
|
12
|
+
};
|
|
13
|
+
MUI: Partial<import("..").TableConfig<any>> & {
|
|
14
|
+
Strategies: typeof import("./mui").MUIStrategies;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Plugins = void 0;
|
|
4
|
+
const rdg_1 = require("./rdg");
|
|
5
|
+
const glide_1 = require("./glide");
|
|
6
|
+
const mui_1 = require("./mui");
|
|
7
|
+
/**
|
|
8
|
+
* Presets for specific table/grid libraries. Each plugin exposes:
|
|
9
|
+
* - Plugins.X — full preset (selectors + headerTransformer if any + strategies). Spread: useTable(loc, { ...Plugins.MUI, maxPages: 5 }).
|
|
10
|
+
* - Plugins.X.Strategies — strategies only. Use with your own selectors: useTable(loc, { rowSelector: '...', strategies: Plugins.MUI.Strategies }).
|
|
11
|
+
*/
|
|
12
|
+
exports.Plugins = {
|
|
13
|
+
RDG: rdg_1.RDG,
|
|
14
|
+
Glide: glide_1.Glide,
|
|
15
|
+
MUI: mui_1.MUI,
|
|
16
|
+
};
|