@rickcedwhat/playwright-smart-table 5.3.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -957
- package/dist/examples/glide-strategies/columns.d.ts +13 -0
- package/dist/examples/glide-strategies/columns.js +43 -0
- package/dist/examples/glide-strategies/headers.d.ts +9 -0
- package/dist/examples/glide-strategies/headers.js +68 -0
- package/dist/src/filterEngine.d.ts +11 -0
- package/dist/src/filterEngine.js +39 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +18 -0
- package/dist/src/plugins.d.ts +32 -0
- package/dist/src/plugins.js +13 -0
- package/dist/src/smartRow.d.ts +7 -0
- package/dist/src/smartRow.js +160 -0
- package/dist/src/strategies/columns.d.ts +18 -0
- package/dist/src/strategies/columns.js +21 -0
- package/dist/src/strategies/dedupe.d.ts +9 -0
- package/dist/src/strategies/dedupe.js +27 -0
- package/dist/src/strategies/fill.d.ts +7 -0
- package/dist/src/strategies/fill.js +88 -0
- package/dist/src/strategies/glide.d.ts +29 -0
- package/dist/src/strategies/glide.js +98 -0
- package/dist/src/strategies/headers.d.ts +13 -0
- package/dist/src/strategies/headers.js +30 -0
- package/dist/src/strategies/index.d.ts +54 -0
- package/dist/src/strategies/index.js +43 -0
- package/dist/src/strategies/loading.d.ts +48 -0
- package/dist/src/strategies/loading.js +82 -0
- package/dist/src/strategies/pagination.d.ts +33 -0
- package/dist/src/strategies/pagination.js +79 -0
- package/dist/src/strategies/rdg.d.ts +25 -0
- package/dist/src/strategies/rdg.js +100 -0
- package/dist/src/strategies/resolution.d.ts +22 -0
- package/dist/src/strategies/resolution.js +30 -0
- package/dist/src/strategies/sorting.d.ts +12 -0
- package/dist/src/strategies/sorting.js +68 -0
- package/dist/src/strategies/stabilization.d.ts +29 -0
- package/dist/src/strategies/stabilization.js +91 -0
- package/dist/src/strategies/validation.d.ts +22 -0
- package/dist/src/strategies/validation.js +54 -0
- package/dist/src/strategies/virtualizedPagination.d.ts +32 -0
- package/dist/src/strategies/virtualizedPagination.js +80 -0
- package/dist/src/typeContext.d.ts +6 -0
- package/dist/src/typeContext.js +465 -0
- package/dist/src/types.d.ts +458 -0
- package/dist/src/types.js +2 -0
- package/dist/src/useTable.d.ts +44 -0
- package/dist/src/useTable.js +642 -0
- package/dist/src/utils/debugUtils.d.ts +17 -0
- package/dist/src/utils/debugUtils.js +62 -0
- package/dist/src/utils/smartRowArray.d.ts +14 -0
- package/dist/src/utils/smartRowArray.js +22 -0
- package/dist/src/utils/stringUtils.d.ts +22 -0
- package/dist/src/utils/stringUtils.js +73 -0
- package/dist/src/utils.d.ts +7 -0
- package/dist/src/utils.js +29 -0
- package/dist/typeContext.d.ts +1 -1
- package/dist/typeContext.js +27 -5
- package/dist/types.d.ts +27 -6
- package/dist/useTable.js +21 -16
- package/dist/utils/smartRowArray.d.ts +14 -0
- package/dist/utils/smartRowArray.js +22 -0
- package/package.json +16 -20
|
@@ -0,0 +1,98 @@
|
|
|
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.GlideStrategies = exports.glideGetActiveCell = exports.glideGetCellLocator = exports.glidePaginationStrategy = exports.glideFillStrategy = void 0;
|
|
13
|
+
const columns_1 = require("../../examples/glide-strategies/columns");
|
|
14
|
+
const headers_1 = require("../../examples/glide-strategies/headers");
|
|
15
|
+
const pagination_1 = require("./pagination");
|
|
16
|
+
const stabilization_1 = require("./stabilization");
|
|
17
|
+
const glideFillStrategy = (_a) => __awaiter(void 0, [_a], void 0, function* ({ value, page }) {
|
|
18
|
+
// Edit Cell
|
|
19
|
+
yield page.keyboard.press('Enter');
|
|
20
|
+
// Wait for editor to appear
|
|
21
|
+
const textarea = page.locator('textarea.gdg-input');
|
|
22
|
+
yield textarea.waitFor({ state: 'visible', timeout: 2000 });
|
|
23
|
+
yield page.keyboard.type(String(value));
|
|
24
|
+
// Wait for textarea value to match what we typed
|
|
25
|
+
yield textarea.evaluate((el, expectedValue) => {
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
const checkValue = () => {
|
|
28
|
+
if (el.value === expectedValue) {
|
|
29
|
+
resolve();
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
setTimeout(checkValue, 10);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
checkValue();
|
|
36
|
+
});
|
|
37
|
+
}, String(value));
|
|
38
|
+
// Small delay to let the grid process the value
|
|
39
|
+
yield page.waitForTimeout(50);
|
|
40
|
+
yield page.keyboard.press('Enter');
|
|
41
|
+
// Wait for editor to close (commit completed)
|
|
42
|
+
yield textarea.waitFor({ state: 'detached', timeout: 2000 });
|
|
43
|
+
// Wait for accessibility layer to sync with canvas state
|
|
44
|
+
yield page.waitForTimeout(300);
|
|
45
|
+
});
|
|
46
|
+
exports.glideFillStrategy = glideFillStrategy;
|
|
47
|
+
exports.glidePaginationStrategy = pagination_1.PaginationStrategies.infiniteScroll({
|
|
48
|
+
scrollTarget: 'xpath=//ancestor::body//div[contains(@class, "dvn-scroller")]',
|
|
49
|
+
scrollAmount: 500,
|
|
50
|
+
action: 'js-scroll',
|
|
51
|
+
stabilization: stabilization_1.StabilizationStrategies.contentChanged({ timeout: 5000 }),
|
|
52
|
+
timeout: 5000 // Overall timeout
|
|
53
|
+
});
|
|
54
|
+
const glideGetCellLocator = ({ row, columnIndex }) => {
|
|
55
|
+
// Use relative locator to support virtualization (where rowIndex resets or is offsets)
|
|
56
|
+
// The accessibility DOM usually contains 'td' elements with the data.
|
|
57
|
+
return row.locator('td').nth(columnIndex);
|
|
58
|
+
};
|
|
59
|
+
exports.glideGetCellLocator = glideGetCellLocator;
|
|
60
|
+
const glideGetActiveCell = (_a) => __awaiter(void 0, [_a], void 0, function* ({ page }) {
|
|
61
|
+
// Find the focused cell/element
|
|
62
|
+
// Use broad selector for focused element
|
|
63
|
+
const focused = page.locator('*:focus').first();
|
|
64
|
+
if ((yield focused.count()) === 0)
|
|
65
|
+
return null;
|
|
66
|
+
// Debug log
|
|
67
|
+
if (process.env.DEBUG)
|
|
68
|
+
console.log('Found focused element:', yield focused.evaluate((e) => e.outerHTML));
|
|
69
|
+
// Try to extract position from ID if possible
|
|
70
|
+
const id = (yield focused.getAttribute('id')) || '';
|
|
71
|
+
// Expected format: glide-cell-COL-ROW
|
|
72
|
+
const parts = id.split('-');
|
|
73
|
+
let rowIndex = -1;
|
|
74
|
+
let columnIndex = -1;
|
|
75
|
+
if (parts.length >= 4 && parts[0] === 'glide' && parts[1] === 'cell') {
|
|
76
|
+
columnIndex = parseInt(parts[2]) - 1; // 1-based in ID to 0-based
|
|
77
|
+
rowIndex = parseInt(parts[3]);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Fallback: If we can't parse ID, we assume it's the correct cell
|
|
81
|
+
// because we just navigated to it.
|
|
82
|
+
// Returning -1 indices might be confusing but won't stop smartRow from using the locator.
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
rowIndex,
|
|
86
|
+
columnIndex,
|
|
87
|
+
locator: focused
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
exports.glideGetActiveCell = glideGetActiveCell;
|
|
91
|
+
exports.GlideStrategies = {
|
|
92
|
+
fill: exports.glideFillStrategy,
|
|
93
|
+
pagination: exports.glidePaginationStrategy,
|
|
94
|
+
header: headers_1.scrollRightHeader,
|
|
95
|
+
cellNavigation: columns_1.keyboardCellNavigation,
|
|
96
|
+
getCellLocator: exports.glideGetCellLocator,
|
|
97
|
+
getActiveCell: exports.glideGetActiveCell
|
|
98
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { StrategyContext } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Defines the contract for a header retrieval strategy.
|
|
4
|
+
* Returns a list of unique header names found in the table.
|
|
5
|
+
*/
|
|
6
|
+
export type HeaderStrategy = (context: StrategyContext) => Promise<string[]>;
|
|
7
|
+
export declare const HeaderStrategies: {
|
|
8
|
+
/**
|
|
9
|
+
* Default strategy: Returns only the headers currently visible in the DOM.
|
|
10
|
+
* This is fast but won't find virtualized columns off-screen.
|
|
11
|
+
*/
|
|
12
|
+
visible: ({ config, resolve, root }: StrategyContext) => Promise<string[]>;
|
|
13
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
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.HeaderStrategies = void 0;
|
|
13
|
+
exports.HeaderStrategies = {
|
|
14
|
+
/**
|
|
15
|
+
* Default strategy: Returns only the headers currently visible in the DOM.
|
|
16
|
+
* This is fast but won't find virtualized columns off-screen.
|
|
17
|
+
*/
|
|
18
|
+
visible: (_a) => __awaiter(void 0, [_a], void 0, function* ({ config, resolve, root }) {
|
|
19
|
+
const headerLoc = resolve(config.headerSelector, root);
|
|
20
|
+
try {
|
|
21
|
+
// Wait for at least one header to be visible
|
|
22
|
+
yield headerLoc.first().waitFor({ state: 'visible', timeout: 3000 });
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
// Ignore hydration/timeout issues, return what we have
|
|
26
|
+
}
|
|
27
|
+
const texts = yield headerLoc.allInnerTexts();
|
|
28
|
+
return texts.map(t => t.trim());
|
|
29
|
+
})
|
|
30
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export * from './pagination';
|
|
2
|
+
export * from './sorting';
|
|
3
|
+
export * from './columns';
|
|
4
|
+
export * from './headers';
|
|
5
|
+
export * from './fill';
|
|
6
|
+
export * from './resolution';
|
|
7
|
+
export * from './dedupe';
|
|
8
|
+
export * from './loading';
|
|
9
|
+
export declare const Strategies: {
|
|
10
|
+
Pagination: {
|
|
11
|
+
clickNext: (nextButtonSelector: import("..").Selector, options?: {
|
|
12
|
+
stabilization?: import("./stabilization").StabilizationStrategy;
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}) => import("..").PaginationStrategy;
|
|
15
|
+
infiniteScroll: (options?: {
|
|
16
|
+
action?: "scroll" | "js-scroll";
|
|
17
|
+
scrollTarget?: import("..").Selector;
|
|
18
|
+
scrollAmount?: number;
|
|
19
|
+
stabilization?: import("./stabilization").StabilizationStrategy;
|
|
20
|
+
timeout?: number;
|
|
21
|
+
}) => import("..").PaginationStrategy;
|
|
22
|
+
};
|
|
23
|
+
Sorting: {
|
|
24
|
+
AriaSort: () => import("..").SortingStrategy;
|
|
25
|
+
};
|
|
26
|
+
CellNavigation: {
|
|
27
|
+
default: () => Promise<void>;
|
|
28
|
+
};
|
|
29
|
+
Header: {
|
|
30
|
+
visible: ({ config, resolve, root }: import("..").StrategyContext) => Promise<string[]>;
|
|
31
|
+
};
|
|
32
|
+
Fill: {
|
|
33
|
+
default: ({ row, columnName, value, fillOptions }: Parameters<import("..").FillStrategy>[0]) => Promise<void>;
|
|
34
|
+
};
|
|
35
|
+
Resolution: {
|
|
36
|
+
default: import("./resolution").ColumnResolutionStrategy;
|
|
37
|
+
};
|
|
38
|
+
Dedupe: {
|
|
39
|
+
byTopPosition: (tolerance?: number) => import("..").DedupeStrategy;
|
|
40
|
+
};
|
|
41
|
+
Loading: {
|
|
42
|
+
Table: {
|
|
43
|
+
hasSpinner: (selector?: string) => ({ root }: import("..").TableContext) => Promise<boolean>;
|
|
44
|
+
custom: (fn: (context: import("..").TableContext) => Promise<boolean>) => (context: import("..").TableContext) => Promise<boolean>;
|
|
45
|
+
never: () => Promise<boolean>;
|
|
46
|
+
};
|
|
47
|
+
Row: {
|
|
48
|
+
hasClass: (className?: string) => (row: import("..").SmartRow) => Promise<boolean>;
|
|
49
|
+
hasText: (text?: string | RegExp) => (row: import("..").SmartRow) => Promise<boolean>;
|
|
50
|
+
hasEmptyCells: () => (row: import("..").SmartRow) => Promise<boolean>;
|
|
51
|
+
never: () => Promise<boolean>;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.Strategies = void 0;
|
|
18
|
+
const pagination_1 = require("./pagination");
|
|
19
|
+
const sorting_1 = require("./sorting");
|
|
20
|
+
const columns_1 = require("./columns");
|
|
21
|
+
const headers_1 = require("./headers");
|
|
22
|
+
const fill_1 = require("./fill");
|
|
23
|
+
const resolution_1 = require("./resolution");
|
|
24
|
+
const dedupe_1 = require("./dedupe");
|
|
25
|
+
const loading_1 = require("./loading");
|
|
26
|
+
__exportStar(require("./pagination"), exports);
|
|
27
|
+
__exportStar(require("./sorting"), exports);
|
|
28
|
+
__exportStar(require("./columns"), exports);
|
|
29
|
+
__exportStar(require("./headers"), exports);
|
|
30
|
+
__exportStar(require("./fill"), exports);
|
|
31
|
+
__exportStar(require("./resolution"), exports);
|
|
32
|
+
__exportStar(require("./dedupe"), exports);
|
|
33
|
+
__exportStar(require("./loading"), exports);
|
|
34
|
+
exports.Strategies = {
|
|
35
|
+
Pagination: pagination_1.PaginationStrategies,
|
|
36
|
+
Sorting: sorting_1.SortingStrategies,
|
|
37
|
+
CellNavigation: columns_1.CellNavigationStrategies,
|
|
38
|
+
Header: headers_1.HeaderStrategies,
|
|
39
|
+
Fill: fill_1.FillStrategies,
|
|
40
|
+
Resolution: resolution_1.ResolutionStrategies,
|
|
41
|
+
Dedupe: dedupe_1.DedupeStrategies,
|
|
42
|
+
Loading: loading_1.LoadingStrategies,
|
|
43
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { SmartRow, TableContext } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Strategies for detecting loading states.
|
|
4
|
+
* Return `true` if the item is loading/busy, `false` if it is ready.
|
|
5
|
+
*/
|
|
6
|
+
export declare const LoadingStrategies: {
|
|
7
|
+
/**
|
|
8
|
+
* Strategies for detecting if the entire table is loading.
|
|
9
|
+
*/
|
|
10
|
+
Table: {
|
|
11
|
+
/**
|
|
12
|
+
* Checks if a global spinner or loading overlay is visible.
|
|
13
|
+
* @param selector Selector for the loading indicator (e.g. '.loading-spinner')
|
|
14
|
+
*/
|
|
15
|
+
hasSpinner: (selector?: string) => ({ root }: TableContext) => Promise<boolean>;
|
|
16
|
+
/**
|
|
17
|
+
* Custom function to determine table loading state.
|
|
18
|
+
*/
|
|
19
|
+
custom: (fn: (context: TableContext) => Promise<boolean>) => (context: TableContext) => Promise<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Assume table is never loading (default).
|
|
22
|
+
*/
|
|
23
|
+
never: () => Promise<boolean>;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Strategies for detecting if a specific row is loading (e.g. Skeleton).
|
|
27
|
+
*/
|
|
28
|
+
Row: {
|
|
29
|
+
/**
|
|
30
|
+
* Checks if the row contains a specific class indicating it's a skeleton/loading row.
|
|
31
|
+
* @param className Class name acting as the loading indicator (default: 'skeleton')
|
|
32
|
+
*/
|
|
33
|
+
hasClass: (className?: string) => (row: SmartRow) => Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Checks if the row's text content matches a "Loading..." string or regex.
|
|
36
|
+
*/
|
|
37
|
+
hasText: (text?: string | RegExp) => (row: SmartRow) => Promise<boolean>;
|
|
38
|
+
/**
|
|
39
|
+
* Checks if the row has any cell with empty/falsy content (if strict).
|
|
40
|
+
* Useful if rows render with empty cells before populating.
|
|
41
|
+
*/
|
|
42
|
+
hasEmptyCells: () => (row: SmartRow) => Promise<boolean>;
|
|
43
|
+
/**
|
|
44
|
+
* Assume row is never loading (default).
|
|
45
|
+
*/
|
|
46
|
+
never: () => Promise<boolean>;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
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.LoadingStrategies = void 0;
|
|
13
|
+
/**
|
|
14
|
+
* Strategies for detecting loading states.
|
|
15
|
+
* Return `true` if the item is loading/busy, `false` if it is ready.
|
|
16
|
+
*/
|
|
17
|
+
exports.LoadingStrategies = {
|
|
18
|
+
/**
|
|
19
|
+
* Strategies for detecting if the entire table is loading.
|
|
20
|
+
*/
|
|
21
|
+
Table: {
|
|
22
|
+
/**
|
|
23
|
+
* Checks if a global spinner or loading overlay is visible.
|
|
24
|
+
* @param selector Selector for the loading indicator (e.g. '.loading-spinner')
|
|
25
|
+
*/
|
|
26
|
+
hasSpinner: (selector = '.loading-spinner') => (_a) => __awaiter(void 0, [_a], void 0, function* ({ root }) {
|
|
27
|
+
// Check if spinner exists and is visible within the table wrapper or page
|
|
28
|
+
const spinner = root.locator(selector).first();
|
|
29
|
+
try {
|
|
30
|
+
return yield spinner.isVisible();
|
|
31
|
+
}
|
|
32
|
+
catch (_b) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}),
|
|
36
|
+
/**
|
|
37
|
+
* Custom function to determine table loading state.
|
|
38
|
+
*/
|
|
39
|
+
custom: (fn) => fn,
|
|
40
|
+
/**
|
|
41
|
+
* Assume table is never loading (default).
|
|
42
|
+
*/
|
|
43
|
+
never: () => __awaiter(void 0, void 0, void 0, function* () { return false; })
|
|
44
|
+
},
|
|
45
|
+
/**
|
|
46
|
+
* Strategies for detecting if a specific row is loading (e.g. Skeleton).
|
|
47
|
+
*/
|
|
48
|
+
Row: {
|
|
49
|
+
/**
|
|
50
|
+
* Checks if the row contains a specific class indicating it's a skeleton/loading row.
|
|
51
|
+
* @param className Class name acting as the loading indicator (default: 'skeleton')
|
|
52
|
+
*/
|
|
53
|
+
hasClass: (className = 'skeleton') => (row) => __awaiter(void 0, void 0, void 0, function* () {
|
|
54
|
+
const cls = yield row.getAttribute('class');
|
|
55
|
+
return cls ? cls.includes(className) : false;
|
|
56
|
+
}),
|
|
57
|
+
/**
|
|
58
|
+
* Checks if the row's text content matches a "Loading..." string or regex.
|
|
59
|
+
*/
|
|
60
|
+
hasText: (text = 'Loading...') => (row) => __awaiter(void 0, void 0, void 0, function* () {
|
|
61
|
+
const content = yield row.innerText();
|
|
62
|
+
if (typeof text === 'string')
|
|
63
|
+
return content.includes(text);
|
|
64
|
+
return text.test(content);
|
|
65
|
+
}),
|
|
66
|
+
/**
|
|
67
|
+
* Checks if the row has any cell with empty/falsy content (if strict).
|
|
68
|
+
* Useful if rows render with empty cells before populating.
|
|
69
|
+
*/
|
|
70
|
+
hasEmptyCells: () => (row) => __awaiter(void 0, void 0, void 0, function* () {
|
|
71
|
+
// Logic: Get all cells, check if any are empty.
|
|
72
|
+
// Note: This might be expensive if done for every row check.
|
|
73
|
+
// Simplified: check if InnerText is empty or very short?
|
|
74
|
+
const text = yield row.innerText();
|
|
75
|
+
return !text.trim();
|
|
76
|
+
}),
|
|
77
|
+
/**
|
|
78
|
+
* Assume row is never loading (default).
|
|
79
|
+
*/
|
|
80
|
+
never: () => __awaiter(void 0, void 0, void 0, function* () { return false; })
|
|
81
|
+
}
|
|
82
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PaginationStrategy, Selector } from '../types';
|
|
2
|
+
import { StabilizationStrategy } from './stabilization';
|
|
3
|
+
export declare const PaginationStrategies: {
|
|
4
|
+
/**
|
|
5
|
+
* Strategy: Clicks a "Next" button and waits for stabilization.
|
|
6
|
+
* @param nextButtonSelector Selector for the next page button.
|
|
7
|
+
* @param options.stabilization Strategy to determine when the page has updated.
|
|
8
|
+
* Defaults to `contentChanged({ scope: 'first' })`.
|
|
9
|
+
* @param options.timeout Timeout for the click action.
|
|
10
|
+
*/
|
|
11
|
+
clickNext: (nextButtonSelector: Selector, options?: {
|
|
12
|
+
stabilization?: StabilizationStrategy;
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}) => PaginationStrategy;
|
|
15
|
+
/**
|
|
16
|
+
* Strategy: Infinite Scroll (generic).
|
|
17
|
+
* Supports both simple "Scroll to Bottom" and "Virtualized Scroll".
|
|
18
|
+
*
|
|
19
|
+
* @param options.action 'scroll' (mouse wheel) or 'js-scroll' (direct scrollTop).
|
|
20
|
+
* @param options.scrollTarget Selector for the scroll container (defaults to table root).
|
|
21
|
+
* @param options.scrollAmount Amount to scroll in pixels (default 500).
|
|
22
|
+
* @param options.stabilization Strategy to determine if new content loaded.
|
|
23
|
+
* Defaults to `rowCountIncreased` (simple append).
|
|
24
|
+
* Use `contentChanged` for virtualization.
|
|
25
|
+
*/
|
|
26
|
+
infiniteScroll: (options?: {
|
|
27
|
+
action?: "scroll" | "js-scroll";
|
|
28
|
+
scrollTarget?: Selector;
|
|
29
|
+
scrollAmount?: number;
|
|
30
|
+
stabilization?: StabilizationStrategy;
|
|
31
|
+
timeout?: number;
|
|
32
|
+
}) => PaginationStrategy;
|
|
33
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
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.PaginationStrategies = void 0;
|
|
13
|
+
const stabilization_1 = require("./stabilization");
|
|
14
|
+
exports.PaginationStrategies = {
|
|
15
|
+
/**
|
|
16
|
+
* Strategy: Clicks a "Next" button and waits for stabilization.
|
|
17
|
+
* @param nextButtonSelector Selector for the next page button.
|
|
18
|
+
* @param options.stabilization Strategy to determine when the page has updated.
|
|
19
|
+
* Defaults to `contentChanged({ scope: 'first' })`.
|
|
20
|
+
* @param options.timeout Timeout for the click action.
|
|
21
|
+
*/
|
|
22
|
+
clickNext: (nextButtonSelector, options = {}) => {
|
|
23
|
+
return (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
24
|
+
var _a;
|
|
25
|
+
const { root, resolve, page } = context;
|
|
26
|
+
const nextBtn = resolve(nextButtonSelector, root).first();
|
|
27
|
+
if (!(yield nextBtn.isVisible()) || !(yield nextBtn.isEnabled())) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
// Default stabilization: Wait for first row content to change
|
|
31
|
+
const stabilization = (_a = options.stabilization) !== null && _a !== void 0 ? _a : stabilization_1.StabilizationStrategies.contentChanged({ scope: 'first', timeout: options.timeout });
|
|
32
|
+
// Stabilization: Wrap action
|
|
33
|
+
const success = yield stabilization(context, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
+
yield nextBtn.click({ timeout: 2000 }).catch(() => { });
|
|
35
|
+
}));
|
|
36
|
+
return success;
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
/**
|
|
40
|
+
* Strategy: Infinite Scroll (generic).
|
|
41
|
+
* Supports both simple "Scroll to Bottom" and "Virtualized Scroll".
|
|
42
|
+
*
|
|
43
|
+
* @param options.action 'scroll' (mouse wheel) or 'js-scroll' (direct scrollTop).
|
|
44
|
+
* @param options.scrollTarget Selector for the scroll container (defaults to table root).
|
|
45
|
+
* @param options.scrollAmount Amount to scroll in pixels (default 500).
|
|
46
|
+
* @param options.stabilization Strategy to determine if new content loaded.
|
|
47
|
+
* Defaults to `rowCountIncreased` (simple append).
|
|
48
|
+
* Use `contentChanged` for virtualization.
|
|
49
|
+
*/
|
|
50
|
+
infiniteScroll: (options = {}) => {
|
|
51
|
+
return (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
52
|
+
var _a, _b;
|
|
53
|
+
const { root, resolve, page } = context;
|
|
54
|
+
const scrollTarget = options.scrollTarget
|
|
55
|
+
? resolve(options.scrollTarget, root)
|
|
56
|
+
: root;
|
|
57
|
+
// Default stabilization: Wait for row count to increase (Append mode)
|
|
58
|
+
const stabilization = (_a = options.stabilization) !== null && _a !== void 0 ? _a : stabilization_1.StabilizationStrategies.rowCountIncreased({ timeout: options.timeout });
|
|
59
|
+
const amount = (_b = options.scrollAmount) !== null && _b !== void 0 ? _b : 500;
|
|
60
|
+
const doScroll = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
61
|
+
const box = yield scrollTarget.boundingBox();
|
|
62
|
+
// Action: Scroll
|
|
63
|
+
if (options.action === 'js-scroll' || !box) {
|
|
64
|
+
yield scrollTarget.evaluate((el, y) => {
|
|
65
|
+
el.scrollTop += y;
|
|
66
|
+
}, amount);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Mouse Wheel
|
|
70
|
+
yield page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
|
71
|
+
yield page.mouse.wheel(0, amount);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
// Stabilization: Wait
|
|
75
|
+
const success = yield stabilization(context, doScroll);
|
|
76
|
+
return success;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { TableContext } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Scrolls the grid horizontally to collect all column headers.
|
|
4
|
+
* Handles empty headers by labeling them (e.g. "Checkbox").
|
|
5
|
+
*/
|
|
6
|
+
export declare const scrollRightHeaderRDG: (context: TableContext) => Promise<string[]>;
|
|
7
|
+
/**
|
|
8
|
+
* Uses a row-relative locator to avoid issues with absolute aria-rowindex
|
|
9
|
+
* changing during pagination/scrolling.
|
|
10
|
+
*/
|
|
11
|
+
export declare const rdgGetCellLocator: ({ row, columnIndex }: any) => any;
|
|
12
|
+
/**
|
|
13
|
+
* Scrolls virtualized columns into view before reading.
|
|
14
|
+
*/
|
|
15
|
+
export declare const rdgCellNavigation: ({ root, page, index }: any) => Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Scrolls the grid vertically to load more virtualized rows.
|
|
18
|
+
*/
|
|
19
|
+
export declare const rdgPaginationStrategy: import("../types").PaginationStrategy;
|
|
20
|
+
export declare const RDGStrategies: {
|
|
21
|
+
header: (context: TableContext) => Promise<string[]>;
|
|
22
|
+
getCellLocator: ({ row, columnIndex }: any) => any;
|
|
23
|
+
cellNavigation: ({ root, page, index }: any) => Promise<void>;
|
|
24
|
+
pagination: import("../types").PaginationStrategy;
|
|
25
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
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.RDGStrategies = exports.rdgPaginationStrategy = exports.rdgCellNavigation = exports.rdgGetCellLocator = exports.scrollRightHeaderRDG = void 0;
|
|
13
|
+
/**
|
|
14
|
+
* Scrolls the grid horizontally to collect all column headers.
|
|
15
|
+
* Handles empty headers by labeling them (e.g. "Checkbox").
|
|
16
|
+
*/
|
|
17
|
+
const scrollRightHeaderRDG = (context) => __awaiter(void 0, void 0, void 0, function* () {
|
|
18
|
+
const { resolve, config, root, page } = context;
|
|
19
|
+
const collectedHeaders = new Set();
|
|
20
|
+
const gridHandle = yield root.evaluateHandle((el) => {
|
|
21
|
+
return el.querySelector('[role="grid"]') || el.closest('[role="grid"]');
|
|
22
|
+
});
|
|
23
|
+
const expectedColumns = yield gridHandle.evaluate(el => el ? parseInt(el.getAttribute('aria-colcount') || '0', 10) : 0);
|
|
24
|
+
const getVisible = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
25
|
+
const headerLoc = resolve(config.headerSelector, root);
|
|
26
|
+
const texts = yield headerLoc.allInnerTexts();
|
|
27
|
+
return texts.map(t => {
|
|
28
|
+
const trimmed = t.trim();
|
|
29
|
+
// Assign a name to empty headers (like selection checkboxes)
|
|
30
|
+
return trimmed.length > 0 ? trimmed : 'Checkbox';
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
let currentHeaders = yield getVisible();
|
|
34
|
+
currentHeaders.forEach(h => collectedHeaders.add(h));
|
|
35
|
+
const hasScroll = yield gridHandle.evaluate(el => el ? el.scrollWidth > el.clientWidth : false);
|
|
36
|
+
if (hasScroll) {
|
|
37
|
+
yield gridHandle.evaluate(el => el.scrollLeft = 0);
|
|
38
|
+
yield page.waitForTimeout(200);
|
|
39
|
+
let iteration = 0;
|
|
40
|
+
// Safety break at 30 iterations to prevent infinite loops
|
|
41
|
+
while (collectedHeaders.size < expectedColumns && iteration < 30) {
|
|
42
|
+
yield gridHandle.evaluate(el => el.scrollLeft += 500);
|
|
43
|
+
yield page.waitForTimeout(300);
|
|
44
|
+
const newHeaders = yield getVisible();
|
|
45
|
+
newHeaders.forEach(h => collectedHeaders.add(h));
|
|
46
|
+
const atEnd = yield gridHandle.evaluate(el => el.scrollLeft >= el.scrollWidth - el.clientWidth - 10);
|
|
47
|
+
iteration++;
|
|
48
|
+
if (atEnd)
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
yield gridHandle.evaluate(el => el.scrollLeft = 0);
|
|
52
|
+
yield page.waitForTimeout(200);
|
|
53
|
+
}
|
|
54
|
+
return Array.from(collectedHeaders);
|
|
55
|
+
});
|
|
56
|
+
exports.scrollRightHeaderRDG = scrollRightHeaderRDG;
|
|
57
|
+
/**
|
|
58
|
+
* Uses a row-relative locator to avoid issues with absolute aria-rowindex
|
|
59
|
+
* changing during pagination/scrolling.
|
|
60
|
+
*/
|
|
61
|
+
const rdgGetCellLocator = ({ row, columnIndex }) => {
|
|
62
|
+
const ariaColIndex = columnIndex + 1;
|
|
63
|
+
return row.locator(`[role="gridcell"][aria-colindex="${ariaColIndex}"]`);
|
|
64
|
+
};
|
|
65
|
+
exports.rdgGetCellLocator = rdgGetCellLocator;
|
|
66
|
+
/**
|
|
67
|
+
* Scrolls virtualized columns into view before reading.
|
|
68
|
+
*/
|
|
69
|
+
const rdgCellNavigation = (_a) => __awaiter(void 0, [_a], void 0, function* ({ root, page, index }) {
|
|
70
|
+
// Check if the column header is visible and scroll horizontally if needed
|
|
71
|
+
const headerCell = root.locator(`[role="columnheader"][aria-colindex="${index + 1}"]`);
|
|
72
|
+
const isVisible = yield headerCell.isVisible().catch(() => false);
|
|
73
|
+
if (!isVisible) {
|
|
74
|
+
const estimatedScroll = index * 150;
|
|
75
|
+
yield root.evaluate((el, scrollAmount) => {
|
|
76
|
+
el.scrollLeft = scrollAmount;
|
|
77
|
+
}, estimatedScroll);
|
|
78
|
+
yield page.waitForTimeout(300);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
exports.rdgCellNavigation = rdgCellNavigation;
|
|
82
|
+
/**
|
|
83
|
+
* Scrolls the grid vertically to load more virtualized rows.
|
|
84
|
+
*/
|
|
85
|
+
const pagination_1 = require("./pagination");
|
|
86
|
+
const stabilization_1 = require("./stabilization");
|
|
87
|
+
/**
|
|
88
|
+
* Scrolls the grid vertically to load more virtualized rows.
|
|
89
|
+
*/
|
|
90
|
+
exports.rdgPaginationStrategy = pagination_1.PaginationStrategies.infiniteScroll({
|
|
91
|
+
action: 'js-scroll',
|
|
92
|
+
scrollAmount: 500,
|
|
93
|
+
stabilization: stabilization_1.StabilizationStrategies.contentChanged({ timeout: 5000 })
|
|
94
|
+
});
|
|
95
|
+
exports.RDGStrategies = {
|
|
96
|
+
header: exports.scrollRightHeaderRDG,
|
|
97
|
+
getCellLocator: exports.rdgGetCellLocator,
|
|
98
|
+
cellNavigation: exports.rdgCellNavigation,
|
|
99
|
+
pagination: exports.rdgPaginationStrategy
|
|
100
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { StrategyContext } from '../types';
|
|
2
|
+
export interface ColumnResolutionStrategy {
|
|
3
|
+
/**
|
|
4
|
+
* Resolves a column name (string or Regex) to a column index.
|
|
5
|
+
* Returns undefined if not found.
|
|
6
|
+
*/
|
|
7
|
+
resolveIndex(options: {
|
|
8
|
+
query: string | RegExp;
|
|
9
|
+
headerMap: Map<string, number>;
|
|
10
|
+
context: StrategyContext;
|
|
11
|
+
}): number | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a column name to a clean string name (for error messages or debugging).
|
|
14
|
+
*/
|
|
15
|
+
resolveName(options: {
|
|
16
|
+
query: string | RegExp;
|
|
17
|
+
headerMap: Map<string, number>;
|
|
18
|
+
}): string;
|
|
19
|
+
}
|
|
20
|
+
export declare const ResolutionStrategies: {
|
|
21
|
+
default: ColumnResolutionStrategy;
|
|
22
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
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
|
+
};
|