@vuu-ui/vuu-playwright-helpers 3.5.14 → 3.5.16
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 +50 -2
- package/package.json +2 -2
- package/src/PwChart.js +77 -0
- package/src/PwChartDataPoint.js +61 -0
- package/src/PwContextMenu.js +14 -0
- package/src/PwTable.js +11 -7
- package/src/index.js +2 -0
- package/types/PwChart.d.ts +124 -0
- package/types/PwChartDataPoint.d.ts +116 -0
- package/types/PwComponent.d.ts +45 -0
- package/types/PwContextMenu.d.ts +116 -8
- package/types/PwMenuItem.d.ts +150 -2
- package/types/PwTable.d.ts +222 -7
- package/types/PwTableHeaderCell.d.ts +29 -0
- package/types/index.d.ts +2 -0
package/README.md
CHANGED
|
@@ -41,8 +41,16 @@ The existing `locateCell` overloads remain available for gradual migration.
|
|
|
41
41
|
const menu = await table.showContextMenu("ric", 2);
|
|
42
42
|
|
|
43
43
|
expect(await menu.hasMenuItems()).toBe(true);
|
|
44
|
-
|
|
45
|
-
expect(
|
|
44
|
+
// exact match, in order - a partial list is false
|
|
45
|
+
expect(
|
|
46
|
+
await menu.hasMenuItems(["Copy text", "Filter", "Sort", "Delete row"]),
|
|
47
|
+
).toBe(true);
|
|
48
|
+
expect(await menu.menuItemLabels()).toEqual([
|
|
49
|
+
"Copy text",
|
|
50
|
+
"Filter",
|
|
51
|
+
"Sort",
|
|
52
|
+
"Delete row",
|
|
53
|
+
]);
|
|
46
54
|
|
|
47
55
|
const copy = menu.menuItem("Copy text"); // or by 0 based index, menu.menuItem(0)
|
|
48
56
|
expect(await copy.hasIcon("edit")).toBe(true);
|
|
@@ -61,6 +69,46 @@ await menu.press("Enter");
|
|
|
61
69
|
await menu.close(); // Escape
|
|
62
70
|
```
|
|
63
71
|
|
|
72
|
+
To assert that right clicking a cell does not show a context menu, use
|
|
73
|
+
`assertNoContextMenu`. It waits for the page to render after the right click
|
|
74
|
+
before asserting, so it cannot pass before a menu would have appeared.
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
await table.assertNoContextMenu("ccy", 2);
|
|
78
|
+
```
|
|
79
|
+
|
|
64
80
|
When a key press depends on the focus change made by the previous key, call
|
|
65
81
|
`assertFocusedMenuItem` in between. Keys pressed before the menu has processed a
|
|
66
82
|
focus change may be delivered to the previously focused item.
|
|
83
|
+
|
|
84
|
+
## PwChart, PwChartDataPoint
|
|
85
|
+
|
|
86
|
+
`PwChart` wraps a Vuu `Chart` (ECharts, svg renderer). Data points are located by
|
|
87
|
+
series name (the Vuu column name) and 0 based data index, or category (x axis)
|
|
88
|
+
value, mirroring `PwTable.cell(colName, ariaRowIndex)`.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const chart = await new PwChart(page).isReady();
|
|
92
|
+
expect(await chart.seriesNames()).toEqual(["price", "volume"]);
|
|
93
|
+
expect(await chart.categories()).toHaveLength(25);
|
|
94
|
+
|
|
95
|
+
const point = await chart.dataPoint("price", 3); // or chart.dataPoint("price", "2026-04-05")
|
|
96
|
+
expect(point.value).toEqual(103);
|
|
97
|
+
expect(point.isExcluded).toBe(false);
|
|
98
|
+
await point.hover();
|
|
99
|
+
await point.click(); // selects, with a chart selectionModel
|
|
100
|
+
await expect(point.symbol).toHaveAttribute("fill", "purple");
|
|
101
|
+
|
|
102
|
+
const menu = await chart.showContextMenu("price", 3); // or point.showContextMenu()
|
|
103
|
+
await menu.menuItem("Exclude Item").click();
|
|
104
|
+
await chart.assertNoContextMenu("price", 3); // when no menu is configured
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The data point properties (`value`, `x`, `y`, `isExcluded`) are a snapshot, taken
|
|
108
|
+
when the data point is obtained. Mouse actions resolve the position again and wait
|
|
109
|
+
until it is stable, so they work while the chart is resizing. To assert a change to
|
|
110
|
+
the data, obtain the data point again, e.g.
|
|
111
|
+
`await expect.poll(async () => (await chart.dataPoint("price", 3)).isExcluded).toBe(true)`.
|
|
112
|
+
|
|
113
|
+
The Vuu `Chart` exposes its ECharts instance on the chart canvas element
|
|
114
|
+
(`echartsInstance`), which `PwChart` uses to map data points to pixel positions.
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "3.5.
|
|
2
|
+
"version": "3.5.16",
|
|
3
3
|
"author": "heswell",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "./src/index.js",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"type-defs": "node ../../scripts/build-type-defs.mjs"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@vuu-ui/vuu-protocol-types": "3.5.
|
|
12
|
+
"@vuu-ui/vuu-protocol-types": "3.5.16"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
15
|
"@playwright/test": "^1.62.1"
|
package/src/PwChart.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { expect } from "@playwright/test";
|
|
2
|
+
import { PwComponent } from "./PwComponent.js";
|
|
3
|
+
import { PwContextMenu } from "./PwContextMenu.js";
|
|
4
|
+
import { PwChartDataPoint } from "./PwChartDataPoint.js";
|
|
5
|
+
class PwChart extends PwComponent {
|
|
6
|
+
defaultCssSelector = ".vuuChart";
|
|
7
|
+
get canvas() {
|
|
8
|
+
return this.locator.locator("[_echarts_instance_]");
|
|
9
|
+
}
|
|
10
|
+
async isReady(options) {
|
|
11
|
+
await super.isReady(options);
|
|
12
|
+
await expect.poll(()=>this.canvas.evaluate((element)=>(element.echartsInstance?.getOption().series ?? []).some((series)=>series.data.length > 0)), options).toBe(true);
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
async seriesNames() {
|
|
16
|
+
await this.isReady();
|
|
17
|
+
return this.canvas.evaluate((element)=>{
|
|
18
|
+
const series = element.echartsInstance?.getOption().series ?? [];
|
|
19
|
+
return Array.from(new Set(series.map(({ name })=>name)));
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async categories() {
|
|
23
|
+
await this.isReady();
|
|
24
|
+
return this.canvas.evaluate((element)=>element.echartsInstance?.getOption().xAxis?.[0]?.data ?? []);
|
|
25
|
+
}
|
|
26
|
+
async dataPoint(seriesName, dataIndexOrCategory) {
|
|
27
|
+
await this.isReady();
|
|
28
|
+
const resolve = ()=>this.resolveDataPoint(seriesName, dataIndexOrCategory);
|
|
29
|
+
return new PwChartDataPoint(this.page, this.canvas, await resolve(), resolve);
|
|
30
|
+
}
|
|
31
|
+
async resolveDataPoint(seriesName, dataIndexOrCategory) {
|
|
32
|
+
const info = await this.canvas.evaluate((element, [seriesName, dataIndexOrCategory])=>{
|
|
33
|
+
const chart = element.echartsInstance;
|
|
34
|
+
if (!chart) return "chart has no echartsInstance";
|
|
35
|
+
const { series = [], xAxis = [] } = chart.getOption();
|
|
36
|
+
const categories = xAxis[0]?.data ?? [];
|
|
37
|
+
const dataIndex = "number" == typeof dataIndexOrCategory ? dataIndexOrCategory : categories.indexOf(dataIndexOrCategory);
|
|
38
|
+
if (-1 === dataIndex) return `no category "${dataIndexOrCategory}"`;
|
|
39
|
+
const namedSeries = series.filter(({ name })=>name === seriesName);
|
|
40
|
+
if (0 === namedSeries.length) return `no series "${seriesName}"`;
|
|
41
|
+
const valueSeries = namedSeries.find(({ data })=>"number" == typeof data[dataIndex]?.value);
|
|
42
|
+
if (!valueSeries) return `series "${seriesName}" has no value at data index ${dataIndex}`;
|
|
43
|
+
const value = valueSeries.data[dataIndex].value;
|
|
44
|
+
const [x, y] = chart.convertToPixel({
|
|
45
|
+
seriesId: valueSeries.id
|
|
46
|
+
}, [
|
|
47
|
+
dataIndex,
|
|
48
|
+
value
|
|
49
|
+
]);
|
|
50
|
+
return {
|
|
51
|
+
seriesName,
|
|
52
|
+
dataIndex,
|
|
53
|
+
category: categories[dataIndex],
|
|
54
|
+
value,
|
|
55
|
+
isExcluded: valueSeries.id !== seriesName,
|
|
56
|
+
x,
|
|
57
|
+
y
|
|
58
|
+
};
|
|
59
|
+
}, [
|
|
60
|
+
seriesName,
|
|
61
|
+
dataIndexOrCategory
|
|
62
|
+
]);
|
|
63
|
+
if ("string" == typeof info) throw Error(`PwChart, dataPoint, ${info}`);
|
|
64
|
+
return info;
|
|
65
|
+
}
|
|
66
|
+
async showContextMenu(seriesName, dataIndexOrCategory) {
|
|
67
|
+
const dataPoint = await this.dataPoint(seriesName, dataIndexOrCategory);
|
|
68
|
+
return dataPoint.showContextMenu();
|
|
69
|
+
}
|
|
70
|
+
async assertNoContextMenu(seriesName, dataIndexOrCategory) {
|
|
71
|
+
const dataPoint = await this.dataPoint(seriesName, dataIndexOrCategory);
|
|
72
|
+
await PwContextMenu.assertNotOpened(this.page, ()=>dataPoint.click({
|
|
73
|
+
button: "right"
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export { PwChart };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { PlaywrightPageObject } from "./PlaywrightPageObject.js";
|
|
2
|
+
import { PwContextMenu } from "./PwContextMenu.js";
|
|
3
|
+
class PwChartDataPoint extends PlaywrightPageObject {
|
|
4
|
+
canvas;
|
|
5
|
+
resolve;
|
|
6
|
+
seriesName;
|
|
7
|
+
dataIndex;
|
|
8
|
+
category;
|
|
9
|
+
value;
|
|
10
|
+
isExcluded;
|
|
11
|
+
x;
|
|
12
|
+
y;
|
|
13
|
+
constructor(page, canvas, info, resolve){
|
|
14
|
+
super(page), this.canvas = canvas, this.resolve = resolve;
|
|
15
|
+
this.seriesName = info.seriesName;
|
|
16
|
+
this.dataIndex = info.dataIndex;
|
|
17
|
+
this.category = info.category;
|
|
18
|
+
this.value = info.value;
|
|
19
|
+
this.isExcluded = info.isExcluded;
|
|
20
|
+
this.x = info.x;
|
|
21
|
+
this.y = info.y;
|
|
22
|
+
}
|
|
23
|
+
get symbol() {
|
|
24
|
+
const position = `,${round(this.x)},${round(this.y)})`;
|
|
25
|
+
return this.canvas.locator(`svg path[transform$="${position}"]`);
|
|
26
|
+
}
|
|
27
|
+
async click(options) {
|
|
28
|
+
const position = await this.stablePosition();
|
|
29
|
+
await this.canvas.click({
|
|
30
|
+
...options,
|
|
31
|
+
position
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async hover() {
|
|
35
|
+
await this.canvas.hover({
|
|
36
|
+
position: await this.stablePosition()
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
showContextMenu() {
|
|
40
|
+
return PwContextMenu.open(this.page, ()=>this.click({
|
|
41
|
+
button: "right"
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
async stablePosition() {
|
|
45
|
+
let { x, y } = await this.resolve();
|
|
46
|
+
for(let attempt = 0; attempt < MAX_STABLE_ATTEMPTS; attempt++){
|
|
47
|
+
await nextAnimationFrames(this.page);
|
|
48
|
+
const current = await this.resolve();
|
|
49
|
+
if (current.x === x && current.y === y) return {
|
|
50
|
+
x,
|
|
51
|
+
y
|
|
52
|
+
};
|
|
53
|
+
({ x, y } = current);
|
|
54
|
+
}
|
|
55
|
+
throw Error(`PwChartDataPoint, ${this.seriesName}[${this.dataIndex}] position is not stable`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const MAX_STABLE_ATTEMPTS = 50;
|
|
59
|
+
const round = (value)=>parseFloat(value.toFixed(4));
|
|
60
|
+
const nextAnimationFrames = (page)=>page.evaluate(()=>new Promise((resolve)=>requestAnimationFrame(()=>requestAnimationFrame(()=>resolve()))));
|
|
61
|
+
export { PwChartDataPoint };
|
package/src/PwContextMenu.js
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import { expect } from "@playwright/test";
|
|
2
2
|
import { PwComponent } from "./PwComponent.js";
|
|
3
3
|
import { PwMenuItem } from "./PwMenuItem.js";
|
|
4
|
+
import { byId } from "./selector-utils.js";
|
|
4
5
|
class PwContextMenu extends PwComponent {
|
|
5
6
|
defaultCssSelector = '.vuuContextMenuPanel[role="menu"]';
|
|
7
|
+
static async open(page, openAction) {
|
|
8
|
+
await openAction();
|
|
9
|
+
const menu = await new PwContextMenu(page).isReady();
|
|
10
|
+
const menuId = await menu.locator.getAttribute("id");
|
|
11
|
+
const contextMenu = menuId ? new PwContextMenu(page, byId(menuId)) : menu;
|
|
12
|
+
await expect(contextMenu.locator.and(page.locator(":focus-within"))).toHaveCount(1);
|
|
13
|
+
return contextMenu;
|
|
14
|
+
}
|
|
15
|
+
static async assertNotOpened(page, openAction) {
|
|
16
|
+
await openAction();
|
|
17
|
+
await page.evaluate(()=>new Promise((resolve)=>requestAnimationFrame(()=>requestAnimationFrame(()=>resolve()))));
|
|
18
|
+
await new PwContextMenu(page).assertNotAttached();
|
|
19
|
+
}
|
|
6
20
|
get menuItems() {
|
|
7
21
|
return this.locator.getByRole("menuitem");
|
|
8
22
|
}
|
package/src/PwTable.js
CHANGED
|
@@ -2,7 +2,7 @@ import { expect } from "@playwright/test";
|
|
|
2
2
|
import { PwComponent } from "./PwComponent.js";
|
|
3
3
|
import { PwContextMenu } from "./PwContextMenu.js";
|
|
4
4
|
import { PwTableHeaderCell } from "./PwTableHeaderCell.js";
|
|
5
|
-
import { byDataIcon,
|
|
5
|
+
import { byDataIcon, cssAttributeValue } from "./selector-utils.js";
|
|
6
6
|
const byAriaRowIndex = (ariaRowIndex)=>`[aria-rowindex="${ariaRowIndex}"]:scope`;
|
|
7
7
|
const xpathStringLiteral = (value)=>{
|
|
8
8
|
if (!value.includes("'")) return `'${value}'`;
|
|
@@ -53,12 +53,16 @@ class PwTable extends PwComponent {
|
|
|
53
53
|
}
|
|
54
54
|
async showContextMenu(columnName, ariaRowIndex) {
|
|
55
55
|
const cell = await this.cell(columnName, ariaRowIndex);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
return PwContextMenu.open(this.page, ()=>cell.click({
|
|
57
|
+
button: "right"
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
async assertNoContextMenu(columnName, ariaRowIndex) {
|
|
61
|
+
const cell = await this.cell(columnName, ariaRowIndex);
|
|
62
|
+
await PwContextMenu.assertNotOpened(this.page, ()=>cell.click({
|
|
63
|
+
button: "right"
|
|
64
|
+
}));
|
|
65
|
+
await expect(cell).not.toContainClass("ContextOpen");
|
|
62
66
|
}
|
|
63
67
|
locateCell(rowOrText, column) {
|
|
64
68
|
if ("number" == typeof rowOrText && "number" == typeof column) return this.locator.locator(`[aria-rowindex="${rowOrText}"] > [aria-colindex="${column}"]`);
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { PlaywrightPageObject } from "./PlaywrightPageObject.js";
|
|
2
|
+
export { PwChart } from "./PwChart.js";
|
|
3
|
+
export { PwChartDataPoint } from "./PwChartDataPoint.js";
|
|
2
4
|
export { PwComponent } from "./PwComponent.js";
|
|
3
5
|
export { PwContextMenu } from "./PwContextMenu.js";
|
|
4
6
|
export { PwMenuItem } from "./PwMenuItem.js";
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { type Locator } from "@playwright/test";
|
|
2
|
+
import { PwComponent } from "./PwComponent";
|
|
3
|
+
import { PwContextMenu } from "./PwContextMenu";
|
|
4
|
+
import { PwChartDataPoint } from "./PwChartDataPoint";
|
|
5
|
+
/**
|
|
6
|
+
* A Vuu Chart (VuuChart, an ECharts line chart). Data points are identified by
|
|
7
|
+
* series name, which is the Vuu column name, and 0 based data index, which is
|
|
8
|
+
* also the index of the category on the x axis.
|
|
9
|
+
*
|
|
10
|
+
* The chart must use the svg renderer for symbol locators (PwChartDataPoint.symbol).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* test("chart renders price and volume", async ({ mount, page }) => {
|
|
14
|
+
* await mount("Chart/LineChart/SimpleLineChart");
|
|
15
|
+
* const chart = await new PwChart(page).isReady();
|
|
16
|
+
* expect(await chart.seriesNames()).toEqual(["price", "volume"]);
|
|
17
|
+
* const point = await chart.dataPoint("price", 0);
|
|
18
|
+
* expect(point.value).toEqual(100);
|
|
19
|
+
* });
|
|
20
|
+
*/
|
|
21
|
+
export declare class PwChart extends PwComponent {
|
|
22
|
+
protected readonly defaultCssSelector = ".vuuChart";
|
|
23
|
+
/**
|
|
24
|
+
* Locator for the element that renders the chart. This element receives
|
|
25
|
+
* mouse events and holds the ECharts instance.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* test("chart renders svg", async ({ page }) => {
|
|
29
|
+
* const chart = new PwChart(page);
|
|
30
|
+
* await expect(chart.canvas.locator("svg")).toBeVisible();
|
|
31
|
+
* });
|
|
32
|
+
*/
|
|
33
|
+
get canvas(): Locator;
|
|
34
|
+
/**
|
|
35
|
+
* Wait until the chart is visible and has rendered series data. Resolves
|
|
36
|
+
* with the chart, so it can be chained.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* test("chart is ready", async ({ mount, page }) => {
|
|
40
|
+
* await mount("Chart/LineChart/SimpleLineChart");
|
|
41
|
+
* const chart = await new PwChart(page).isReady();
|
|
42
|
+
* expect(await chart.categories()).toHaveLength(25);
|
|
43
|
+
* });
|
|
44
|
+
*/
|
|
45
|
+
isReady(options?: {
|
|
46
|
+
timeout?: number;
|
|
47
|
+
}): Promise<this>;
|
|
48
|
+
/**
|
|
49
|
+
* The names of the rendered series, in render order. These are the Vuu
|
|
50
|
+
* column names of the series columns.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* test("price only", async ({ page }) => {
|
|
54
|
+
* await page.getByRole("button", { name: "Price Only" }).click();
|
|
55
|
+
* const chart = await new PwChart(page).isReady();
|
|
56
|
+
* await expect.poll(() => chart.seriesNames()).toEqual(["price"]);
|
|
57
|
+
* });
|
|
58
|
+
*/
|
|
59
|
+
seriesNames(): Promise<string[]>;
|
|
60
|
+
/**
|
|
61
|
+
* The category (x axis) values, in order.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* test("first category", async ({ page }) => {
|
|
65
|
+
* const chart = await new PwChart(page).isReady();
|
|
66
|
+
* expect((await chart.categories())[0]).toEqual("2026-04-02");
|
|
67
|
+
* });
|
|
68
|
+
*/
|
|
69
|
+
categories(): Promise<string[]>;
|
|
70
|
+
/**
|
|
71
|
+
* Locate a data point by series name and 0 based data index, or by category
|
|
72
|
+
* (x axis) value. Waits for the chart to be ready. Rejects if the series or
|
|
73
|
+
* data point does not exist, or the data point has no value.
|
|
74
|
+
*
|
|
75
|
+
* @param seriesName the series (Vuu column) name
|
|
76
|
+
* @param dataIndexOrCategory 0 based data index, or category value
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* test("locate data points", async ({ page }) => {
|
|
80
|
+
* const chart = new PwChart(page);
|
|
81
|
+
* const byIndex = await chart.dataPoint("price", 3);
|
|
82
|
+
* const byCategory = await chart.dataPoint("price", "2026-04-05");
|
|
83
|
+
* expect(byIndex.value).toEqual(byCategory.value);
|
|
84
|
+
* });
|
|
85
|
+
*/
|
|
86
|
+
dataPoint(seriesName: string, dataIndexOrCategory: number | string): Promise<PwChartDataPoint>;
|
|
87
|
+
private resolveDataPoint;
|
|
88
|
+
/**
|
|
89
|
+
* Right click a data point to invoke the context menu. Resolves once the
|
|
90
|
+
* menu is visible and has focus. Waits until the test timeout if no context
|
|
91
|
+
* menu is shown, use assertNoContextMenu to test for that.
|
|
92
|
+
*
|
|
93
|
+
* @param seriesName the series (Vuu column) name
|
|
94
|
+
* @param dataIndexOrCategory 0 based data index, or category value
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* test("exclude data point", async ({ mount, page }) => {
|
|
98
|
+
* await mount("Chart/LineChart/DataExclusions");
|
|
99
|
+
* const chart = new PwChart(page);
|
|
100
|
+
* const menu = await chart.showContextMenu("price", 3);
|
|
101
|
+
* expect(await menu.hasMenuItems(["Exclude Item"])).toBe(true);
|
|
102
|
+
* await menu.menuItem("Exclude Item").click();
|
|
103
|
+
* await expect
|
|
104
|
+
* .poll(async () => (await chart.dataPoint("price", 3)).isExcluded)
|
|
105
|
+
* .toBe(true);
|
|
106
|
+
* });
|
|
107
|
+
*/
|
|
108
|
+
showContextMenu(seriesName: string, dataIndexOrCategory: number | string): Promise<PwContextMenu>;
|
|
109
|
+
/**
|
|
110
|
+
* Right click a data point and assert that no context menu is shown. Waits
|
|
111
|
+
* for the page to render after the right click before asserting, so it
|
|
112
|
+
* cannot pass before a menu would have appeared.
|
|
113
|
+
*
|
|
114
|
+
* @param seriesName the series (Vuu column) name
|
|
115
|
+
* @param dataIndexOrCategory 0 based data index, or category value
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* test("no context menu in view mode", async ({ mount, page }) => {
|
|
119
|
+
* await mount("Chart/LineChart/EditableChart");
|
|
120
|
+
* await new PwChart(page).assertNoContextMenu("price", 3);
|
|
121
|
+
* });
|
|
122
|
+
*/
|
|
123
|
+
assertNoContextMenu(seriesName: string, dataIndexOrCategory: number | string): Promise<void>;
|
|
124
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { Locator, Page } from "@playwright/test";
|
|
2
|
+
import { PlaywrightPageObject } from "./PlaywrightPageObject";
|
|
3
|
+
import { PwContextMenu } from "./PwContextMenu";
|
|
4
|
+
/**
|
|
5
|
+
* Position and value of a chart data point, as resolved by PwChart.
|
|
6
|
+
*/
|
|
7
|
+
export interface ChartDataPointInfo {
|
|
8
|
+
seriesName: string;
|
|
9
|
+
/**
|
|
10
|
+
* 0 based index into the series data (and the category axis).
|
|
11
|
+
*/
|
|
12
|
+
dataIndex: number;
|
|
13
|
+
/**
|
|
14
|
+
* The category (x axis) value at dataIndex.
|
|
15
|
+
*/
|
|
16
|
+
category: string;
|
|
17
|
+
value: number;
|
|
18
|
+
/**
|
|
19
|
+
* True when the data point is rendered as an excluded data item.
|
|
20
|
+
*/
|
|
21
|
+
isExcluded: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Pixel position of the data point, relative to the chart canvas.
|
|
24
|
+
*/
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A single data point within a PwChart. Obtained from PwChart.dataPoint.
|
|
30
|
+
*
|
|
31
|
+
* The properties (value, x, y etc) are a snapshot, taken when the data point is
|
|
32
|
+
* obtained. Interactions (click, hover, right click) are performed with the
|
|
33
|
+
* mouse at the data point's current position, which is resolved again before
|
|
34
|
+
* each action, waiting until the position is stable (e.g. while the chart is
|
|
35
|
+
* resizing). This mirrors Playwright Locator actionability checks.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* test("select a data point", async ({ page }) => {
|
|
39
|
+
* const chart = new PwChart(page);
|
|
40
|
+
* const point = await chart.dataPoint("price", 3);
|
|
41
|
+
* expect(point.value).toEqual(103);
|
|
42
|
+
* await point.click();
|
|
43
|
+
* await expect(point.symbol).toHaveAttribute("fill", "purple");
|
|
44
|
+
* });
|
|
45
|
+
*/
|
|
46
|
+
export declare class PwChartDataPoint extends PlaywrightPageObject implements ChartDataPointInfo {
|
|
47
|
+
private readonly canvas;
|
|
48
|
+
private readonly resolve;
|
|
49
|
+
readonly seriesName: string;
|
|
50
|
+
readonly dataIndex: number;
|
|
51
|
+
readonly category: string;
|
|
52
|
+
readonly value: number;
|
|
53
|
+
readonly isExcluded: boolean;
|
|
54
|
+
readonly x: number;
|
|
55
|
+
readonly y: number;
|
|
56
|
+
/**
|
|
57
|
+
* @param page the Playwright page
|
|
58
|
+
* @param canvas Locator for the element that renders the chart (and receives mouse events)
|
|
59
|
+
* @param info position and value of the data point
|
|
60
|
+
* @param resolve resolves the current position and value of the data point
|
|
61
|
+
*/
|
|
62
|
+
constructor(page: Page, canvas: Locator, info: ChartDataPointInfo, resolve: () => Promise<ChartDataPointInfo>);
|
|
63
|
+
/**
|
|
64
|
+
* Locator for the symbol rendered at this data point (an SVG path, requires
|
|
65
|
+
* the svg renderer). Matched by position only, so its size and color can be
|
|
66
|
+
* asserted as they change, e.g. on hover or selection. Uses the position
|
|
67
|
+
* when the data point was obtained.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* test("hover enlarges symbol", async ({ page }) => {
|
|
71
|
+
* const point = await new PwChart(page).dataPoint("price", 3);
|
|
72
|
+
* await expect(point.symbol).toBeAttached();
|
|
73
|
+
* await point.hover();
|
|
74
|
+
* await expect(point.symbol).toHaveAttribute("transform", /^matrix\(8,/);
|
|
75
|
+
* });
|
|
76
|
+
*/
|
|
77
|
+
get symbol(): Locator;
|
|
78
|
+
/**
|
|
79
|
+
* Click the data point. With a chart selectionModel, this toggles selection.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* test("click selects data point", async ({ page }) => {
|
|
83
|
+
* const point = await new PwChart(page).dataPoint("price", 3);
|
|
84
|
+
* await point.click();
|
|
85
|
+
* await expect(point.symbol).toHaveAttribute("fill", "purple");
|
|
86
|
+
* });
|
|
87
|
+
*/
|
|
88
|
+
click(options?: Omit<Parameters<Locator["click"]>[0], "position">): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Move the mouse over the data point.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* test("hover highlights data point", async ({ page }) => {
|
|
94
|
+
* const point = await new PwChart(page).dataPoint("price", 3);
|
|
95
|
+
* await point.hover();
|
|
96
|
+
* await expect(point.symbol).toHaveAttribute("transform", /^matrix\(8,/);
|
|
97
|
+
* });
|
|
98
|
+
*/
|
|
99
|
+
hover(): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Right click the data point to invoke the context menu. Resolves once the
|
|
102
|
+
* menu is visible and has focus.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* test("exclude data point", async ({ page }) => {
|
|
106
|
+
* const point = await new PwChart(page).dataPoint("price", 3);
|
|
107
|
+
* const menu = await point.showContextMenu();
|
|
108
|
+
* await menu.menuItem("Exclude Item").click();
|
|
109
|
+
* });
|
|
110
|
+
*/
|
|
111
|
+
showContextMenu(): Promise<PwContextMenu>;
|
|
112
|
+
/**
|
|
113
|
+
* The current position of the data point, once it has stopped moving.
|
|
114
|
+
*/
|
|
115
|
+
private stablePosition;
|
|
116
|
+
}
|
package/types/PwComponent.d.ts
CHANGED
|
@@ -9,6 +9,18 @@ export type PwComponentTarget = string | Locator;
|
|
|
9
9
|
* Base class for helpers that wrap a single component instance. Subclasses
|
|
10
10
|
* provide a defaultCssSelector, which can be overridden at runtime by passing
|
|
11
11
|
* a css selector or Locator to the constructor.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* // a hypothetical helper for a toolbar component
|
|
15
|
+
* export class PwToolbar extends PwComponent {
|
|
16
|
+
* protected readonly defaultCssSelector = ".vuuToolbar";
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* test("toolbar renders", async ({ page }) => {
|
|
20
|
+
* await new PwToolbar(page).assertVisible();
|
|
21
|
+
* await new PwToolbar(page, "#main-toolbar").assertVisible();
|
|
22
|
+
* await new PwToolbar(page, page.getByTestId("toolbar")).assertVisible();
|
|
23
|
+
* });
|
|
12
24
|
*/
|
|
13
25
|
export declare abstract class PwComponent extends PlaywrightPageObject {
|
|
14
26
|
protected readonly target?: PwComponentTarget | undefined;
|
|
@@ -21,6 +33,12 @@ export declare abstract class PwComponent extends PlaywrightPageObject {
|
|
|
21
33
|
/**
|
|
22
34
|
* Locator for the component root element. Use this for web-first assertions
|
|
23
35
|
* that are not covered by a helper method.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* test("menu is vertical", async ({ page }) => {
|
|
39
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
40
|
+
* await expect(menu.locator).toHaveAttribute("aria-orientation", "vertical");
|
|
41
|
+
* });
|
|
24
42
|
*/
|
|
25
43
|
get locator(): Locator;
|
|
26
44
|
/**
|
|
@@ -36,8 +54,35 @@ export declare abstract class PwComponent extends PlaywrightPageObject {
|
|
|
36
54
|
/**
|
|
37
55
|
* Is the component currently visible. Does not wait or retry, prefer
|
|
38
56
|
* assertVisible within tests unless the component is known to be stable.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* test("menu is open", async ({ page }) => {
|
|
60
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
61
|
+
* expect(await menu.isVisible()).toBe(true);
|
|
62
|
+
* });
|
|
39
63
|
*/
|
|
40
64
|
isVisible(): Promise<boolean>;
|
|
65
|
+
/**
|
|
66
|
+
* Assert the component is visible, retrying until the expect timeout.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* test("table renders", async ({ mount, page }) => {
|
|
70
|
+
* await mount("Table/ContextMenu/LocalContextMenu");
|
|
71
|
+
* await new PwTable(page).assertVisible();
|
|
72
|
+
* });
|
|
73
|
+
*/
|
|
41
74
|
assertVisible(): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Assert the component is not in the DOM, retrying until the expect timeout.
|
|
77
|
+
* Passes as soon as the element is absent, so it does not prove an element
|
|
78
|
+
* will not appear later (see PwTable.assertNoContextMenu).
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* test("menu closes after click", async ({ page }) => {
|
|
82
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
83
|
+
* await menu.menuItem("Copy text").click();
|
|
84
|
+
* await menu.assertNotAttached();
|
|
85
|
+
* });
|
|
86
|
+
*/
|
|
42
87
|
assertNotAttached(): Promise<void>;
|
|
43
88
|
}
|
package/types/PwContextMenu.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Locator } from "@playwright/test";
|
|
1
|
+
import { type Locator, type Page } from "@playwright/test";
|
|
2
2
|
import { PwComponent } from "./PwComponent";
|
|
3
3
|
import { PwMenuItem } from "./PwMenuItem";
|
|
4
4
|
/**
|
|
@@ -9,7 +9,9 @@ import { PwMenuItem } from "./PwMenuItem";
|
|
|
9
9
|
* test("cell menu", async ({ page }) => {
|
|
10
10
|
* const table = new PwTable(page);
|
|
11
11
|
* const menu = await table.showContextMenu("ric", 2);
|
|
12
|
-
* expect(
|
|
12
|
+
* expect(
|
|
13
|
+
* await menu.hasMenuItems(["Copy text", "Filter", "Sort", "Delete row"]),
|
|
14
|
+
* ).toBe(true);
|
|
13
15
|
* const filterMenu = await menu.menuItem("Filter").openSubMenu();
|
|
14
16
|
* await filterMenu.menuItem("Include value").click();
|
|
15
17
|
* await menu.assertNotAttached();
|
|
@@ -17,33 +19,129 @@ import { PwMenuItem } from "./PwMenuItem";
|
|
|
17
19
|
*/
|
|
18
20
|
export declare class PwContextMenu extends PwComponent {
|
|
19
21
|
protected readonly defaultCssSelector = ".vuuContextMenuPanel[role=\"menu\"]";
|
|
22
|
+
/**
|
|
23
|
+
* Perform the action that opens a context menu (e.g. a right click), then
|
|
24
|
+
* resolve with the menu once it is visible and has focus. Component helpers
|
|
25
|
+
* use this to implement showContextMenu.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* test("context menu from custom element", async ({ page }) => {
|
|
29
|
+
* const menu = await PwContextMenu.open(page, () =>
|
|
30
|
+
* page.getByTestId("panel").click({ button: "right" }),
|
|
31
|
+
* );
|
|
32
|
+
* expect(await menu.hasMenuItems()).toBe(true);
|
|
33
|
+
* });
|
|
34
|
+
*/
|
|
35
|
+
static open(page: Page, openAction: () => Promise<unknown>): Promise<PwContextMenu>;
|
|
36
|
+
/**
|
|
37
|
+
* Perform the action that would open a context menu (e.g. a right click),
|
|
38
|
+
* then assert that no context menu is shown.
|
|
39
|
+
*
|
|
40
|
+
* A negative assertion passes as soon as the menu is absent, which may be
|
|
41
|
+
* before a menu would have rendered. To avoid false passes we wait for two
|
|
42
|
+
* animation frames after the action, allowing React to commit and the menu
|
|
43
|
+
* to be positioned, before asserting.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* test("no context menu on panel", async ({ page }) => {
|
|
47
|
+
* await PwContextMenu.assertNotOpened(page, () =>
|
|
48
|
+
* page.getByTestId("panel").click({ button: "right" }),
|
|
49
|
+
* );
|
|
50
|
+
* });
|
|
51
|
+
*/
|
|
52
|
+
static assertNotOpened(page: Page, openAction: () => Promise<unknown>): Promise<void>;
|
|
20
53
|
/**
|
|
21
54
|
* Locator for the menu items of this menu. Sub menus are rendered in their
|
|
22
55
|
* own (portalled) panels, so their items are not included.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* test("menu has four items", async ({ page }) => {
|
|
59
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
60
|
+
* await expect(menu.menuItems).toHaveCount(4);
|
|
61
|
+
* });
|
|
23
62
|
*/
|
|
24
63
|
get menuItems(): Locator;
|
|
25
64
|
/**
|
|
26
65
|
* The labels of the menu items, in rendered order.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* test("menu items are in expected order", async ({ page }) => {
|
|
69
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
70
|
+
* expect(await menu.menuItemLabels()).toEqual([
|
|
71
|
+
* "Copy text",
|
|
72
|
+
* "Filter",
|
|
73
|
+
* "Sort",
|
|
74
|
+
* "Delete row",
|
|
75
|
+
* ]);
|
|
76
|
+
* });
|
|
27
77
|
*/
|
|
28
78
|
menuItemLabels(): Promise<string[]>;
|
|
29
79
|
/**
|
|
30
80
|
* With no argument, does the menu have any menu items. With labels, does
|
|
31
|
-
* the menu have exactly these menu items, in this order.
|
|
81
|
+
* the menu have exactly these menu items, in this order. Labels are compared
|
|
82
|
+
* exactly, a partial list returns false.
|
|
83
|
+
*
|
|
84
|
+
* Does not wait or retry. The menu items are rendered with the menu, so this
|
|
85
|
+
* is safe to call once the menu is ready (e.g. after showContextMenu).
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* test("cell menu has expected items", async ({ page }) => {
|
|
89
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
90
|
+
* expect(await menu.hasMenuItems()).toBe(true);
|
|
91
|
+
* expect(
|
|
92
|
+
* await menu.hasMenuItems(["Copy text", "Filter", "Sort", "Delete row"]),
|
|
93
|
+
* ).toBe(true);
|
|
94
|
+
* // exact match, a partial list is false
|
|
95
|
+
* expect(await menu.hasMenuItems(["Copy text", "Filter"])).toBe(false);
|
|
96
|
+
* });
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* test("sub menu has expected items", async ({ page }) => {
|
|
100
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
101
|
+
* const filterMenu = await menu.menuItem("Filter").openSubMenu();
|
|
102
|
+
* expect(
|
|
103
|
+
* await filterMenu.hasMenuItems(["Include value", "Exclude value"]),
|
|
104
|
+
* ).toBe(true);
|
|
105
|
+
* });
|
|
32
106
|
*/
|
|
33
107
|
hasMenuItems(labels?: string[]): Promise<boolean>;
|
|
34
108
|
/**
|
|
35
|
-
* Menu item by label, or by 0 based index.
|
|
109
|
+
* Menu item by label (exact match), or by 0 based index.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* test("locate menu items", async ({ page }) => {
|
|
113
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
114
|
+
* const copy = menu.menuItem("Copy text");
|
|
115
|
+
* expect(await copy.isEnabled()).toBe(true);
|
|
116
|
+
* expect(await menu.menuItem(1).label()).toEqual("Filter");
|
|
117
|
+
* });
|
|
36
118
|
*/
|
|
37
119
|
menuItem(labelOrIndex: string | number): PwMenuItem;
|
|
38
120
|
/**
|
|
39
121
|
* The menu item that currently has keyboard focus. The item must also be the
|
|
40
122
|
* menu's active item (tabindex="0"), which confirms the menu has processed the
|
|
41
123
|
* focus change and is ready for the next key press.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* test("first item focused after ArrowDown", async ({ page }) => {
|
|
127
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
128
|
+
* await menu.press("ArrowDown");
|
|
129
|
+
* await expect(menu.focusedMenuItem.locator).toHaveText("Copy text");
|
|
130
|
+
* });
|
|
42
131
|
*/
|
|
43
132
|
get focusedMenuItem(): PwMenuItem;
|
|
44
133
|
/**
|
|
45
134
|
* Assert the focused menu item. Wait on this between key presses, keys
|
|
46
135
|
* pressed before the menu has processed a focus change may be misdirected.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* test("arrow keys move focus", async ({ page }) => {
|
|
139
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
140
|
+
* await menu.press("ArrowDown");
|
|
141
|
+
* await menu.assertFocusedMenuItem("Copy text");
|
|
142
|
+
* await menu.press("ArrowDown");
|
|
143
|
+
* await menu.assertFocusedMenuItem("Filter");
|
|
144
|
+
* });
|
|
47
145
|
*/
|
|
48
146
|
assertFocusedMenuItem(label: string): Promise<void>;
|
|
49
147
|
/**
|
|
@@ -53,13 +151,23 @@ export declare class PwContextMenu extends PwComponent {
|
|
|
53
151
|
* a key depends on the focus change of the previous key.
|
|
54
152
|
*
|
|
55
153
|
* @example
|
|
56
|
-
*
|
|
57
|
-
* await
|
|
58
|
-
*
|
|
154
|
+
* test("Enter invokes focused item", async ({ page }) => {
|
|
155
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
156
|
+
* await menu.press("ArrowDown");
|
|
157
|
+
* await menu.assertFocusedMenuItem("Copy text");
|
|
158
|
+
* await menu.press("Enter");
|
|
159
|
+
* await menu.assertNotAttached();
|
|
160
|
+
* });
|
|
59
161
|
*/
|
|
60
162
|
press(...keys: string[]): Promise<void>;
|
|
61
163
|
/**
|
|
62
|
-
* Close the menu by pressing Escape.
|
|
164
|
+
* Close the menu by pressing Escape. Resolves once the menu is removed.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* test("Escape closes menu", async ({ page }) => {
|
|
168
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
169
|
+
* await menu.close();
|
|
170
|
+
* });
|
|
63
171
|
*/
|
|
64
172
|
close(): Promise<void>;
|
|
65
173
|
}
|
package/types/PwMenuItem.d.ts
CHANGED
|
@@ -4,45 +4,193 @@ import { PwContextMenu } from "./PwContextMenu";
|
|
|
4
4
|
/**
|
|
5
5
|
* A single item within a PwContextMenu. Usually obtained from
|
|
6
6
|
* PwContextMenu.menuItem rather than constructed directly.
|
|
7
|
+
*
|
|
8
|
+
* The boolean query methods (hasChildren, isDisabled, hasIcon etc) read the
|
|
9
|
+
* current state and do not retry. For state that changes after an action, use
|
|
10
|
+
* a web-first assertion on `locator`, e.g.
|
|
11
|
+
* `await expect(item.locator).toHaveAttribute("aria-expanded", "true")`.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* test("menu item state", async ({ page }) => {
|
|
15
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
16
|
+
* const item = menu.menuItem("Copy text");
|
|
17
|
+
* expect(await item.isEnabled()).toBe(true);
|
|
18
|
+
* expect(await item.hasIcon("edit")).toBe(true);
|
|
19
|
+
* await item.click();
|
|
20
|
+
* });
|
|
7
21
|
*/
|
|
8
22
|
export declare class PwMenuItem extends PwComponent {
|
|
9
23
|
protected readonly defaultCssSelector = "[role=\"menuitem\"]";
|
|
10
24
|
/**
|
|
11
25
|
* The text label of the menu item.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* test("second item is Filter", async ({ page }) => {
|
|
29
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
30
|
+
* expect(await menu.menuItem(1).label()).toEqual("Filter");
|
|
31
|
+
* });
|
|
12
32
|
*/
|
|
13
33
|
label(): Promise<string>;
|
|
14
34
|
/**
|
|
15
|
-
* Does this menu item open a sub menu.
|
|
35
|
+
* Does this menu item open a sub menu (aria-haspopup="menu").
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* test("Filter has a sub menu", async ({ page }) => {
|
|
39
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
40
|
+
* expect(await menu.menuItem("Filter").hasChildren()).toBe(true);
|
|
41
|
+
* expect(await menu.menuItem("Copy text").hasChildren()).toBe(false);
|
|
42
|
+
* });
|
|
16
43
|
*/
|
|
17
44
|
hasChildren(): Promise<boolean>;
|
|
45
|
+
/**
|
|
46
|
+
* Is the menu item disabled (aria-disabled="true"). Disabled items cannot be
|
|
47
|
+
* clicked and are skipped by keyboard navigation.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* test("Delete row is disabled", async ({ page }) => {
|
|
51
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
52
|
+
* expect(await menu.menuItem("Delete row").isDisabled()).toBe(true);
|
|
53
|
+
* });
|
|
54
|
+
*/
|
|
18
55
|
isDisabled(): Promise<boolean>;
|
|
56
|
+
/**
|
|
57
|
+
* Is the menu item enabled. The inverse of isDisabled.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* test("Copy text is enabled", async ({ page }) => {
|
|
61
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
62
|
+
* expect(await menu.menuItem("Copy text").isEnabled()).toBe(true);
|
|
63
|
+
* });
|
|
64
|
+
*/
|
|
19
65
|
isEnabled(): Promise<boolean>;
|
|
20
66
|
/**
|
|
21
|
-
* Is the sub menu of this menu item open. Always
|
|
67
|
+
* Is the sub menu of this menu item open (aria-expanded="true"). Always
|
|
68
|
+
* false for leaf items.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* test("Filter expands on hover", async ({ page }) => {
|
|
72
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
73
|
+
* const filter = menu.menuItem("Filter");
|
|
74
|
+
* expect(await filter.isExpanded()).toBe(false);
|
|
75
|
+
* await filter.openSubMenu();
|
|
76
|
+
* expect(await filter.isExpanded()).toBe(true);
|
|
77
|
+
* });
|
|
22
78
|
*/
|
|
23
79
|
isExpanded(): Promise<boolean>;
|
|
80
|
+
/**
|
|
81
|
+
* Does the menu item have DOM focus. Does not wait or retry, prefer
|
|
82
|
+
* PwContextMenu.assertFocusedMenuItem after a key press.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* test("Copy text focused", async ({ page }) => {
|
|
86
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
87
|
+
* await menu.press("ArrowDown");
|
|
88
|
+
* await menu.assertFocusedMenuItem("Copy text");
|
|
89
|
+
* expect(await menu.menuItem("Copy text").isFocused()).toBe(true);
|
|
90
|
+
* });
|
|
91
|
+
*/
|
|
24
92
|
isFocused(): Promise<boolean>;
|
|
25
93
|
/**
|
|
26
94
|
* Is the menu item decorated with the given icon (data-icon attribute).
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* test("Copy text has edit icon", async ({ page }) => {
|
|
98
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
99
|
+
* expect(await menu.menuItem("Copy text").hasIcon("edit")).toBe(true);
|
|
100
|
+
* });
|
|
27
101
|
*/
|
|
28
102
|
hasIcon(iconName: string): Promise<boolean>;
|
|
29
103
|
/**
|
|
30
104
|
* Click the menu item. Playwright will not click a disabled item unless
|
|
31
105
|
* { force: true } is passed.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* test("click invokes menu action", async ({ page }) => {
|
|
109
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
110
|
+
* await menu.menuItem("Copy text").click();
|
|
111
|
+
* await menu.assertNotAttached();
|
|
112
|
+
* });
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* test("clicking disabled item does nothing", async ({ page }) => {
|
|
116
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
117
|
+
* await menu.menuItem("Delete row").click({ force: true });
|
|
118
|
+
* await menu.assertVisible();
|
|
119
|
+
* });
|
|
32
120
|
*/
|
|
33
121
|
click(options?: Parameters<Locator["click"]>[0]): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Hover over the menu item. Hovering an item with children opens its sub
|
|
124
|
+
* menu, use openSubMenu to also wait for the sub menu.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* test("hover expands Filter", async ({ page }) => {
|
|
128
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
129
|
+
* const filter = menu.menuItem("Filter");
|
|
130
|
+
* await filter.hover();
|
|
131
|
+
* await expect(filter.locator).toHaveAttribute("aria-expanded", "true");
|
|
132
|
+
* });
|
|
133
|
+
*/
|
|
34
134
|
hover(): Promise<void>;
|
|
135
|
+
/**
|
|
136
|
+
* Give the menu item DOM focus.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* test("focus Sort then open with keyboard", async ({ page }) => {
|
|
140
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
141
|
+
* const sort = menu.menuItem("Sort");
|
|
142
|
+
* await sort.focus();
|
|
143
|
+
* await sort.press("ArrowRight");
|
|
144
|
+
* await expect(sort.locator).toHaveAttribute("aria-expanded", "true");
|
|
145
|
+
* });
|
|
146
|
+
*/
|
|
35
147
|
focus(): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* Press a key with the menu item as target. Focuses the item first.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* test("Enter on item invokes action", async ({ page }) => {
|
|
153
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
154
|
+
* await menu.menuItem("Copy text").press("Enter");
|
|
155
|
+
* await menu.assertNotAttached();
|
|
156
|
+
* });
|
|
157
|
+
*/
|
|
36
158
|
press(key: string): Promise<void>;
|
|
37
159
|
/**
|
|
38
160
|
* The sub menu opened by this menu item. The sub menu must already be open,
|
|
39
161
|
* use openSubMenu to open it.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* test("sub menu opened by hover", async ({ page }) => {
|
|
165
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
166
|
+
* const filter = menu.menuItem("Filter");
|
|
167
|
+
* await filter.hover();
|
|
168
|
+
* const filterMenu = await filter.subMenu();
|
|
169
|
+
* expect(await filterMenu.hasMenuItems()).toBe(true);
|
|
170
|
+
* });
|
|
40
171
|
*/
|
|
41
172
|
subMenu(): Promise<PwContextMenu>;
|
|
42
173
|
/**
|
|
43
174
|
* Open the sub menu of this menu item, using the mouse (hover) or keyboard
|
|
44
175
|
* (ArrowRight). Resolves once the sub menu is visible and, when opened with
|
|
45
176
|
* the keyboard, once focus has moved into the sub menu.
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* test("open sub menu with mouse", async ({ page }) => {
|
|
180
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
181
|
+
* const filterMenu = await menu.menuItem("Filter").openSubMenu();
|
|
182
|
+
* await filterMenu.menuItem("Include value").click();
|
|
183
|
+
* await menu.assertNotAttached();
|
|
184
|
+
* });
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* test("open sub menu with keyboard", async ({ page }) => {
|
|
188
|
+
* const menu = await new PwTable(page).showContextMenu("ric", 2);
|
|
189
|
+
* const sortMenu = await menu.menuItem("Sort").openSubMenu("keyboard");
|
|
190
|
+
* await sortMenu.assertFocusedMenuItem("Ascending");
|
|
191
|
+
* await sortMenu.press("ArrowLeft");
|
|
192
|
+
* await sortMenu.assertNotAttached();
|
|
193
|
+
* });
|
|
46
194
|
*/
|
|
47
195
|
openSubMenu(using?: "mouse" | "keyboard"): Promise<PwContextMenu>;
|
|
48
196
|
}
|
package/types/PwTable.d.ts
CHANGED
|
@@ -4,18 +4,79 @@ import { PwComponent } from "./PwComponent";
|
|
|
4
4
|
import { PwContextMenu } from "./PwContextMenu";
|
|
5
5
|
import { PwTableHeaderCell } from "./PwTableHeaderCell";
|
|
6
6
|
type AriaRole = "cell" | "textbox" | "combobox";
|
|
7
|
+
/**
|
|
8
|
+
* A Vuu Table. Rows are located by 1-based aria row index, which includes
|
|
9
|
+
* header rows (with one header, the first data row is 2).
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* test("table renders instruments", async ({ mount, page }) => {
|
|
13
|
+
* await mount("Table/ContextMenu/LocalContextMenu");
|
|
14
|
+
* const table = new PwTable(page);
|
|
15
|
+
* await table.assertVisible();
|
|
16
|
+
* await expect(await table.cell("ric", 2)).toHaveText("AAOO.L");
|
|
17
|
+
* });
|
|
18
|
+
*/
|
|
7
19
|
export declare class PwTable extends PwComponent {
|
|
8
20
|
protected readonly defaultCssSelector = ".vuuTable";
|
|
21
|
+
/**
|
|
22
|
+
* Locator for the scrollable content container (the rendered rows).
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* test("rows are zebra striped", async ({ page }) => {
|
|
26
|
+
* const table = new PwTable(page);
|
|
27
|
+
* await expect(table.contentContainer).toContainClass("vuuTable-zebra");
|
|
28
|
+
* });
|
|
29
|
+
*/
|
|
9
30
|
get contentContainer(): Locator;
|
|
31
|
+
/**
|
|
32
|
+
* Locator for the scrollbar container, the element that scrolls.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* test("scrolls vertically", async ({ page }) => {
|
|
36
|
+
* const table = new PwTable(page);
|
|
37
|
+
* await table.scrollbarContainer.evaluate((el) => el.scrollTo(0, 1000));
|
|
38
|
+
* await expect(table.row(2)).not.toBeInViewport();
|
|
39
|
+
* });
|
|
40
|
+
*/
|
|
10
41
|
get scrollbarContainer(): Locator;
|
|
11
42
|
/**
|
|
12
|
-
*
|
|
43
|
+
* Row Locator by 1-based aria row index, including headers.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* test("first data row is selected", async ({ page }) => {
|
|
47
|
+
* const table = new PwTable(page);
|
|
48
|
+
* await (await table.cell("ric", 2)).click();
|
|
49
|
+
* await expect(table.row(2)).toHaveAttribute("aria-selected", "true");
|
|
50
|
+
* });
|
|
13
51
|
*/
|
|
14
52
|
row(row: number): Locator;
|
|
15
53
|
/**
|
|
16
54
|
* Locate cell by column name and 1-based aria row index, including headers.
|
|
55
|
+
* Preferred over locateCell for new usage.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* test("first ric", async ({ page }) => {
|
|
59
|
+
* const table = new PwTable(page);
|
|
60
|
+
* await expect(await table.cell("ric", 2)).toHaveText("AAOO.L");
|
|
61
|
+
* });
|
|
17
62
|
*/
|
|
18
63
|
cell(colName: string, ariaRowIndex: number): Promise<Locator>;
|
|
64
|
+
/**
|
|
65
|
+
* Double click a cell to edit it, fill the editor with value, then commit
|
|
66
|
+
* (Enter) or cancel (Escape) the edit.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* test("edit bbg", async ({ mount, page }) => {
|
|
70
|
+
* await mount("Table/Editing/EditableInstrumentsInlineEdit");
|
|
71
|
+
* await page.getByRole("radio", { name: "Edit" }).click();
|
|
72
|
+
* const table = new PwTable(page);
|
|
73
|
+
* const textbox = (await table.cell("bbg", 2)).getByRole("textbox");
|
|
74
|
+
* await table.editCell("bbg", 2, "X", "cancel");
|
|
75
|
+
* await expect(textbox).not.toHaveValue("X");
|
|
76
|
+
* await table.editCell("bbg", 2, "X");
|
|
77
|
+
* await expect(textbox).toHaveValue("X");
|
|
78
|
+
* });
|
|
79
|
+
*/
|
|
19
80
|
editCell(columnName: string, ariaRowIndex: number, value: string, action?: "commit" | "cancel"): Promise<void>;
|
|
20
81
|
/**
|
|
21
82
|
* The labels displayed in the column headers, in rendered order.
|
|
@@ -60,10 +121,21 @@ export declare class PwTable extends PwComponent {
|
|
|
60
121
|
* });
|
|
61
122
|
*/
|
|
62
123
|
cellsWithIcon(iconName: string): Locator;
|
|
124
|
+
/**
|
|
125
|
+
* The column header cell for the named column.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* test("currency column not selected", async ({ page }) => {
|
|
129
|
+
* const table = new PwTable(page);
|
|
130
|
+
* expect(await table.headerCell("currency").isSelected).toBe(false);
|
|
131
|
+
* });
|
|
132
|
+
*/
|
|
63
133
|
headerCell(columnName: string): PwTableHeaderCell;
|
|
64
134
|
/**
|
|
65
135
|
* Right click a cell to invoke the context menu. Resolves once the menu is
|
|
66
|
-
* visible
|
|
136
|
+
* visible and has focus, so keyboard navigation can begin immediately.
|
|
137
|
+
* Waits until the test timeout if no context menu is shown, use
|
|
138
|
+
* assertNoContextMenu to test for that.
|
|
67
139
|
*
|
|
68
140
|
* @param columnName name of the column
|
|
69
141
|
* @param ariaRowIndex 1-based aria row index, including headers (with one header, the first data row is 2)
|
|
@@ -77,31 +149,174 @@ export declare class PwTable extends PwComponent {
|
|
|
77
149
|
*/
|
|
78
150
|
showContextMenu(columnName: string, ariaRowIndex: number): Promise<PwContextMenu>;
|
|
79
151
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
152
|
+
* Right click a cell and assert that no context menu is shown and the cell
|
|
153
|
+
* is not marked as having an open context menu.
|
|
154
|
+
*
|
|
155
|
+
* A negative assertion passes as soon as the menu is absent, which may be
|
|
156
|
+
* before a menu would have rendered. To avoid false passes we wait for two
|
|
157
|
+
* animation frames after the right click, allowing React to commit and the
|
|
158
|
+
* menu to be positioned, before asserting.
|
|
159
|
+
*
|
|
160
|
+
* @param columnName name of the column
|
|
161
|
+
* @param ariaRowIndex 1-based aria row index, including headers (with one header, the first data row is 2)
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* test("no menu when none configured", async ({ page }) => {
|
|
165
|
+
* const table = new PwTable(page);
|
|
166
|
+
* await table.assertNoContextMenu("ccy", 2);
|
|
167
|
+
* });
|
|
168
|
+
*/
|
|
169
|
+
assertNoContextMenu(columnName: string, ariaRowIndex: number): Promise<void>;
|
|
170
|
+
/**
|
|
171
|
+
* Locate cell(s) by accessible name (cell contents). May match several cells.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* test("right click ric", async ({ page }) => {
|
|
175
|
+
* const table = new PwTable(page);
|
|
176
|
+
* await table.locateCell("AAOP N").click({ button: "right" });
|
|
177
|
+
* });
|
|
82
178
|
*/
|
|
83
179
|
locateCell(text: string): Locator;
|
|
84
180
|
/**
|
|
85
|
-
* Locate cell by aria row index and aria col index
|
|
86
|
-
*
|
|
181
|
+
* Locate cell by 1-based aria row index and 1-based aria col index.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* test("down arrow from header focuses first cell", async ({ page }) => {
|
|
185
|
+
* const table = new PwTable(page);
|
|
186
|
+
* await table.locateColumnHeader(1).press("ArrowDown");
|
|
187
|
+
* await expect(table.locateCell(2, 1)).toBeFocused();
|
|
188
|
+
* });
|
|
87
189
|
*/
|
|
88
190
|
locateCell(row: number, column: number): Locator;
|
|
89
191
|
/**
|
|
90
|
-
* Locate cell by aria row index and column name
|
|
192
|
+
* Locate cell by 1-based aria row index and column name. Prefer cell(columnName, ariaRowIndex).
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* test("first ric", async ({ page }) => {
|
|
196
|
+
* const table = new PwTable(page);
|
|
197
|
+
* await expect(await table.locateCell(2, "ric")).toHaveText("AAOO.L");
|
|
198
|
+
* });
|
|
91
199
|
*/
|
|
92
200
|
locateCell(row: number, columnName: string): Promise<Locator>;
|
|
201
|
+
/**
|
|
202
|
+
* Locate a column header cell by 1-based aria col index.
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* test("click header focuses it", async ({ page }) => {
|
|
206
|
+
* const table = new PwTable(page);
|
|
207
|
+
* const header = table.locateColumnHeader(1);
|
|
208
|
+
* await header.click();
|
|
209
|
+
* await expect(header).toBeFocused();
|
|
210
|
+
* });
|
|
211
|
+
*/
|
|
93
212
|
locateColumnHeader(column: number): Locator;
|
|
213
|
+
/**
|
|
214
|
+
* When isEditable is true, assert the cell is editable but not currently in
|
|
215
|
+
* edit mode and, if value is passed, that its textbox has that value. When
|
|
216
|
+
* isEditable is false or undefined, nothing is asserted.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* test("first cell is editable", async ({ mount, page }) => {
|
|
220
|
+
* await mount("Table/Editing/EditableInstruments");
|
|
221
|
+
* await page.getByRole("radio", { name: "Edit" }).click();
|
|
222
|
+
* const table = new PwTable(page);
|
|
223
|
+
* await table.assertCellIsEditable(2, 1, true, "AAOO L");
|
|
224
|
+
* });
|
|
225
|
+
*/
|
|
94
226
|
assertCellIsEditable(row: number, column: number, isEditable: boolean | undefined, value?: string): Promise<void>;
|
|
95
227
|
private getCellText;
|
|
228
|
+
/**
|
|
229
|
+
* Assert the value of a cell input (toHaveValue). Pass a role to target an
|
|
230
|
+
* element within the cell, e.g. "textbox" for an editable cell.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* test("editable cell value", async ({ mount, page }) => {
|
|
234
|
+
* await mount("Table/Editing/EditableInstruments");
|
|
235
|
+
* await page.getByRole("radio", { name: "Edit" }).click();
|
|
236
|
+
* const table = new PwTable(page);
|
|
237
|
+
* await table.assertCellValue(2, 1, "AAOO L", "textbox");
|
|
238
|
+
* await table.assertCellValue(table.locateCell(2, 1), "AAOO L", "textbox");
|
|
239
|
+
* });
|
|
240
|
+
*/
|
|
96
241
|
assertCellValue(cell: Locator, text: string, role?: AriaRole): Promise<void>;
|
|
97
242
|
assertCellValue(row: number, column: number, text: string, role?: AriaRole): Promise<void>;
|
|
243
|
+
/**
|
|
244
|
+
* Assert the text content of a cell (toHaveText). Pass a role to target an
|
|
245
|
+
* element within the cell, e.g. "combobox" for a dropdown cell.
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* test("first cell text", async ({ page }) => {
|
|
249
|
+
* const table = new PwTable(page);
|
|
250
|
+
* await table.assertCellContent(2, 1, "AAOO L");
|
|
251
|
+
* await table.assertCellContent(table.locateCell(2, 1), "AAOO L");
|
|
252
|
+
* });
|
|
253
|
+
*/
|
|
98
254
|
assertCellContent(cell: Locator, text: string, role?: AriaRole): Promise<void>;
|
|
99
255
|
assertCellContent(row: number, column: number, text: string, role?: AriaRole): Promise<void>;
|
|
256
|
+
/**
|
|
257
|
+
* Assert a cell, or an element within it with the given role, is focused.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* test("click focuses editable cell", async ({ mount, page }) => {
|
|
261
|
+
* await mount("Table/Editing/EditableInstruments");
|
|
262
|
+
* await page.getByRole("radio", { name: "Edit" }).click();
|
|
263
|
+
* const table = new PwTable(page);
|
|
264
|
+
* const cell = table.locateCell(3, 3);
|
|
265
|
+
* await cell.click();
|
|
266
|
+
* await table.assertCellIsFocused(cell, "textbox");
|
|
267
|
+
* await table.assertCellIsFocused(3, 3, "textbox");
|
|
268
|
+
* });
|
|
269
|
+
*/
|
|
100
270
|
assertCellIsFocused(cell: Locator, role?: AriaRole): Promise<void>;
|
|
101
271
|
assertCellIsFocused(row: number, column: number, role?: AriaRole): Promise<void>;
|
|
272
|
+
/**
|
|
273
|
+
* Assert a cell is (default) or is not in edit mode.
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* test("Enter starts editing, Escape stops", async ({ mount, page }) => {
|
|
277
|
+
* await mount("Table/Editing/EditableInstruments");
|
|
278
|
+
* await page.getByRole("radio", { name: "Edit" }).click();
|
|
279
|
+
* const table = new PwTable(page);
|
|
280
|
+
* const cell = table.locateCell(3, 3);
|
|
281
|
+
* await cell.click();
|
|
282
|
+
* await cell.press("Enter");
|
|
283
|
+
* await table.assertCellIsEditing(cell);
|
|
284
|
+
* await cell.press("Escape");
|
|
285
|
+
* await table.assertCellIsEditing(cell, false);
|
|
286
|
+
* });
|
|
287
|
+
*/
|
|
102
288
|
assertCellIsEditing(cell: Locator, isEditing?: boolean): Promise<void>;
|
|
103
289
|
assertCellIsEditing(row: number, column: number, isEditing?: boolean): Promise<void>;
|
|
290
|
+
/**
|
|
291
|
+
* Assert which rows are rendered, given the visible range of data rows.
|
|
292
|
+
* Checks the rendered row count includes the leading and trailing buffers
|
|
293
|
+
* and that buffered rows beyond the viewport are not in view.
|
|
294
|
+
*
|
|
295
|
+
* @param range the visible range of (0-based) data rows
|
|
296
|
+
* @param renderBufferSize number of rows rendered beyond each end of the viewport
|
|
297
|
+
* @param totalRowCount size of the dataSource
|
|
298
|
+
* @param headerCount number of header rows, default 1
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* test("initial render", async ({ page }) => {
|
|
302
|
+
* const table = new PwTable(page);
|
|
303
|
+
* await table.assertRenderedRows({ from: 0, to: 30 }, 5, 1000);
|
|
304
|
+
* });
|
|
305
|
+
*/
|
|
104
306
|
assertRenderedRows({ from, to }: VuuRange, renderBufferSize: number, totalRowCount: number, headerCount?: number): Promise<void>;
|
|
307
|
+
/**
|
|
308
|
+
* Assert which columns are rendered and which are in the viewport, checked
|
|
309
|
+
* against the first data row. Ranges are inclusive, 1-based aria col indexes.
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* test("column virtualization", async ({ page }) => {
|
|
313
|
+
* const table = new PwTable(page);
|
|
314
|
+
* await table.assertRenderedColumns({
|
|
315
|
+
* rendered: { from: 1, to: 8 },
|
|
316
|
+
* visible: { from: 1, to: 6 },
|
|
317
|
+
* });
|
|
318
|
+
* });
|
|
319
|
+
*/
|
|
105
320
|
assertRenderedColumns({ rendered, visible, }: {
|
|
106
321
|
rendered: VuuRange;
|
|
107
322
|
visible: VuuRange;
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { type Page } from "@playwright/test";
|
|
2
2
|
import { PwComponent, type PwComponentTarget } from "./PwComponent";
|
|
3
|
+
/**
|
|
4
|
+
* A Table column header cell. Usually obtained from PwTable.headerCell.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* test("currency header", async ({ page }) => {
|
|
8
|
+
* const header = new PwTable(page).headerCell("currency");
|
|
9
|
+
* await expect(header.locator).toHaveText("Currency");
|
|
10
|
+
* });
|
|
11
|
+
*/
|
|
3
12
|
export declare class PwTableHeaderCell extends PwComponent {
|
|
4
13
|
protected readonly defaultCssSelector = ".vuuTableHeaderCell";
|
|
5
14
|
/**
|
|
@@ -7,6 +16,26 @@ export declare class PwTableHeaderCell extends PwComponent {
|
|
|
7
16
|
* @param target css selector or Locator for the header cell
|
|
8
17
|
*/
|
|
9
18
|
constructor(page: Page, target: PwComponentTarget);
|
|
19
|
+
/**
|
|
20
|
+
* Does the header cell have the selected class. Does not wait or retry,
|
|
21
|
+
* use assertSelected after an action that selects the column.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* test("no column initially selected", async ({ page }) => {
|
|
25
|
+
* const table = new PwTable(page);
|
|
26
|
+
* expect(await table.headerCell("currency").isSelected).toBe(false);
|
|
27
|
+
* });
|
|
28
|
+
*/
|
|
10
29
|
get isSelected(): Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Assert the header cell is selected, retrying until the expect timeout.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* test("column is selected", async ({ page }) => {
|
|
35
|
+
* const table = new PwTable(page);
|
|
36
|
+
* // ...select the currency column
|
|
37
|
+
* await table.headerCell("currency").assertSelected();
|
|
38
|
+
* });
|
|
39
|
+
*/
|
|
11
40
|
assertSelected(): Promise<void>;
|
|
12
41
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { PlaywrightPageObject } from "./PlaywrightPageObject";
|
|
2
|
+
export { PwChart } from "./PwChart";
|
|
3
|
+
export { type ChartDataPointInfo, PwChartDataPoint } from "./PwChartDataPoint";
|
|
2
4
|
export { PwComponent, type PwComponentTarget } from "./PwComponent";
|
|
3
5
|
export { PwContextMenu } from "./PwContextMenu";
|
|
4
6
|
export { PwMenuItem } from "./PwMenuItem";
|