@rickcedwhat/playwright-smart-table 6.24.0 → 6.25.0-next.49ce2de
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/countRows.d.ts +27 -0
- package/dist/engine/countRows.js +139 -0
- package/dist/engine/rowByIndex.d.ts +19 -0
- package/dist/engine/rowByIndex.js +70 -0
- package/dist/engine/rowFinder.d.ts +2 -0
- package/dist/engine/rowFinder.js +13 -4
- package/dist/engine/scanPages.d.ts +2 -2
- package/dist/engine/sorting.d.ts +11 -0
- package/dist/engine/sorting.js +51 -0
- package/dist/engine/tableIteration.js +2 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/presets/glide/index.js +0 -1
- package/dist/presets/rdg.js +0 -2
- package/dist/smartRow.js +39 -29
- package/dist/strategies/contentReady.d.ts +0 -1
- package/dist/strategies/index.d.ts +2 -3
- package/dist/strategies/resolution.d.ts +1 -3
- package/dist/strategies/resolution.js +0 -28
- package/dist/types.d.ts +12 -2
- package/dist/useTable.js +51 -241
- package/dist/utils/locatorDescription.d.ts +15 -0
- package/dist/utils/locatorDescription.js +56 -0
- package/dist/utils/navigationBarrier.d.ts +0 -2
- package/dist/utils/navigationBarrier.js +1 -4
- package/dist/utils/safeStringify.d.ts +2 -0
- package/dist/utils/safeStringify.js +18 -0
- package/package.json +5 -6
- package/dist/typeContext.d.ts +0 -6
- package/dist/typeContext.js +0 -1133
- package/dist/utils.d.ts +0 -7
- package/dist/utils.js +0 -21
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Locator } from '@playwright/test';
|
|
2
|
+
import type { FilterValue, FinalTableConfig, TableContext } from '../types';
|
|
3
|
+
import type { FilterEngine } from '../filterEngine';
|
|
4
|
+
import type { TableMapper } from './tableMapper';
|
|
5
|
+
import type { RowFinder } from './rowFinder';
|
|
6
|
+
export interface CountRowsEnv<T = any> {
|
|
7
|
+
config: FinalTableConfig<T>;
|
|
8
|
+
rootLocator: Locator;
|
|
9
|
+
getRowLocators: () => Locator;
|
|
10
|
+
tableMapper: TableMapper;
|
|
11
|
+
filterEngine: FilterEngine;
|
|
12
|
+
rowFinder: RowFinder<T>;
|
|
13
|
+
advancePage: (useBulk: boolean) => Promise<boolean>;
|
|
14
|
+
getCurrentPageIndex: () => number;
|
|
15
|
+
getContext: () => TableContext;
|
|
16
|
+
/** Restores page 1 after a paginated count. */
|
|
17
|
+
reset: () => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Counts matching rows on the current page, or across pages when pagination and
|
|
21
|
+
* `maxPages > 1` allow it. A paginated count always resets to page 1 afterwards (#348).
|
|
22
|
+
* Callers must have initialized the table.
|
|
23
|
+
*/
|
|
24
|
+
export declare function countRows<T>(env: CountRowsEnv<T>, filters?: Record<string, FilterValue>, options?: {
|
|
25
|
+
exact?: boolean;
|
|
26
|
+
maxPages?: number;
|
|
27
|
+
}): Promise<number>;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.countRows = countRows;
|
|
4
|
+
const scanPages_1 = require("./scanPages");
|
|
5
|
+
const elementTracker_1 = require("../utils/elementTracker");
|
|
6
|
+
const debugUtils_1 = require("../utils/debugUtils");
|
|
7
|
+
const loadingWait_1 = require("../utils/loadingWait");
|
|
8
|
+
const safeStringify_1 = require("../utils/safeStringify");
|
|
9
|
+
function log(config, msg) {
|
|
10
|
+
(0, debugUtils_1.logDebug)(config, 'verbose', msg);
|
|
11
|
+
}
|
|
12
|
+
/** Splits filters by how they're evaluated: DOM text, `columnOverrides.read`, or synthetic columns. */
|
|
13
|
+
function partitionFilters(config, filters) {
|
|
14
|
+
var _a, _b, _c;
|
|
15
|
+
const domFilters = {};
|
|
16
|
+
const overrideFilters = {};
|
|
17
|
+
const syntheticFilters = {};
|
|
18
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
19
|
+
if ((_a = config.syntheticColumns) === null || _a === void 0 ? void 0 : _a[key]) {
|
|
20
|
+
syntheticFilters[key] = value;
|
|
21
|
+
}
|
|
22
|
+
else if ((_c = (_b = config.columnOverrides) === null || _b === void 0 ? void 0 : _b[key]) === null || _c === void 0 ? void 0 : _c.read) {
|
|
23
|
+
overrideFilters[key] = value;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
domFilters[key] = value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { domFilters, overrideFilters, syntheticFilters };
|
|
30
|
+
}
|
|
31
|
+
function requireMap(tableMapper) {
|
|
32
|
+
const map = tableMapper.getMapSync();
|
|
33
|
+
if (!map)
|
|
34
|
+
throw new Error('Initialization Error: Table map not available. Call "await table.init()" first.');
|
|
35
|
+
return map;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Counts matching rows on the current page, or across pages when pagination and
|
|
39
|
+
* `maxPages > 1` allow it. A paginated count always resets to page 1 afterwards (#348).
|
|
40
|
+
* Callers must have initialized the table.
|
|
41
|
+
*/
|
|
42
|
+
async function countRows(env, filters, options) {
|
|
43
|
+
var _a;
|
|
44
|
+
var _b, _c;
|
|
45
|
+
const { config, rootLocator, tableMapper, rowFinder } = env;
|
|
46
|
+
const filtersRecord = filters !== null && filters !== void 0 ? filters : {};
|
|
47
|
+
const hasFilters = Object.keys(filtersRecord).length > 0;
|
|
48
|
+
const effectiveMaxPages = (_b = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _b !== void 0 ? _b : config.maxPages;
|
|
49
|
+
const pag = config.strategies.pagination;
|
|
50
|
+
const hasPagination = effectiveMaxPages > 1 && !!((pag === null || pag === void 0 ? void 0 : pag.goNext) || (pag === null || pag === void 0 ? void 0 : pag.goNextBulk));
|
|
51
|
+
const exact = (_c = options === null || options === void 0 ? void 0 : options.exact) !== null && _c !== void 0 ? _c : false;
|
|
52
|
+
const { domFilters, overrideFilters, syntheticFilters } = partitionFilters(config, filtersRecord);
|
|
53
|
+
const hasOverrides = Object.keys(overrideFilters).length > 0;
|
|
54
|
+
const hasSynthetics = Object.keys(syntheticFilters).length > 0;
|
|
55
|
+
const hasPostFilters = hasOverrides || hasSynthetics;
|
|
56
|
+
const resolveRows = () => {
|
|
57
|
+
const rows = env.getRowLocators();
|
|
58
|
+
if (Object.keys(domFilters).length === 0)
|
|
59
|
+
return rows;
|
|
60
|
+
return env.filterEngine.applyFilters(rows, domFilters, requireMap(tableMapper), exact, rootLocator.page(), rootLocator);
|
|
61
|
+
};
|
|
62
|
+
const countPostFilterMatches = async (candidates, indices) => {
|
|
63
|
+
if (!hasPostFilters)
|
|
64
|
+
return indices.length;
|
|
65
|
+
const map = requireMap(tableMapper);
|
|
66
|
+
let count = 0;
|
|
67
|
+
for (const idx of indices) {
|
|
68
|
+
const row = candidates[idx];
|
|
69
|
+
if (hasOverrides && !await rowFinder.matchesOverrideFilters(row, overrideFilters, map, exact))
|
|
70
|
+
continue;
|
|
71
|
+
if (hasSynthetics && !await rowFinder.matchesSyntheticFilters(row, syntheticFilters, map, exact))
|
|
72
|
+
continue;
|
|
73
|
+
count++;
|
|
74
|
+
}
|
|
75
|
+
return count;
|
|
76
|
+
};
|
|
77
|
+
const isTableLoading = (_a = config.strategies.loading) === null || _a === void 0 ? void 0 : _a.isTableLoading;
|
|
78
|
+
const waitForReady = async () => {
|
|
79
|
+
if (!isTableLoading)
|
|
80
|
+
return;
|
|
81
|
+
await (0, loadingWait_1.waitWhileTableLoading)(config, env.getContext(), rootLocator.page(), 'countRows');
|
|
82
|
+
};
|
|
83
|
+
await waitForReady();
|
|
84
|
+
const filterSuffix = hasFilters ? ` filters=${(0, safeStringify_1.safeStringify)(filtersRecord)}` : '';
|
|
85
|
+
if (!hasPagination) {
|
|
86
|
+
log(config, `countRows: counting rows in current viewport (no pagination)${filterSuffix}`);
|
|
87
|
+
if (!hasPostFilters)
|
|
88
|
+
return resolveRows().count();
|
|
89
|
+
const all = await resolveRows().all();
|
|
90
|
+
return countPostFilterMatches(all, all.map((_, i) => i));
|
|
91
|
+
}
|
|
92
|
+
log(config, `countRows: paginating up to ${effectiveMaxPages} page(s)${filterSuffix}`);
|
|
93
|
+
const tracker = new elementTracker_1.ElementTracker('countRows');
|
|
94
|
+
let total = 0;
|
|
95
|
+
let pagesScanned = 1;
|
|
96
|
+
let paginationError;
|
|
97
|
+
try {
|
|
98
|
+
const scanned = await (0, scanPages_1.scanPages)({
|
|
99
|
+
advancePage: env.advancePage,
|
|
100
|
+
getCurrentPageIndex: env.getCurrentPageIndex,
|
|
101
|
+
config,
|
|
102
|
+
waitForReady,
|
|
103
|
+
}, async ({ pagesScanned: pageNum }) => {
|
|
104
|
+
const rowLocators = resolveRows();
|
|
105
|
+
const newIndices = await tracker.getUnseenIndices(rowLocators);
|
|
106
|
+
const candidates = await rowLocators.all();
|
|
107
|
+
const matched = await countPostFilterMatches(candidates, newIndices);
|
|
108
|
+
total += matched;
|
|
109
|
+
log(config, `countRows: page ${pageNum} — ${matched} row(s) (running total: ${total})`);
|
|
110
|
+
return 'continue';
|
|
111
|
+
}, {
|
|
112
|
+
maxPages: effectiveMaxPages,
|
|
113
|
+
useBulk: false,
|
|
114
|
+
label: 'countRows',
|
|
115
|
+
finalScanOnEof: true,
|
|
116
|
+
});
|
|
117
|
+
pagesScanned = scanned.pagesScanned;
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
paginationError = e;
|
|
121
|
+
throw e;
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
await tracker.cleanup(rootLocator.page());
|
|
125
|
+
// Always restore page 1, even if pagination threw mid-count (#348), so the table
|
|
126
|
+
// stays usable for callers that catch the error. If reset() itself fails, only let
|
|
127
|
+
// it surface when pagination succeeded — otherwise preserve the original error.
|
|
128
|
+
log(config, `countRows: ${total} total row(s) across ${pagesScanned} page(s) — resetting`);
|
|
129
|
+
try {
|
|
130
|
+
await env.reset();
|
|
131
|
+
}
|
|
132
|
+
catch (resetError) {
|
|
133
|
+
if (paginationError === undefined)
|
|
134
|
+
throw resetError;
|
|
135
|
+
(0, debugUtils_1.logDebug)(config, 'error', 'countRows: reset() failed after pagination error', resetError);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return total;
|
|
139
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Locator } from '@playwright/test';
|
|
2
|
+
import type { FinalTableConfig, SmartRow, TableContext } from '../types';
|
|
3
|
+
export interface RowByIndexEnv<T = any> {
|
|
4
|
+
config: FinalTableConfig<T>;
|
|
5
|
+
getRowLocators: () => Locator;
|
|
6
|
+
getMap: () => Map<string, number>;
|
|
7
|
+
advancePage: (useBulk: boolean) => Promise<boolean>;
|
|
8
|
+
getCurrentPageIndex: () => number;
|
|
9
|
+
getContext: () => TableContext;
|
|
10
|
+
makeSmartRow: (rowLocator: Locator, map: Map<string, number>, rowIndex: number, tablePageIndex: number, healSelector?: string) => SmartRow<T>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Finds the row whose logical index (per `strategies.resolveRowIndex`) is `index`:
|
|
14
|
+
* already mounted, else via `viewport.scrollToRow`, else by advancing pages until it
|
|
15
|
+
* mounts (bounded by `maxPages`). Callers must have initialized the table.
|
|
16
|
+
*/
|
|
17
|
+
export declare function findRowByIndex<T>(env: RowByIndexEnv<T>, index: number, options?: {
|
|
18
|
+
maxPages?: number;
|
|
19
|
+
}): Promise<SmartRow<T>>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findRowByIndex = findRowByIndex;
|
|
4
|
+
const rowResolution_1 = require("./rowResolution");
|
|
5
|
+
const scanPages_1 = require("./scanPages");
|
|
6
|
+
const debugUtils_1 = require("../utils/debugUtils");
|
|
7
|
+
const safeStringify_1 = require("../utils/safeStringify");
|
|
8
|
+
/**
|
|
9
|
+
* Finds the row whose logical index (per `strategies.resolveRowIndex`) is `index`:
|
|
10
|
+
* already mounted, else via `viewport.scrollToRow`, else by advancing pages until it
|
|
11
|
+
* mounts (bounded by `maxPages`). Callers must have initialized the table.
|
|
12
|
+
*/
|
|
13
|
+
async function findRowByIndex(env, index, options) {
|
|
14
|
+
var _a;
|
|
15
|
+
const { config } = env;
|
|
16
|
+
(0, debugUtils_1.logDebug)(config, 'verbose', `findRowByIndex: index=${index} options=${(0, safeStringify_1.safeStringify)(options)}`);
|
|
17
|
+
const resolveRI = config.strategies.resolveRowIndex;
|
|
18
|
+
const viewport = config.strategies.viewport;
|
|
19
|
+
// Identifying a row by its logical index requires a resolveRowIndex strategy. Without one
|
|
20
|
+
// the library can only offer render-window-relative getRowByIndex, or filter-based findRow.
|
|
21
|
+
if (!resolveRI) {
|
|
22
|
+
throw new Error(`findRowByIndex(${index}) requires a strategies.resolveRowIndex to identify rows by their logical index. ` +
|
|
23
|
+
`Configure one (e.g. read the grid's row-index attribute), or use getRowByIndex() (current render window) / findRow(filters).`);
|
|
24
|
+
}
|
|
25
|
+
let foundSelector;
|
|
26
|
+
// The currently-mounted row whose logical index === index, if any.
|
|
27
|
+
const findMounted = async () => {
|
|
28
|
+
const rows = await env.getRowLocators().all();
|
|
29
|
+
for (const r of rows) {
|
|
30
|
+
const result = await resolveRI(r);
|
|
31
|
+
if (result === undefined)
|
|
32
|
+
continue;
|
|
33
|
+
const normalized = (0, rowResolution_1.normalizeRowIndexResult)(result);
|
|
34
|
+
if (normalized.index === index) {
|
|
35
|
+
foundSelector = normalized.selector;
|
|
36
|
+
return r;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
// 1. Already mounted (no navigation needed)?
|
|
42
|
+
let loc = await findMounted();
|
|
43
|
+
// 2. Fast path: jump straight to it via the viewport's random-access scroll.
|
|
44
|
+
if (!loc && (viewport === null || viewport === void 0 ? void 0 : viewport.scrollToRow)) {
|
|
45
|
+
(0, debugUtils_1.logDebug)(config, 'verbose', `findRowByIndex: scrolling to row ${index}`);
|
|
46
|
+
await viewport.scrollToRow(env.getContext(), index);
|
|
47
|
+
loc = await findMounted();
|
|
48
|
+
}
|
|
49
|
+
// 3. Fallback: advance pages (on infinite-scroll tables, a "page" is a scroll step) until
|
|
50
|
+
// the row is mounted. Page 1 was already searched above.
|
|
51
|
+
const maxPages = (_a = options === null || options === void 0 ? void 0 : options.maxPages) !== null && _a !== void 0 ? _a : config.maxPages;
|
|
52
|
+
const scanForMounted = async () => {
|
|
53
|
+
let hit = null;
|
|
54
|
+
await (0, scanPages_1.scanPages)({ advancePage: env.advancePage, getCurrentPageIndex: env.getCurrentPageIndex, config }, async ({ pagesScanned }) => {
|
|
55
|
+
if (pagesScanned > 1)
|
|
56
|
+
hit = await findMounted();
|
|
57
|
+
return hit ? 'stop' : 'continue';
|
|
58
|
+
}, { maxPages, useBulk: false, label: 'findRowByIndex', finalScanOnEof: false });
|
|
59
|
+
return hit;
|
|
60
|
+
};
|
|
61
|
+
if (!loc && config.strategies.pagination) {
|
|
62
|
+
loc = await scanForMounted();
|
|
63
|
+
}
|
|
64
|
+
if (!loc) {
|
|
65
|
+
throw new Error(`findRowByIndex(${index}): could not reach row ${index}. ` +
|
|
66
|
+
`Ensure a viewport.scrollToRow or pagination can bring it into view (searched up to maxPages=${maxPages}).`);
|
|
67
|
+
}
|
|
68
|
+
await (0, debugUtils_1.debugDelay)(config, 'findRow');
|
|
69
|
+
return env.makeSmartRow(loc, env.getMap(), index, env.getCurrentPageIndex(), foundSelector);
|
|
70
|
+
}
|
|
@@ -25,6 +25,8 @@ export declare class RowFinder<T = any> {
|
|
|
25
25
|
maxPages?: number;
|
|
26
26
|
}): Promise<SmartRow<T>>;
|
|
27
27
|
private waitForTableReady;
|
|
28
|
+
/** Unfiltered rows keep the plain `SmartRow #n` name that `_makeSmart` applies. */
|
|
29
|
+
private nameFilteredRow;
|
|
28
30
|
findRows(filters?: Record<string, FilterValue>, options?: {
|
|
29
31
|
exact?: boolean;
|
|
30
32
|
maxPages?: number;
|
package/dist/engine/rowFinder.js
CHANGED
|
@@ -9,6 +9,7 @@ const navigationBarrier_1 = require("../utils/navigationBarrier");
|
|
|
9
9
|
const rowResolution_1 = require("./rowResolution");
|
|
10
10
|
const resolveCellLocator_1 = require("../utils/resolveCellLocator");
|
|
11
11
|
const scanPages_1 = require("./scanPages");
|
|
12
|
+
const locatorDescription_1 = require("../utils/locatorDescription");
|
|
12
13
|
const loadingWait_1 = require("../utils/loadingWait");
|
|
13
14
|
class RowFinder {
|
|
14
15
|
constructor(rootLocator, config, resolve, filterEngine, tableMapper, makeSmartRow, tableState = { currentPageIndex: 0 }, advancePage = async () => false) {
|
|
@@ -111,12 +112,13 @@ class RowFinder {
|
|
|
111
112
|
await (0, debugUtils_1.debugDelay)(this.config, 'findRow');
|
|
112
113
|
const map = await this.tableMapper.getMap();
|
|
113
114
|
const resolved = await this.resolveRowIndex(rowLocator);
|
|
114
|
-
|
|
115
|
+
const describedRow = (0, locatorDescription_1.describeLocator)(rowLocator, (0, locatorDescription_1.describeRowByFilters)(filters), this.config);
|
|
116
|
+
return this.makeSmartRow(describedRow, map, resolved === null || resolved === void 0 ? void 0 : resolved.index, this.tableState.currentPageIndex, undefined, resolved === null || resolved === void 0 ? void 0 : resolved.selector);
|
|
115
117
|
}
|
|
116
118
|
(0, debugUtils_1.logDebug)(this.config, 'error', 'Row not found', filters);
|
|
117
119
|
await (0, debugUtils_1.debugDelay)(this.config, 'findRow');
|
|
118
|
-
const sentinel = this.resolve(this.config.rowSelector, this.rootLocator)
|
|
119
|
-
.filter({ hasText: "___SENTINEL_ROW_NOT_FOUND___" + Date.now() });
|
|
120
|
+
const sentinel = (0, locatorDescription_1.describeLocator)(this.resolve(this.config.rowSelector, this.rootLocator)
|
|
121
|
+
.filter({ hasText: "___SENTINEL_ROW_NOT_FOUND___" + Date.now() }), (0, locatorDescription_1.describeSentinelRow)(filters), this.config);
|
|
120
122
|
const smartRow = this.makeSmartRow(sentinel, await this.tableMapper.getMap(), undefined);
|
|
121
123
|
smartRow[sentinel_1.SENTINEL_ROW] = true;
|
|
122
124
|
return smartRow;
|
|
@@ -134,6 +136,12 @@ class RowFinder {
|
|
|
134
136
|
};
|
|
135
137
|
await (0, loadingWait_1.waitWhileTableLoading)(this.config, context, this.rootLocator.page(), 'waitForTableReady');
|
|
136
138
|
}
|
|
139
|
+
/** Unfiltered rows keep the plain `SmartRow #n` name that `_makeSmart` applies. */
|
|
140
|
+
nameFilteredRow(row, resolved, filters) {
|
|
141
|
+
if (!resolved || Object.keys(filters).length === 0)
|
|
142
|
+
return row;
|
|
143
|
+
return (0, locatorDescription_1.describeLocator)(row, (0, locatorDescription_1.describeRowByIndex)(resolved.index, filters), this.config);
|
|
144
|
+
}
|
|
137
145
|
async findRows(filters = {}, options) {
|
|
138
146
|
var _a;
|
|
139
147
|
var _b, _c;
|
|
@@ -168,7 +176,8 @@ class RowFinder {
|
|
|
168
176
|
const barrier = useBarrier ? new navigationBarrier_1.NavigationBarrier(newIndices.length) : undefined;
|
|
169
177
|
for (const idx of newIndices) {
|
|
170
178
|
const resolved = await (0, rowResolution_1.resolveLogicalRowIndex)(currentRows[idx], this.config, () => allRows.length);
|
|
171
|
-
const
|
|
179
|
+
const rowLocator = this.nameFilteredRow(currentRows[idx], resolved, filtersRecord);
|
|
180
|
+
const smartRow = this.makeSmartRow(rowLocator, map, resolved === null || resolved === void 0 ? void 0 : resolved.index, this.tableState.currentPageIndex, barrier, resolved === null || resolved === void 0 ? void 0 : resolved.selector);
|
|
172
181
|
const loadingOutcome = await (0, rowResolution_1.resolveRowLoading)(smartRow, this.config.strategies.loading, 'skip', (msg) => (0, debugUtils_1.logDebug)(this.config, 'verbose', `findRows: ${msg}`));
|
|
173
182
|
if (loadingOutcome !== 'process') {
|
|
174
183
|
barrier === null || barrier === void 0 ? void 0 : barrier.markFinished();
|
|
@@ -21,10 +21,10 @@ import type { FinalTableConfig } from '../types';
|
|
|
21
21
|
* | findRow | yes | options.useBulkPagination | yes | DOM/post filters only |
|
|
22
22
|
* | countRows | yes | no (single-step) | yes | count-only / post filters |
|
|
23
23
|
* | async iterator | yes | no | via runMap path | same as map |
|
|
24
|
-
* | findRowByIndex | no
|
|
24
|
+
* | findRowByIndex | no | no | no | stops once the target index is mounted* |
|
|
25
25
|
* | bringIntoView | n/a | path planner | n/a | not a page scan |
|
|
26
26
|
*
|
|
27
|
-
* \* findRowByIndex
|
|
27
|
+
* \* findRowByIndex only falls back to scanPages after the mounted / viewport.scrollToRow paths miss.
|
|
28
28
|
*/
|
|
29
29
|
export type ScanDecision = 'continue' | 'stop';
|
|
30
30
|
export interface ScanPageContext {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Page } from '@playwright/test';
|
|
2
|
+
import type { FinalTableConfig, StrategyContext } from '../types';
|
|
3
|
+
export interface SortingEnv<T = any> {
|
|
4
|
+
config: FinalTableConfig<T>;
|
|
5
|
+
getPage: () => Page;
|
|
6
|
+
/** Strategy context including `getHeaderCell`. */
|
|
7
|
+
getContext: () => StrategyContext;
|
|
8
|
+
}
|
|
9
|
+
/** Drives the sorting strategy until `columnName` reports `direction`, retrying up to 3 times. */
|
|
10
|
+
export declare function applySort<T>(env: SortingEnv<T>, columnName: string, direction: 'asc' | 'desc'): Promise<void>;
|
|
11
|
+
export declare function getSortState<T>(env: SortingEnv<T>, columnName: string): Promise<'asc' | 'desc' | 'none'>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applySort = applySort;
|
|
4
|
+
exports.getSortState = getSortState;
|
|
5
|
+
const debugUtils_1 = require("../utils/debugUtils");
|
|
6
|
+
const loadingWait_1 = require("../utils/loadingWait");
|
|
7
|
+
const MAX_SORT_ATTEMPTS = 3;
|
|
8
|
+
function requireSorting(config) {
|
|
9
|
+
if (!config.strategies.sorting)
|
|
10
|
+
throw new Error('No sorting strategy has been configured.');
|
|
11
|
+
return config.strategies.sorting;
|
|
12
|
+
}
|
|
13
|
+
/** Waits for the table to settle after `doSort`: `isTableLoading` when configured, otherwise a fixed delay. */
|
|
14
|
+
async function waitForSortToSettle(env, context) {
|
|
15
|
+
var _a, _b, _c, _d;
|
|
16
|
+
const loading = env.config.strategies.loading;
|
|
17
|
+
if (loading === null || loading === void 0 ? void 0 : loading.isTableLoading) {
|
|
18
|
+
const timeout = (_b = (_a = loading.sortStabilizationTimeout) !== null && _a !== void 0 ? _a : loading.loadingTimeout) !== null && _b !== void 0 ? _b : 10000;
|
|
19
|
+
const pollInterval = (_c = loading.sortStabilizationPollInterval) !== null && _c !== void 0 ? _c : 100;
|
|
20
|
+
await (0, loadingWait_1.waitWhileTableLoading)(env.config, context, env.getPage(), 'sorting.apply', { timeout, pollInterval });
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
await env.getPage().waitForTimeout((_d = loading === null || loading === void 0 ? void 0 : loading.sortStabilizationFallbackDelay) !== null && _d !== void 0 ? _d : 200);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Drives the sorting strategy until `columnName` reports `direction`, retrying up to 3 times. */
|
|
27
|
+
async function applySort(env, columnName, direction) {
|
|
28
|
+
const sorting = requireSorting(env.config);
|
|
29
|
+
(0, debugUtils_1.logDebug)(env.config, 'verbose', `Applying sort for column "${columnName}" (${direction})`);
|
|
30
|
+
const context = env.getContext();
|
|
31
|
+
for (let i = 0; i < MAX_SORT_ATTEMPTS; i++) {
|
|
32
|
+
const currentState = await sorting.getSortState({ columnName, context });
|
|
33
|
+
if (currentState === direction) {
|
|
34
|
+
(0, debugUtils_1.logDebug)(env.config, 'verbose', `Sort for "${columnName}" is already "${direction}".`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
await sorting.doSort({ columnName, direction, context });
|
|
38
|
+
await waitForSortToSettle(env, context);
|
|
39
|
+
await (0, debugUtils_1.debugDelay)(env.config, 'default');
|
|
40
|
+
const newState = await sorting.getSortState({ columnName, context });
|
|
41
|
+
if (newState === direction) {
|
|
42
|
+
(0, debugUtils_1.logDebug)(env.config, 'verbose', `Successfully sorted "${columnName}" to "${direction}".`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`Failed to sort column "${columnName}" to "${direction}" after ${MAX_SORT_ATTEMPTS} attempts.`);
|
|
47
|
+
}
|
|
48
|
+
async function getSortState(env, columnName) {
|
|
49
|
+
const sorting = requireSorting(env.config);
|
|
50
|
+
return sorting.getSortState({ columnName, context: env.getContext() });
|
|
51
|
+
}
|
|
@@ -45,8 +45,9 @@ async function runMap(env, callback, options = {}, label = 'map') {
|
|
|
45
45
|
let rowIndex = 0;
|
|
46
46
|
let stopped = false;
|
|
47
47
|
let stoppedIndex = Infinity;
|
|
48
|
+
// In parallel/synchronized mode rows can call stop() out of order; the earliest row wins.
|
|
48
49
|
const stop = (idx) => {
|
|
49
|
-
if (
|
|
50
|
+
if (idx < stoppedIndex) {
|
|
50
51
|
log(env.config, `${label}: stop() called at row ${idx} — halting`);
|
|
51
52
|
stopped = true;
|
|
52
53
|
stoppedIndex = idx;
|
package/dist/packageVersion.d.ts
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* Generated by scripts/embed-version.mjs from package.json
|
|
4
4
|
*/
|
|
5
5
|
/** Semver of this package. Use in logs or diagnostics to confirm the installed build. */
|
|
6
|
-
export declare const PLAYWRIGHT_SMART_TABLE_VERSION: "6.
|
|
6
|
+
export declare const PLAYWRIGHT_SMART_TABLE_VERSION: "6.25.0-next.49ce2de";
|
package/dist/packageVersion.js
CHANGED
|
@@ -6,4 +6,4 @@ exports.PLAYWRIGHT_SMART_TABLE_VERSION = void 0;
|
|
|
6
6
|
* Generated by scripts/embed-version.mjs from package.json
|
|
7
7
|
*/
|
|
8
8
|
/** Semver of this package. Use in logs or diagnostics to confirm the installed build. */
|
|
9
|
-
exports.PLAYWRIGHT_SMART_TABLE_VERSION = "6.
|
|
9
|
+
exports.PLAYWRIGHT_SMART_TABLE_VERSION = "6.25.0-next.49ce2de";
|
|
@@ -102,7 +102,6 @@ const GlideDefaultStrategies = {
|
|
|
102
102
|
getActiveCell: glideGetActiveCell
|
|
103
103
|
};
|
|
104
104
|
/** Strategies only for Glide Data Grid. Includes fillSimple; use when you want to supply your own selectors or override fill. */
|
|
105
|
-
// fallow-ignore-next-line unused-export
|
|
106
105
|
exports.GlideStrategies = Object.assign(Object.assign({}, GlideDefaultStrategies), { fillSimple: glideFillSimple });
|
|
107
106
|
/**
|
|
108
107
|
* Factory that returns a configured Glide preset.
|
package/dist/presets/rdg.js
CHANGED
|
@@ -99,7 +99,6 @@ const RDGDefaultStrategies = {
|
|
|
99
99
|
pagination: rdgPaginationStrategy
|
|
100
100
|
};
|
|
101
101
|
/** Full strategies for React Data Grid. Use when you want to supply your own selectors: `strategies: presets.rdg.Strategies` */
|
|
102
|
-
// fallow-ignore-next-line unused-export
|
|
103
102
|
exports.RDGStrategies = RDGDefaultStrategies;
|
|
104
103
|
/**
|
|
105
104
|
* Full preset for React Data Grid (selectors + default strategies).
|
|
@@ -174,7 +173,6 @@ const rdg2DViewportStrategy = {
|
|
|
174
173
|
};
|
|
175
174
|
/** Default strategies for the RDG2D preset (extends RDG with viewport recovery). */
|
|
176
175
|
const RDG2DDefaultStrategies = Object.assign(Object.assign({}, RDGDefaultStrategies), { viewport: rdg2DViewportStrategy });
|
|
177
|
-
// fallow-ignore-next-line unused-export
|
|
178
176
|
exports.RDG2DStrategies = RDG2DDefaultStrategies;
|
|
179
177
|
/**
|
|
180
178
|
* Full preset for React Data Grid with 2D virtualization (rows + columns both virtualized).
|
package/dist/smartRow.js
CHANGED
|
@@ -7,6 +7,7 @@ const debugUtils_1 = require("./utils/debugUtils");
|
|
|
7
7
|
const paginationPath_1 = require("./utils/paginationPath");
|
|
8
8
|
const sentinel_1 = require("./utils/sentinel");
|
|
9
9
|
const pageIndex_1 = require("./utils/pageIndex");
|
|
10
|
+
const locatorDescription_1 = require("./utils/locatorDescription");
|
|
10
11
|
/**
|
|
11
12
|
* Internal helper to navigate to a cell with active cell optimization.
|
|
12
13
|
* Uses `strategies.navigation` primitives (goUp/goDown/goLeft/goRight, optional snap/seek/goHome).
|
|
@@ -328,7 +329,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
328
329
|
// Attach Methods
|
|
329
330
|
smart.getCell = (colName) => {
|
|
330
331
|
var _a;
|
|
331
|
-
var _b;
|
|
332
|
+
var _b, _c;
|
|
332
333
|
if ((_a = config.syntheticColumns) === null || _a === void 0 ? void 0 : _a[colName]) {
|
|
333
334
|
throw new Error(`Column "${colName}" is synthetic (no DOM cell) — use getValue("${colName}") or toJSON() instead`);
|
|
334
335
|
}
|
|
@@ -351,7 +352,8 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
351
352
|
else {
|
|
352
353
|
baseLocator = resolve(config.cellSelector, rowLocator).nth(idx);
|
|
353
354
|
}
|
|
354
|
-
const
|
|
355
|
+
const cellDescription = `${(_c = (0, locatorDescription_1.getLocatorDescription)(rowLocator)) !== null && _c !== void 0 ? _c : 'SmartRow'} › "${colName}" cell`;
|
|
356
|
+
const smartCell = (0, locatorDescription_1.describeLocator)(baseLocator, cellDescription, config);
|
|
355
357
|
smartCell.bringIntoView = async () => {
|
|
356
358
|
const page = rootLocator.page();
|
|
357
359
|
const navigatedCell = await _navigateToCell({
|
|
@@ -850,36 +852,44 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
850
852
|
barrier: smart._barrier
|
|
851
853
|
});
|
|
852
854
|
const columnOverride = (_b = config.columnOverrides) === null || _b === void 0 ? void 0 : _b[colName];
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
855
|
+
try {
|
|
856
|
+
if (columnOverride === null || columnOverride === void 0 ? void 0 : columnOverride.write) {
|
|
857
|
+
const cellLocator = smart.getCell(colName);
|
|
858
|
+
let currentValue;
|
|
859
|
+
if (columnOverride.read) {
|
|
860
|
+
currentValue = await columnOverride.read(cellLocator, {
|
|
861
|
+
row: smart, columnName: colName, columnIndex: colIdx,
|
|
862
|
+
getCell: (name) => smart.getCell(name),
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
await columnOverride.write({
|
|
866
|
+
cell: cellLocator,
|
|
867
|
+
targetValue: value,
|
|
868
|
+
currentValue,
|
|
869
|
+
row: smart
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
const strategy = config.strategies.fill || fill_1.FillStrategies.default;
|
|
874
|
+
(0, debugUtils_1.logDebug)(config, 'verbose', `Filling cell "${colName}" with value`, value);
|
|
875
|
+
await strategy({
|
|
876
|
+
row: smart,
|
|
877
|
+
columnName: colName,
|
|
878
|
+
value,
|
|
879
|
+
index: rowIndex !== null && rowIndex !== void 0 ? rowIndex : -1,
|
|
880
|
+
page: rowLocator.page(),
|
|
881
|
+
rootLocator,
|
|
882
|
+
config,
|
|
883
|
+
table: table,
|
|
884
|
+
fillOptions
|
|
860
885
|
});
|
|
861
886
|
}
|
|
862
|
-
await columnOverride.write({
|
|
863
|
-
cell: cellLocator,
|
|
864
|
-
targetValue: value,
|
|
865
|
-
currentValue,
|
|
866
|
-
row: smart
|
|
867
|
-
});
|
|
868
887
|
}
|
|
869
|
-
|
|
870
|
-
const
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
columnName: colName,
|
|
875
|
-
value,
|
|
876
|
-
index: rowIndex !== null && rowIndex !== void 0 ? rowIndex : -1,
|
|
877
|
-
page: rowLocator.page(),
|
|
878
|
-
rootLocator,
|
|
879
|
-
config,
|
|
880
|
-
table: table,
|
|
881
|
-
fillOptions
|
|
882
|
-
});
|
|
888
|
+
catch (err) {
|
|
889
|
+
const source = (columnOverride === null || columnOverride === void 0 ? void 0 : columnOverride.write) ? 'columnOverrides.write' : 'fill strategy';
|
|
890
|
+
const wrapped = new Error(`[SmartTable] smartFill: ${source} for "${colName}" failed — ${err instanceof Error ? err.message : String(err)}`);
|
|
891
|
+
wrapped.cause = err;
|
|
892
|
+
throw wrapped;
|
|
883
893
|
}
|
|
884
894
|
// Delay after filling
|
|
885
895
|
await (0, debugUtils_1.debugDelay)(config, 'getCell');
|
|
@@ -3,7 +3,6 @@ export * from './sorting';
|
|
|
3
3
|
export * from './columns';
|
|
4
4
|
export * from './headers';
|
|
5
5
|
export * from './fill';
|
|
6
|
-
export type { ColumnResolutionStrategy } from './resolution';
|
|
7
6
|
export * from './dedupe';
|
|
8
7
|
export * from './loading';
|
|
9
8
|
export * from './stabilization';
|
|
@@ -102,10 +101,10 @@ export declare const Strategies: {
|
|
|
102
101
|
textStable: (options?: {
|
|
103
102
|
timeout?: number;
|
|
104
103
|
interval?: number;
|
|
105
|
-
}) => import("
|
|
104
|
+
}) => import("..").ContentReadyStrategy;
|
|
106
105
|
mutationSettled: (options?: {
|
|
107
106
|
timeout?: number;
|
|
108
107
|
quietPeriod?: number;
|
|
109
|
-
}) => import("
|
|
108
|
+
}) => import("..").ContentReadyStrategy;
|
|
110
109
|
};
|
|
111
110
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StrategyContext } from '../types';
|
|
2
|
+
/** @deprecated Not used by the library. Will be removed in v7. */
|
|
2
3
|
export interface ColumnResolutionStrategy {
|
|
3
4
|
/**
|
|
4
5
|
* Resolves a column name (string or Regex) to a column index.
|
|
@@ -17,6 +18,3 @@ export interface ColumnResolutionStrategy {
|
|
|
17
18
|
headerMap: Map<string, number>;
|
|
18
19
|
}): string;
|
|
19
20
|
}
|
|
20
|
-
export declare const ResolutionStrategies: {
|
|
21
|
-
default: ColumnResolutionStrategy;
|
|
22
|
-
};
|
|
@@ -1,30 +1,2 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ResolutionStrategies = void 0;
|
|
4
|
-
exports.ResolutionStrategies = {
|
|
5
|
-
default: {
|
|
6
|
-
resolveIndex: ({ query, headerMap }) => {
|
|
7
|
-
// 1. Exact / String Match
|
|
8
|
-
if (typeof query === 'string') {
|
|
9
|
-
if (headerMap.has(query))
|
|
10
|
-
return headerMap.get(query);
|
|
11
|
-
}
|
|
12
|
-
// 2. Regex Match
|
|
13
|
-
if (query instanceof RegExp) {
|
|
14
|
-
for (const [colName, idx] of headerMap.entries()) {
|
|
15
|
-
if (query.test(colName))
|
|
16
|
-
return idx;
|
|
17
|
-
}
|
|
18
|
-
return undefined;
|
|
19
|
-
}
|
|
20
|
-
// 3. (Optional) Fuzzy String Match fallback could go here
|
|
21
|
-
// But for strict default strategy, we might want to keep it simple first
|
|
22
|
-
// The original code didn't do fuzzy *resolution* logic inside the get(), it just did strict get().
|
|
23
|
-
// The fuzzy logic was only for *suggestions* on error.
|
|
24
|
-
return undefined;
|
|
25
|
-
},
|
|
26
|
-
resolveName: ({ query }) => {
|
|
27
|
-
return query.toString();
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
};
|