@rickcedwhat/playwright-smart-table 6.20.1 → 6.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/rowFinder.d.ts +1 -0
- package/dist/engine/rowFinder.js +75 -75
- package/dist/engine/scanPages.d.ts +58 -0
- package/dist/engine/scanPages.js +46 -0
- package/dist/engine/tableIteration.d.ts +1 -0
- package/dist/engine/tableIteration.js +38 -20
- package/dist/filterEngine.d.ts +4 -0
- package/dist/filterEngine.js +15 -4
- package/dist/index.d.ts +6 -2
- package/dist/index.js +5 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/plugins/index.d.ts +51 -10
- package/dist/plugins/index.js +14 -6
- package/dist/presets/glide/index.d.ts +1 -1
- package/dist/presets/glide/index.js +2 -2
- package/dist/presets/rdg.d.ts +1 -1
- package/dist/presets/rdg.js +4 -4
- package/dist/smartRow.d.ts +2 -2
- package/dist/smartRow.js +160 -49
- package/dist/strategies/columns.d.ts +5 -9
- package/dist/strategies/columns.js +0 -10
- package/dist/strategies/contentReady.d.ts +20 -0
- package/dist/strategies/contentReady.js +62 -0
- package/dist/strategies/filter.d.ts +0 -4
- package/dist/strategies/filter.js +0 -16
- package/dist/strategies/headers.d.ts +5 -0
- package/dist/strategies/headers.js +17 -9
- package/dist/strategies/index.d.ts +23 -15
- package/dist/strategies/index.js +9 -5
- package/dist/strategies/pagination.js +11 -2
- package/dist/strategies/viewport.d.ts +4 -1
- package/dist/strategies/viewport.js +62 -32
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +45 -1
- package/dist/types.d.ts +43 -1
- package/dist/useTable.js +105 -43
- package/dist/utils/elementTracker.js +6 -3
- package/dist/utils/resolveCellLocator.d.ts +35 -0
- package/dist/utils/resolveCellLocator.js +52 -0
- package/package.json +1 -1
package/dist/plugins/index.d.ts
CHANGED
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @deprecated Use `presets` instead. Plugins will be removed in v7.0.0.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* - Plugins.
|
|
3
|
+
*
|
|
4
|
+
* Compatibility shim over official presets:
|
|
5
|
+
* - `Plugins.MUI` → `presets.muiDataGrid` (**DataGrid only** — not `presets.muiTable`)
|
|
6
|
+
* - `Plugins.RDG` → `presets.rdg`
|
|
7
|
+
* - `Plugins.Glide` → `presets.glide`
|
|
8
|
+
*
|
|
9
|
+
* Prefer: `useTable(loc, { ...presets.muiDataGrid, maxPages: 5 })`.
|
|
10
|
+
* Strategies only: `useTable(loc, { rowSelector: '...', strategies: presets.muiDataGrid.strategies })`.
|
|
6
11
|
*/
|
|
7
12
|
export declare const Plugins: {
|
|
8
13
|
/** @deprecated Use `presets.rdg` */
|
|
9
14
|
RDG: {
|
|
10
|
-
Strategies:
|
|
15
|
+
Strategies: {
|
|
16
|
+
header: (context: import("..").TableContext) => Promise<string[]>;
|
|
17
|
+
getCellLocator: ({ row, columnIndex }: {
|
|
18
|
+
row: import("@playwright/test").Locator;
|
|
19
|
+
columnIndex: number;
|
|
20
|
+
}) => import("@playwright/test").Locator;
|
|
21
|
+
navigation: {
|
|
22
|
+
goRight: ({ root }: import("..").StrategyContext) => Promise<void>;
|
|
23
|
+
goLeft: ({ root }: import("..").StrategyContext) => Promise<void>;
|
|
24
|
+
goDown: ({ root }: import("..").StrategyContext) => Promise<void>;
|
|
25
|
+
goUp: ({ root }: import("..").StrategyContext) => Promise<void>;
|
|
26
|
+
goHome: ({ root }: import("..").StrategyContext) => Promise<void>;
|
|
27
|
+
};
|
|
28
|
+
pagination: import("..").PaginationPrimitives;
|
|
29
|
+
};
|
|
11
30
|
headerSelector?: string | ((root: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
|
|
12
31
|
rowSelector?: string | undefined;
|
|
13
32
|
cellSelector?: string | ((row: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
|
|
@@ -22,14 +41,33 @@ export declare const Plugins: {
|
|
|
22
41
|
autoScroll?: boolean | undefined;
|
|
23
42
|
debug?: import("..").DebugConfig | undefined;
|
|
24
43
|
onReset?: ((context: import("..").TableContext) => Promise<void>) | undefined;
|
|
25
|
-
strategies?: import("
|
|
44
|
+
strategies?: import("..").TableStrategies | undefined;
|
|
26
45
|
columnOverrides?: Partial<Record<string | number | symbol, import("..").ColumnOverride<any>>> | undefined;
|
|
27
46
|
syntheticColumns?: Record<string, import("..").SyntheticColumnDef<any>> | undefined;
|
|
28
47
|
emptyState?: import("@playwright/test").Locator | undefined;
|
|
29
48
|
};
|
|
30
49
|
/** @deprecated Use `presets.glide` */
|
|
31
50
|
Glide: {
|
|
32
|
-
Strategies:
|
|
51
|
+
Strategies: {
|
|
52
|
+
fillSimple: import("..").FillStrategy;
|
|
53
|
+
fill: import("..").FillStrategy;
|
|
54
|
+
pagination: import("..").PaginationPrimitives;
|
|
55
|
+
header: (context: import("..").StrategyContext, options?: {
|
|
56
|
+
limit?: number;
|
|
57
|
+
selector?: string;
|
|
58
|
+
scrollAmount?: number;
|
|
59
|
+
}) => Promise<string[]>;
|
|
60
|
+
viewport: import("..").ViewportStrategy;
|
|
61
|
+
loading: {
|
|
62
|
+
isHeaderLoading: () => Promise<boolean>;
|
|
63
|
+
};
|
|
64
|
+
getCellLocator: ({ row, columnIndex }: any) => any;
|
|
65
|
+
getActiveCell: ({ page }: any) => Promise<{
|
|
66
|
+
rowIndex: number;
|
|
67
|
+
columnIndex: number;
|
|
68
|
+
locator: any;
|
|
69
|
+
} | null>;
|
|
70
|
+
};
|
|
33
71
|
headerSelector?: string | ((root: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
|
|
34
72
|
rowSelector?: string | undefined;
|
|
35
73
|
cellSelector?: string | ((row: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
|
|
@@ -44,14 +82,17 @@ export declare const Plugins: {
|
|
|
44
82
|
autoScroll?: boolean | undefined;
|
|
45
83
|
debug?: import("..").DebugConfig | undefined;
|
|
46
84
|
onReset?: ((context: import("..").TableContext) => Promise<void>) | undefined;
|
|
47
|
-
strategies?: import("
|
|
85
|
+
strategies?: import("..").TableStrategies | undefined;
|
|
48
86
|
columnOverrides?: Partial<Record<string | number | symbol, import("..").ColumnOverride<any>>> | undefined;
|
|
49
87
|
syntheticColumns?: Record<string, import("..").SyntheticColumnDef<any>> | undefined;
|
|
50
88
|
emptyState?: import("@playwright/test").Locator | undefined;
|
|
51
89
|
};
|
|
52
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* @deprecated Use `presets.muiDataGrid`.
|
|
92
|
+
* DataGrid only — for MUI `<Table>` use `presets.muiTable`.
|
|
93
|
+
*/
|
|
53
94
|
MUI: {
|
|
54
|
-
Strategies: import("
|
|
95
|
+
Strategies: import("..").TableStrategies | undefined;
|
|
55
96
|
headerSelector?: string | ((root: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
|
|
56
97
|
rowSelector?: string | undefined;
|
|
57
98
|
cellSelector?: string | ((row: import("@playwright/test").Locator) => import("@playwright/test").Locator) | undefined;
|
|
@@ -66,7 +107,7 @@ export declare const Plugins: {
|
|
|
66
107
|
autoScroll?: boolean | undefined;
|
|
67
108
|
debug?: import("..").DebugConfig | undefined;
|
|
68
109
|
onReset?: ((context: import("..").TableContext) => Promise<void>) | undefined;
|
|
69
|
-
strategies?: import("
|
|
110
|
+
strategies?: import("..").TableStrategies | undefined;
|
|
70
111
|
columnOverrides?: Partial<Record<string | number | symbol, import("..").ColumnOverride<any>>> | undefined;
|
|
71
112
|
syntheticColumns?: Record<string, import("..").SyntheticColumnDef<any>> | undefined;
|
|
72
113
|
emptyState?: import("@playwright/test").Locator | undefined;
|
package/dist/plugins/index.js
CHANGED
|
@@ -4,15 +4,23 @@ exports.Plugins = void 0;
|
|
|
4
4
|
const presets_1 = require("../presets");
|
|
5
5
|
/**
|
|
6
6
|
* @deprecated Use `presets` instead. Plugins will be removed in v7.0.0.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* - Plugins.
|
|
7
|
+
*
|
|
8
|
+
* Compatibility shim over official presets:
|
|
9
|
+
* - `Plugins.MUI` → `presets.muiDataGrid` (**DataGrid only** — not `presets.muiTable`)
|
|
10
|
+
* - `Plugins.RDG` → `presets.rdg`
|
|
11
|
+
* - `Plugins.Glide` → `presets.glide`
|
|
12
|
+
*
|
|
13
|
+
* Prefer: `useTable(loc, { ...presets.muiDataGrid, maxPages: 5 })`.
|
|
14
|
+
* Strategies only: `useTable(loc, { rowSelector: '...', strategies: presets.muiDataGrid.strategies })`.
|
|
10
15
|
*/
|
|
11
16
|
exports.Plugins = {
|
|
12
17
|
/** @deprecated Use `presets.rdg` */
|
|
13
|
-
RDG: Object.assign(Object.assign({}, presets_1.rdg), { Strategies: presets_1.rdg.
|
|
18
|
+
RDG: Object.assign(Object.assign({}, presets_1.rdg), { Strategies: presets_1.rdg.Strategies }),
|
|
14
19
|
/** @deprecated Use `presets.glide` */
|
|
15
|
-
Glide: Object.assign(Object.assign({}, presets_1.glide), { Strategies: presets_1.glide.
|
|
16
|
-
/**
|
|
20
|
+
Glide: Object.assign(Object.assign({}, presets_1.glide), { Strategies: presets_1.glide.Strategies }),
|
|
21
|
+
/**
|
|
22
|
+
* @deprecated Use `presets.muiDataGrid`.
|
|
23
|
+
* DataGrid only — for MUI `<Table>` use `presets.muiTable`.
|
|
24
|
+
*/
|
|
17
25
|
MUI: Object.assign(Object.assign({}, presets_1.muiDataGrid), { Strategies: presets_1.muiDataGrid.strategies }),
|
|
18
26
|
};
|
|
@@ -12,7 +12,7 @@ export declare const GlideStrategies: {
|
|
|
12
12
|
selector?: string;
|
|
13
13
|
scrollAmount?: number;
|
|
14
14
|
}) => Promise<string[]>;
|
|
15
|
-
viewport: import("
|
|
15
|
+
viewport: import("../..").ViewportStrategy;
|
|
16
16
|
loading: {
|
|
17
17
|
isHeaderLoading: () => Promise<boolean>;
|
|
18
18
|
};
|
|
@@ -123,8 +123,8 @@ function createGlide(options = {}) {
|
|
|
123
123
|
}
|
|
124
124
|
/**
|
|
125
125
|
* Full preset for Glide Data Grid (selectors + default strategies only).
|
|
126
|
-
* Spread: useTable(loc, { ...
|
|
127
|
-
* Strategies only (including fillSimple): useTable(loc, { rowSelector: '...', strategies:
|
|
126
|
+
* Spread: `useTable(loc, { ...presets.glide, maxPages: 5 })`.
|
|
127
|
+
* Strategies only (including fillSimple): `useTable(loc, { rowSelector: '...', strategies: presets.glide.Strategies })`.
|
|
128
128
|
* For a non-64-column grid, use `createGlide({ columnCount })` instead.
|
|
129
129
|
*/
|
|
130
130
|
const GlidePreset = {
|
package/dist/presets/rdg.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Locator } from '@playwright/test';
|
|
2
2
|
import { TableContext, TableConfig, ViewportStrategy, StrategyContext } from '../types';
|
|
3
|
-
/** Full strategies for React Data Grid. Use when you want to supply your own selectors: strategies:
|
|
3
|
+
/** Full strategies for React Data Grid. Use when you want to supply your own selectors: `strategies: presets.rdg.Strategies` */
|
|
4
4
|
export declare const RDGStrategies: {
|
|
5
5
|
header: (context: TableContext) => Promise<string[]>;
|
|
6
6
|
getCellLocator: ({ row, columnIndex }: {
|
package/dist/presets/rdg.js
CHANGED
|
@@ -92,20 +92,20 @@ const rdgNavigation = {
|
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
|
-
/** Default strategies for the RDG preset (used when you spread
|
|
95
|
+
/** Default strategies for the RDG preset (used when you spread `presets.rdg`). */
|
|
96
96
|
const RDGDefaultStrategies = {
|
|
97
97
|
header: scrollRightHeaderRDG,
|
|
98
98
|
getCellLocator: rdgGetCellLocator,
|
|
99
99
|
navigation: rdgNavigation,
|
|
100
100
|
pagination: rdgPaginationStrategy
|
|
101
101
|
};
|
|
102
|
-
/** Full strategies for React Data Grid. Use when you want to supply your own selectors: strategies:
|
|
102
|
+
/** Full strategies for React Data Grid. Use when you want to supply your own selectors: `strategies: presets.rdg.Strategies` */
|
|
103
103
|
// fallow-ignore-next-line unused-export
|
|
104
104
|
exports.RDGStrategies = RDGDefaultStrategies;
|
|
105
105
|
/**
|
|
106
106
|
* Full preset for React Data Grid (selectors + default strategies).
|
|
107
|
-
* Spread: useTable(loc, { ...
|
|
108
|
-
* Strategies only: useTable(loc, { rowSelector: '...', strategies:
|
|
107
|
+
* Spread: `useTable(loc, { ...presets.rdg, maxPages: 5 })`.
|
|
108
|
+
* Strategies only: `useTable(loc, { rowSelector: '...', strategies: presets.rdg.Strategies })`.
|
|
109
109
|
*/
|
|
110
110
|
const RDGPreset = {
|
|
111
111
|
rowSelector: '[role="row"].rdg-row',
|
package/dist/smartRow.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Locator, type Page } from '@playwright/test';
|
|
2
2
|
import { SmartRow as SmartRowType, FinalTableConfig, TableResult } from './types';
|
|
3
3
|
import { NavigationBarrier } from './utils/navigationBarrier';
|
|
4
4
|
/**
|
|
@@ -9,5 +9,5 @@ import { NavigationBarrier } from './utils/navigationBarrier';
|
|
|
9
9
|
* @internal Internal factory for creating SmartRow objects.
|
|
10
10
|
* Not part of the public package surface; tests and consumers should use public APIs.
|
|
11
11
|
*/
|
|
12
|
-
declare const createSmartRow: <T = any>(rowLocator: Locator, map: Map<string, number>, rowIndex: number | undefined, config: FinalTableConfig<T>, rootLocator: Locator, resolve: (item: any, parent: Locator | Page) => Locator, table: TableResult<T> | null, tablePageIndex?: number, barrier?: NavigationBarrier) => SmartRowType<T>;
|
|
12
|
+
declare const createSmartRow: <T = any>(rowLocator: Locator, map: Map<string, number>, rowIndex: number | undefined, config: FinalTableConfig<T>, rootLocator: Locator, resolve: (item: any, parent: Locator | Page) => Locator, table: TableResult<T> | null, tablePageIndex?: number, barrier?: NavigationBarrier, renderWindowPosition?: boolean) => SmartRowType<T>;
|
|
13
13
|
export default createSmartRow;
|
package/dist/smartRow.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const test_1 = require("@playwright/test");
|
|
3
4
|
const rowResolution_1 = require("./engine/rowResolution");
|
|
4
5
|
const fill_1 = require("./strategies/fill");
|
|
5
6
|
const stringUtils_1 = require("./utils/stringUtils");
|
|
@@ -12,7 +13,7 @@ const sentinel_1 = require("./utils/sentinel");
|
|
|
12
13
|
* Returns the target cell locator after navigation.
|
|
13
14
|
*/
|
|
14
15
|
const _navigateToCell = async (params) => {
|
|
15
|
-
const { config, rootLocator, page, resolve, getHeaders, column, index, rowLocator, rowIndex, barrier } = params;
|
|
16
|
+
const { config, rootLocator, page, resolve, getHeaders, column, index, rowLocator, rowIndex, barrier, allowMissingCell } = params;
|
|
16
17
|
const rowLabel = typeof rowIndex === 'number' ? `row ${rowIndex}` : 'row ?';
|
|
17
18
|
(0, debugUtils_1.logDebug)(config, 'verbose', `_navigateToCell: resolving column "${column}" (colIndex ${index}), ${rowLabel}`);
|
|
18
19
|
// Get active cell if strategy is available
|
|
@@ -65,7 +66,18 @@ const _navigateToCell = async (params) => {
|
|
|
65
66
|
? await viewport.getVisibleRowRange(context)
|
|
66
67
|
: null;
|
|
67
68
|
const rowVisible = !rowRange || (rowIndex >= rowRange.first && rowIndex <= rowRange.last);
|
|
68
|
-
|
|
69
|
+
let cellAttached = rowVisible && await targetReached();
|
|
70
|
+
if (rowVisible && !cellAttached) {
|
|
71
|
+
try {
|
|
72
|
+
await getCellLocator().waitFor({ state: 'attached', timeout: 500 });
|
|
73
|
+
cellAttached = true;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (!(error instanceof test_1.errors.TimeoutError))
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (cellAttached) {
|
|
69
81
|
(0, debugUtils_1.logDebug)(config, 'verbose', `_navigateToCell: col ${index} in visible range [${knownColRange.first}-${knownColRange.last}], reading directly`);
|
|
70
82
|
// Check in to the barrier without a moveAction — peers that need the scroll
|
|
71
83
|
// still supply their own action and will trigger it when all rows arrive.
|
|
@@ -124,6 +136,8 @@ const _navigateToCell = async (params) => {
|
|
|
124
136
|
}
|
|
125
137
|
// Cell still not accessible — fall through to navigation primitives if configured.
|
|
126
138
|
if (!config.strategies.navigation) {
|
|
139
|
+
if (allowMissingCell)
|
|
140
|
+
return getCellLocator();
|
|
127
141
|
const colRange = viewport.getVisibleColumnRange ? await viewport.getVisibleColumnRange(context) : null;
|
|
128
142
|
const rowRange = viewport.getVisibleRowRange ? await viewport.getVisibleRowRange(context) : null;
|
|
129
143
|
let errMsg = `SmartTable: could not reach cell for column "${column}" (colIndex ${index}) at row ${rowIndex}.\n`;
|
|
@@ -144,15 +158,17 @@ const _navigateToCell = async (params) => {
|
|
|
144
158
|
// Use navigation primitives if available
|
|
145
159
|
if (config.strategies.navigation) {
|
|
146
160
|
const nav = config.strategies.navigation;
|
|
147
|
-
if (typeof rowIndex !== 'number') {
|
|
148
|
-
throw new Error('Row index is required for navigation');
|
|
149
|
-
}
|
|
150
161
|
if (await targetReached()) {
|
|
151
162
|
// Already there. If we have a barrier, check-in to stay in lock-step.
|
|
152
163
|
if (barrier)
|
|
153
164
|
await barrier.sync(index);
|
|
154
165
|
return getCellLocator();
|
|
155
166
|
}
|
|
167
|
+
if (typeof rowIndex !== 'number') {
|
|
168
|
+
if (allowMissingCell)
|
|
169
|
+
return getCellLocator();
|
|
170
|
+
throw new Error('Row index is required for navigation');
|
|
171
|
+
}
|
|
156
172
|
// If getActiveCell is present but returns null (no focus), and cell is in DOM,
|
|
157
173
|
// try to focus it once before entering navigation loop.
|
|
158
174
|
if (config.strategies.getActiveCell) {
|
|
@@ -196,6 +212,8 @@ const _navigateToCell = async (params) => {
|
|
|
196
212
|
currCol = ac.columnIndex;
|
|
197
213
|
}
|
|
198
214
|
}
|
|
215
|
+
// Column-0 snap is owned by the navigation primitive (scrollLeft=0, Home, etc.).
|
|
216
|
+
// Core must not special-case canvas/Home — that was pre-viewport Glide glue.
|
|
199
217
|
if (index === 0 && nav.snapFirstColumnIntoView) {
|
|
200
218
|
(0, debugUtils_1.logDebug)(config, 'verbose', '_navigateToCell: snapFirstColumnIntoView for column index 0');
|
|
201
219
|
await nav.snapFirstColumnIntoView(context);
|
|
@@ -206,27 +224,6 @@ const _navigateToCell = async (params) => {
|
|
|
206
224
|
currCol = ac.columnIndex;
|
|
207
225
|
}
|
|
208
226
|
}
|
|
209
|
-
// Home moves a11y focus within the current row to column 0. If focus row !== target row
|
|
210
|
-
// (e.g. still on previous row), Home jumps to grid origin and breaks `tr.nth(k)` reads.
|
|
211
|
-
if (typeof rowIndex === 'number' && currRow === rowIndex) {
|
|
212
|
-
await rootLocator.evaluate((el) => {
|
|
213
|
-
var _a;
|
|
214
|
-
const canvas = el.closest('canvas') || ((_a = el.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector('canvas'));
|
|
215
|
-
if (canvas instanceof HTMLCanvasElement) {
|
|
216
|
-
canvas.tabIndex = 0;
|
|
217
|
-
canvas.focus();
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
await page.keyboard.press('Home');
|
|
221
|
-
await page.waitForTimeout(120);
|
|
222
|
-
if (config.strategies.getActiveCell) {
|
|
223
|
-
const ac = await config.strategies.getActiveCell({ config, root: rootLocator, page, resolve });
|
|
224
|
-
if (ac) {
|
|
225
|
-
currRow = ac.rowIndex;
|
|
226
|
-
currCol = ac.columnIndex;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
227
|
}
|
|
231
228
|
let cDiff = index - currCol;
|
|
232
229
|
if (Math.abs(cDiff) > 12 && nav.seekColumnIndex) {
|
|
@@ -253,8 +250,8 @@ const _navigateToCell = async (params) => {
|
|
|
253
250
|
const settleMs = (Number.isFinite(nav.settleMs) && nav.settleMs >= 0) ? nav.settleMs : computed;
|
|
254
251
|
await page.waitForTimeout(settleMs);
|
|
255
252
|
}
|
|
256
|
-
//
|
|
257
|
-
//
|
|
253
|
+
// After horizontal steps, poll until getActiveCell / DOM presence confirms the target
|
|
254
|
+
// (a11y trees and virtual mounts often lag the scroll). Tune via navigation.maxWaitMs.
|
|
258
255
|
const pollIntervalMs = 10;
|
|
259
256
|
const computedMax = Math.min(6000, 250 + horizontalSteps * 25);
|
|
260
257
|
const maxWaitMs = (Number.isFinite(nav.maxWaitMs) && nav.maxWaitMs >= 0) ? nav.maxWaitMs : computedMax;
|
|
@@ -290,6 +287,8 @@ const _navigateToCell = async (params) => {
|
|
|
290
287
|
const finalCell = getCellLocator();
|
|
291
288
|
if (await finalCell.count() > 0)
|
|
292
289
|
return finalCell;
|
|
290
|
+
if (allowMissingCell)
|
|
291
|
+
return finalCell;
|
|
293
292
|
const colRange = viewport && viewport.getVisibleColumnRange ? await viewport.getVisibleColumnRange(context) : null;
|
|
294
293
|
const rowRange = viewport && viewport.getVisibleRowRange ? await viewport.getVisibleRowRange(context) : null;
|
|
295
294
|
let errMsg = `SmartTable: could not reach cell for column "${column}" (colIndex ${index}) at row ${rowIndex} after exhausting navigation strategies. Ensure navigation primitives are correctly implemented.\n`;
|
|
@@ -313,7 +312,7 @@ const _navigateToCell = async (params) => {
|
|
|
313
312
|
* @internal Internal factory for creating SmartRow objects.
|
|
314
313
|
* Not part of the public package surface; tests and consumers should use public APIs.
|
|
315
314
|
*/
|
|
316
|
-
const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve, table, tablePageIndex, barrier) => {
|
|
315
|
+
const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve, table, tablePageIndex, barrier, renderWindowPosition = false) => {
|
|
317
316
|
const smart = rowLocator;
|
|
318
317
|
// Attach State
|
|
319
318
|
smart.rowIndex = rowIndex;
|
|
@@ -406,12 +405,22 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
406
405
|
throw new Error((0, stringUtils_1.buildColumnNotFoundError)(colName, [...map.keys(), ...Object.keys((_b = config.syntheticColumns) !== null && _b !== void 0 ? _b : {})]));
|
|
407
406
|
}
|
|
408
407
|
const columnOverride = (_c = config.columnOverrides) === null || _c === void 0 ? void 0 : _c[colName];
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
408
|
+
const page = rootLocator.page();
|
|
409
|
+
// Same nav pipeline as toJSON / getCell().bringIntoView() (#430) so virtualized
|
|
410
|
+
// off-screen columns don't return empty/stale text.
|
|
411
|
+
const cell = await _navigateToCell({
|
|
412
|
+
config,
|
|
413
|
+
rootLocator,
|
|
414
|
+
page,
|
|
415
|
+
resolve,
|
|
416
|
+
getHeaders: table === null || table === void 0 ? void 0 : table.getHeaders,
|
|
417
|
+
column: colName,
|
|
418
|
+
index: idx,
|
|
419
|
+
rowLocator,
|
|
420
|
+
rowIndex,
|
|
421
|
+
barrier: smart._barrier,
|
|
422
|
+
allowMissingCell: !!(columnOverride === null || columnOverride === void 0 ? void 0 : columnOverride.read),
|
|
423
|
+
});
|
|
415
424
|
if (columnOverride === null || columnOverride === void 0 ? void 0 : columnOverride.read) {
|
|
416
425
|
const getCell = (name) => {
|
|
417
426
|
const ci = map.get(name);
|
|
@@ -424,7 +433,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
424
433
|
return (await cell.innerText()).trim();
|
|
425
434
|
};
|
|
426
435
|
smart.toJSON = async (options) => {
|
|
427
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
436
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
428
437
|
// Atomic mode: snapshot the row in a single evaluate, then apply column overrides
|
|
429
438
|
// against a frozen reconstruction. Zero inter-column stagger — even in-place React
|
|
430
439
|
// re-renders can't affect it because overrides read from a detached clone.
|
|
@@ -434,10 +443,84 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
434
443
|
throw new Error('[SmartTable] toJSON({ atomic: true }) requires cellSelector to be a CSS string, not a function.');
|
|
435
444
|
}
|
|
436
445
|
const page = rootLocator.page();
|
|
446
|
+
// Re-pin: the row locator is a positional nth-child from the iteration loop.
|
|
447
|
+
// On recycling virtualizers, elements can shift between SmartRow creation and
|
|
448
|
+
// this clone — the nth element may now show a different row's content.
|
|
449
|
+
// Verify via resolveRowIndex and rescan if drifted.
|
|
450
|
+
let cloneTarget = rowLocator;
|
|
451
|
+
const resolveRI = config.strategies.resolveRowIndex;
|
|
452
|
+
const isSelfHealing = smart._selfHealing === true;
|
|
453
|
+
const rescanForRow = resolveRI && typeof rowIndex === 'number' && !isSelfHealing
|
|
454
|
+
? async () => {
|
|
455
|
+
const rows = await resolve(config.rowSelector, rootLocator).all();
|
|
456
|
+
for (const r of rows) {
|
|
457
|
+
try {
|
|
458
|
+
const rResult = await resolveRI(r);
|
|
459
|
+
if (rResult !== undefined && (0, rowResolution_1.normalizeRowIndexResult)(rResult).index === rowIndex)
|
|
460
|
+
return r;
|
|
461
|
+
}
|
|
462
|
+
catch ( /* element may have detached during scan */_a) { /* element may have detached during scan */ }
|
|
463
|
+
}
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
: null;
|
|
467
|
+
if (rescanForRow) {
|
|
468
|
+
let drifted = false;
|
|
469
|
+
try {
|
|
470
|
+
const curResult = await resolveRI(rowLocator);
|
|
471
|
+
const curIndex = curResult !== undefined
|
|
472
|
+
? (0, rowResolution_1.normalizeRowIndexResult)(curResult).index
|
|
473
|
+
: undefined;
|
|
474
|
+
drifted = curIndex !== rowIndex;
|
|
475
|
+
}
|
|
476
|
+
catch (_k) {
|
|
477
|
+
drifted = true;
|
|
478
|
+
}
|
|
479
|
+
if (drifted) {
|
|
480
|
+
const inBatch = smart._inBatch === true || !!smart._barrier;
|
|
481
|
+
let recovered = await rescanForRow();
|
|
482
|
+
if (!recovered && !inBatch && ((_a = config.strategies.viewport) === null || _a === void 0 ? void 0 : _a.scrollToRow)) {
|
|
483
|
+
await config.strategies.viewport.scrollToRow({ root: rootLocator, config, page, resolve }, rowIndex);
|
|
484
|
+
const deadline = Date.now() + 500;
|
|
485
|
+
while (!recovered && Date.now() < deadline) {
|
|
486
|
+
recovered = await rescanForRow();
|
|
487
|
+
if (!recovered)
|
|
488
|
+
await page.waitForTimeout(50);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (!recovered) {
|
|
492
|
+
throw new Error(`[SmartTable] toJSON({ atomic: true }): row ${rowIndex} recycled out of the DOM and could not be recovered. ` +
|
|
493
|
+
(inBatch
|
|
494
|
+
? `(During a map/forEach batch, scroll-back recovery is disabled to avoid disrupting sibling rows.)`
|
|
495
|
+
: `Ensure a viewport.scrollToRow strategy can restore the row.`));
|
|
496
|
+
}
|
|
497
|
+
cloneTarget = recovered;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
// Wait for async content renders to settle before cloning.
|
|
501
|
+
if (config.strategies.contentReady) {
|
|
502
|
+
await config.strategies.contentReady(cloneTarget, page);
|
|
503
|
+
// Revalidate: the element may have recycled during the await.
|
|
504
|
+
if (rescanForRow) {
|
|
505
|
+
let postIdx;
|
|
506
|
+
try {
|
|
507
|
+
const r = await resolveRI(cloneTarget);
|
|
508
|
+
postIdx = r !== undefined ? (0, rowResolution_1.normalizeRowIndexResult)(r).index : undefined;
|
|
509
|
+
}
|
|
510
|
+
catch (_l) {
|
|
511
|
+
postIdx = undefined;
|
|
512
|
+
}
|
|
513
|
+
if (postIdx !== rowIndex) {
|
|
514
|
+
const reFound = await rescanForRow();
|
|
515
|
+
if (reFound)
|
|
516
|
+
cloneTarget = reFound;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
437
520
|
// Step 1: clone row in browser memory (NOT in the DOM), extract text + row HTML.
|
|
438
521
|
// Uses textContent (not innerText) because the clone is detached — innerText
|
|
439
522
|
// falls back to textContent on detached nodes per HTML spec, so be explicit.
|
|
440
|
-
const cloneHandle = await
|
|
523
|
+
const cloneHandle = await cloneTarget.evaluateHandle(el => el.cloneNode(true));
|
|
441
524
|
let snapshot;
|
|
442
525
|
try {
|
|
443
526
|
snapshot = await cloneHandle.evaluate((row, sel) => {
|
|
@@ -491,7 +574,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
491
574
|
await materializeRow();
|
|
492
575
|
const result = {};
|
|
493
576
|
for (const [col, idx] of columnsToProcess) {
|
|
494
|
-
const columnOverride = (
|
|
577
|
+
const columnOverride = (_b = config.columnOverrides) === null || _b === void 0 ? void 0 : _b[col];
|
|
495
578
|
const mapper = columnOverride === null || columnOverride === void 0 ? void 0 : columnOverride.read;
|
|
496
579
|
if (mapper) {
|
|
497
580
|
const cell = page.locator(`#${snapId}`).locator(cellSel).nth(idx);
|
|
@@ -508,7 +591,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
508
591
|
result[col] = snapshot.cells[idx].text;
|
|
509
592
|
}
|
|
510
593
|
}
|
|
511
|
-
for (const [name, def] of Object.entries((
|
|
594
|
+
for (const [name, def] of Object.entries((_c = config.syntheticColumns) !== null && _c !== void 0 ? _c : {})) {
|
|
512
595
|
if ((options === null || options === void 0 ? void 0 : options.columns) && !options.columns.includes(name))
|
|
513
596
|
continue;
|
|
514
597
|
const snapshotRow = Object.create(smart);
|
|
@@ -607,7 +690,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
607
690
|
// Re-pin to the correct logical row before reading this column (#366).
|
|
608
691
|
const stableRow = await resolveStableRow();
|
|
609
692
|
// Check if we have a column override for this column
|
|
610
|
-
const columnOverride = (
|
|
693
|
+
const columnOverride = (_d = config.columnOverrides) === null || _d === void 0 ? void 0 : _d[col];
|
|
611
694
|
const mapper = columnOverride === null || columnOverride === void 0 ? void 0 : columnOverride.read;
|
|
612
695
|
// --- Navigation Logic Start ---
|
|
613
696
|
const cell = config.strategies.getCellLocator
|
|
@@ -634,7 +717,8 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
634
717
|
index: idx,
|
|
635
718
|
rowLocator: stableRow,
|
|
636
719
|
rowIndex,
|
|
637
|
-
barrier: smart._barrier
|
|
720
|
+
barrier: smart._barrier,
|
|
721
|
+
allowMissingCell: !!mapper,
|
|
638
722
|
});
|
|
639
723
|
if (navigatedCell) {
|
|
640
724
|
targetCell = navigatedCell;
|
|
@@ -654,15 +738,29 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
654
738
|
});
|
|
655
739
|
}
|
|
656
740
|
// Cell loading wait (if configured)
|
|
657
|
-
const isCellLoading = (
|
|
658
|
-
const rawCellTimeout = (
|
|
741
|
+
const isCellLoading = (_e = config.strategies.loading) === null || _e === void 0 ? void 0 : _e.isCellLoading;
|
|
742
|
+
const rawCellTimeout = (_f = config.strategies.loading) === null || _f === void 0 ? void 0 : _f.cellLoadingTimeout;
|
|
659
743
|
// undefined = unset (read as-is immediately); 0 = no wait (immediate check); >0 = wait up to N ms
|
|
660
744
|
const cellLoadingTimeout = rawCellTimeout !== undefined && Number.isFinite(rawCellTimeout) && rawCellTimeout >= 0
|
|
661
745
|
? rawCellTimeout
|
|
662
746
|
: undefined;
|
|
663
|
-
const onCellLoadingTimeout = (
|
|
664
|
-
if (isCellLoading
|
|
665
|
-
|
|
747
|
+
const onCellLoadingTimeout = (_h = (_g = config.strategies.loading) === null || _g === void 0 ? void 0 : _g.onCellLoadingTimeout) !== null && _h !== void 0 ? _h : 'read-as-is';
|
|
748
|
+
if (isCellLoading) {
|
|
749
|
+
let loading = await isCellLoading(targetCell, col, smart);
|
|
750
|
+
// First-paint race: the cell node can attach a tick before the loading
|
|
751
|
+
// indicator mounts. A single false-negative then reads "" and skips
|
|
752
|
+
// onCellLoadingTimeout. Only grace when the cell still looks empty.
|
|
753
|
+
if (!loading && cellLoadingTimeout !== undefined && cellLoadingTimeout > 0) {
|
|
754
|
+
const peek = await targetCell.evaluate((el) => (el.textContent || '').trim()).catch(() => '');
|
|
755
|
+
if (peek === '') {
|
|
756
|
+
const graceDeadline = Date.now() + Math.min(250, cellLoadingTimeout);
|
|
757
|
+
while (!loading && Date.now() < graceDeadline) {
|
|
758
|
+
await page.waitForTimeout(25);
|
|
759
|
+
loading = await isCellLoading(targetCell, col, smart);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
if (loading && cellLoadingTimeout !== undefined) {
|
|
666
764
|
(0, debugUtils_1.logDebug)(config, 'verbose', `toJSON: cell "${col}" — waiting up to ${cellLoadingTimeout}ms`);
|
|
667
765
|
const deadline = Date.now() + cellLoadingTimeout;
|
|
668
766
|
let cellResolved = !(await isCellLoading(targetCell, col, smart)); // immediate check (handles timeout=0)
|
|
@@ -699,7 +797,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
699
797
|
result[col] = (text || '').trim();
|
|
700
798
|
}
|
|
701
799
|
}
|
|
702
|
-
for (const [name, def] of Object.entries((
|
|
800
|
+
for (const [name, def] of Object.entries((_j = config.syntheticColumns) !== null && _j !== void 0 ? _j : {})) {
|
|
703
801
|
if ((options === null || options === void 0 ? void 0 : options.columns) && !options.columns.includes(name))
|
|
704
802
|
continue;
|
|
705
803
|
const snapshotRow = Object.create(smart);
|
|
@@ -775,6 +873,7 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
775
873
|
(0, debugUtils_1.logDebug)(config, 'info', 'Row fill complete');
|
|
776
874
|
};
|
|
777
875
|
smart.bringIntoView = async () => {
|
|
876
|
+
var _a;
|
|
778
877
|
if (rowIndex === undefined) {
|
|
779
878
|
(0, debugUtils_1.logDebug)(config, 'info', 'bringIntoView called on a row with unknown rowIndex (e.g. from getRow()). ' +
|
|
780
879
|
'Page navigation and scrolling will proceed, but virtual-scroll keyboard navigation will be skipped. ' +
|
|
@@ -821,8 +920,20 @@ const createSmartRow = (rowLocator, map, rowIndex, config, rootLocator, resolve,
|
|
|
821
920
|
}
|
|
822
921
|
// Delay after pagination/finding before scrolling
|
|
823
922
|
await (0, debugUtils_1.debugDelay)(config, 'findRow');
|
|
824
|
-
//
|
|
825
|
-
|
|
923
|
+
// Prefer viewport.scrollToRow when configured (#430). scrollIntoViewIfNeeded adjusts
|
|
924
|
+
// both axes and can evict sibling rows on recycling virtualizers.
|
|
925
|
+
const viewport = config.strategies.viewport;
|
|
926
|
+
const logicalRowIndex = (viewport === null || viewport === void 0 ? void 0 : viewport.scrollToRow) && renderWindowPosition
|
|
927
|
+
? (_a = (await (0, rowResolution_1.resolveLogicalRowIndex)(rowLocator, config, () => undefined))) === null || _a === void 0 ? void 0 : _a.index
|
|
928
|
+
: rowIndex;
|
|
929
|
+
if ((viewport === null || viewport === void 0 ? void 0 : viewport.scrollToRow) && typeof logicalRowIndex === 'number') {
|
|
930
|
+
const page = rootLocator.page();
|
|
931
|
+
(0, debugUtils_1.logDebug)(config, 'verbose', `bringIntoView: viewport.scrollToRow(${logicalRowIndex})`);
|
|
932
|
+
await viewport.scrollToRow({ root: rootLocator, config, page, resolve, getHeaders: parentTable === null || parentTable === void 0 ? void 0 : parentTable.getHeaders }, logicalRowIndex);
|
|
933
|
+
}
|
|
934
|
+
else {
|
|
935
|
+
await rowLocator.scrollIntoViewIfNeeded();
|
|
936
|
+
}
|
|
826
937
|
};
|
|
827
938
|
return smart;
|
|
828
939
|
};
|
|
@@ -12,7 +12,9 @@ export interface NavigationPrimitives {
|
|
|
12
12
|
goHome?: (context: StrategyContext) => Promise<void>;
|
|
13
13
|
/**
|
|
14
14
|
* After vertical moves, run before horizontal steps when the target column index is 0.
|
|
15
|
-
* Use for horizontally virtualized
|
|
15
|
+
* Use for horizontally virtualized tables so the first column exists in the DOM again
|
|
16
|
+
* (e.g. set scrollLeft = 0). If the table needs keyboard `Home` or canvas focus, put that
|
|
17
|
+
* here — the core orchestrator does not special-case those behaviors.
|
|
16
18
|
* Do not reset vertical scroll here — RDG-style `goHome` is often unsuitable.
|
|
17
19
|
*/
|
|
18
20
|
snapFirstColumnIntoView?: (context: StrategyContext) => Promise<void>;
|
|
@@ -33,7 +35,8 @@ export interface NavigationPrimitives {
|
|
|
33
35
|
maxWaitMs?: number;
|
|
34
36
|
}
|
|
35
37
|
/**
|
|
36
|
-
* @deprecated Use NavigationPrimitives instead.
|
|
38
|
+
* @deprecated Use NavigationPrimitives (`strategies.navigation`) instead. Removed from
|
|
39
|
+
* the public `Strategies` namespace in #428; this type remains for transitional typings.
|
|
37
40
|
* Defines the contract for a cell navigation strategy.
|
|
38
41
|
*/
|
|
39
42
|
export type CellNavigationStrategy = (context: StrategyContext & {
|
|
@@ -46,10 +49,3 @@ export type CellNavigationStrategy = (context: StrategyContext & {
|
|
|
46
49
|
locator: Locator;
|
|
47
50
|
} | null;
|
|
48
51
|
}) => Promise<void>;
|
|
49
|
-
export declare const CellNavigationStrategies: {
|
|
50
|
-
/**
|
|
51
|
-
* Default strategy: Assumes column is accessible or standard scrolling works.
|
|
52
|
-
* No specific action taken other than what Playwright's default locator handling does.
|
|
53
|
-
*/
|
|
54
|
-
default: () => Promise<void>;
|
|
55
|
-
};
|
|
@@ -1,12 +1,2 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CellNavigationStrategies = void 0;
|
|
4
|
-
exports.CellNavigationStrategies = {
|
|
5
|
-
/**
|
|
6
|
-
* Default strategy: Assumes column is accessible or standard scrolling works.
|
|
7
|
-
* No specific action taken other than what Playwright's default locator handling does.
|
|
8
|
-
*/
|
|
9
|
-
default: async () => {
|
|
10
|
-
// No-op
|
|
11
|
-
}
|
|
12
|
-
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ContentReadyStrategy } from '../types';
|
|
2
|
+
export type { ContentReadyStrategy };
|
|
3
|
+
export declare const ContentReadyStrategies: {
|
|
4
|
+
/**
|
|
5
|
+
* Polls the row's text content until two consecutive reads match.
|
|
6
|
+
*/
|
|
7
|
+
textStable: (options?: {
|
|
8
|
+
timeout?: number;
|
|
9
|
+
interval?: number;
|
|
10
|
+
}) => ContentReadyStrategy;
|
|
11
|
+
/**
|
|
12
|
+
* Uses MutationObserver to wait for DOM mutations on the row's subtree
|
|
13
|
+
* to settle. Resolves when no mutations occur for `quietPeriod` ms, or
|
|
14
|
+
* when `timeout` expires.
|
|
15
|
+
*/
|
|
16
|
+
mutationSettled: (options?: {
|
|
17
|
+
timeout?: number;
|
|
18
|
+
quietPeriod?: number;
|
|
19
|
+
}) => ContentReadyStrategy;
|
|
20
|
+
};
|