@vuu-ui/vuu-playwright-helpers 4.0.0-beta.7 → 4.0.1

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.
@@ -4,18 +4,167 @@ 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
+ * How far to scroll: 'start' or 'end' scroll to that end of the axis, a number
9
+ * scrolls by that many pixels in the direction of the method.
10
+ */
11
+ export type ScrollDistance = "start" | "end" | number;
12
+ /**
13
+ * As ScrollDistance, plus 'page' to scroll vertically by the height of the
14
+ * viewport (as the PageUp and PageDown keys do).
15
+ */
16
+ export type VerticalScrollDistance = ScrollDistance | "page";
17
+ export interface ScrollPosition {
18
+ scrollLeft: number;
19
+ scrollTop: number;
20
+ }
21
+ /**
22
+ * A Vuu Table. Rows are located by 1-based aria row index, which includes
23
+ * header rows (with one header, the first data row is 2).
24
+ *
25
+ * @example
26
+ * test("table renders instruments", async ({ mount, page }) => {
27
+ * await mount("Table/ContextMenu/LocalContextMenu");
28
+ * const table = new PwTable(page);
29
+ * await table.assertVisible();
30
+ * await expect(await table.cell("ric", 2)).toHaveText("AAOO.L");
31
+ * });
32
+ */
7
33
  export declare class PwTable extends PwComponent {
8
34
  protected readonly defaultCssSelector = ".vuuTable";
35
+ /**
36
+ * Locator for the scrollable content container (the rendered rows).
37
+ *
38
+ * @example
39
+ * test("rows are zebra striped", async ({ page }) => {
40
+ * const table = new PwTable(page);
41
+ * await expect(table.contentContainer).toContainClass("vuuTable-zebra");
42
+ * });
43
+ */
9
44
  get contentContainer(): Locator;
45
+ /**
46
+ * Locator for the scrollbar container, the element that scrolls.
47
+ *
48
+ * @example
49
+ * test("scrolls vertically", async ({ page }) => {
50
+ * const table = new PwTable(page);
51
+ * await table.scrollbarContainer.evaluate((el) => el.scrollTo(0, 1000));
52
+ * await expect(table.row(2)).not.toBeInViewport();
53
+ * });
54
+ */
10
55
  get scrollbarContainer(): Locator;
11
56
  /**
12
- * row Locator by aria-index
57
+ * The scroll position of the scrollbar container.
58
+ *
59
+ * @example
60
+ * test("scrolled to top", async ({ page }) => {
61
+ * const table = new PwTable(page);
62
+ * expect((await table.scrollPosition()).scrollTop).toBe(0);
63
+ * });
64
+ */
65
+ scrollPosition(): Promise<ScrollPosition>;
66
+ /**
67
+ * Scroll up, by a number of pixels, a page (the height of the viewport), or
68
+ * to the 'start' (top) or 'end' (bottom). Scrolls the scrollbar container,
69
+ * as a user dragging the scrollbar would, and resolves once the content
70
+ * container has been scrolled to match.
71
+ *
72
+ * @example
73
+ * test("scroll back to top", async ({ page }) => {
74
+ * const table = new PwTable(page);
75
+ * await table.scrollDown("page");
76
+ * await table.scrollUp("start");
77
+ * await expect(table.row(2)).toBeInViewport();
78
+ * });
79
+ */
80
+ scrollUp(distance: VerticalScrollDistance): Promise<void>;
81
+ /**
82
+ * Scroll down, by a number of pixels, a page (the height of the viewport),
83
+ * or to the 'start' (top) or 'end' (bottom). Scrolls the scrollbar
84
+ * container, as a user dragging the scrollbar would, and resolves once the
85
+ * content container has been scrolled to match.
86
+ *
87
+ * @example
88
+ * test("scroll to last row", async ({ page }) => {
89
+ * const table = new PwTable(page);
90
+ * await table.scrollDown("end");
91
+ * await expect(table.row(1001)).toBeInViewport();
92
+ * });
93
+ */
94
+ scrollDown(distance: VerticalScrollDistance): Promise<void>;
95
+ /**
96
+ * Scroll left, by a number of pixels, or to the 'start' (leftmost) or 'end'
97
+ * (rightmost). Scrolls the scrollbar container, as a user dragging the
98
+ * scrollbar would, and resolves once the content container has been
99
+ * scrolled to match.
100
+ *
101
+ * @example
102
+ * test("scroll back to first column", async ({ page }) => {
103
+ * const table = new PwTable(page);
104
+ * await table.scrollRight("end");
105
+ * await table.scrollLeft("start");
106
+ * await expect(table.locateCell(2, 1)).toBeInViewport();
107
+ * });
108
+ */
109
+ scrollLeft(distance: ScrollDistance): Promise<void>;
110
+ /**
111
+ * Scroll right, by a number of pixels, or to the 'start' (leftmost) or
112
+ * 'end' (rightmost). Scrolls the scrollbar container, as a user dragging the
113
+ * scrollbar would, and resolves once the content container has been
114
+ * scrolled to match.
115
+ *
116
+ * @example
117
+ * test("scroll right by 200px", async ({ page }) => {
118
+ * const table = new PwTable(page);
119
+ * await table.scrollRight(200);
120
+ * expect((await table.scrollPosition()).scrollLeft).toBe(200);
121
+ * });
122
+ */
123
+ scrollRight(distance: ScrollDistance): Promise<void>;
124
+ private scroll;
125
+ /**
126
+ * The table keeps the content container at the same proportional scroll
127
+ * position as the scrollbar container.
128
+ */
129
+ private isContentScrollSynced;
130
+ /**
131
+ * Row Locator by 1-based aria row index, including headers.
132
+ *
133
+ * @example
134
+ * test("first data row is selected", async ({ page }) => {
135
+ * const table = new PwTable(page);
136
+ * await (await table.cell("ric", 2)).click();
137
+ * await expect(table.row(2)).toHaveAttribute("aria-selected", "true");
138
+ * });
13
139
  */
14
140
  row(row: number): Locator;
15
141
  /**
16
142
  * Locate cell by column name and 1-based aria row index, including headers.
143
+ * Preferred over locateCell for new usage.
144
+ *
145
+ * @example
146
+ * test("first ric", async ({ page }) => {
147
+ * const table = new PwTable(page);
148
+ * await expect(await table.cell("ric", 2)).toHaveText("AAOO.L");
149
+ * });
17
150
  */
18
151
  cell(colName: string, ariaRowIndex: number): Promise<Locator>;
152
+ /**
153
+ * Double click a cell to edit it, fill the editor with value, then commit
154
+ * (Enter) or cancel (Escape) the edit.
155
+ *
156
+ * @example
157
+ * test("edit bbg", async ({ mount, page }) => {
158
+ * await mount("Table/Editing/EditableInstrumentsInlineEdit");
159
+ * await page.getByRole("radio", { name: "Edit" }).click();
160
+ * const table = new PwTable(page);
161
+ * const textbox = (await table.cell("bbg", 2)).getByRole("textbox");
162
+ * await table.editCell("bbg", 2, "X", "cancel");
163
+ * await expect(textbox).not.toHaveValue("X");
164
+ * await table.editCell("bbg", 2, "X");
165
+ * await expect(textbox).toHaveValue("X");
166
+ * });
167
+ */
19
168
  editCell(columnName: string, ariaRowIndex: number, value: string, action?: "commit" | "cancel"): Promise<void>;
20
169
  /**
21
170
  * The labels displayed in the column headers, in rendered order.
@@ -28,6 +177,28 @@ export declare class PwTable extends PwComponent {
28
177
  * });
29
178
  */
30
179
  columnNames(): Promise<string[]>;
180
+ /**
181
+ * The names (not labels) of the columns in the table, from the
182
+ * data-column-name of the header cells, in rendered order.
183
+ *
184
+ * @example
185
+ * test("renders expected columns", async ({ page }) => {
186
+ * const table = new PwTable(page);
187
+ * expect(await table.headerColumnNames()).toEqual(["ccy", "exchange"]);
188
+ * });
189
+ */
190
+ headerColumnNames(): Promise<string[]>;
191
+ /**
192
+ * Assert the names (not labels) of the columns in the table, in order,
193
+ * retrying until the expect timeout.
194
+ *
195
+ * @example
196
+ * test("column added", async ({ page }) => {
197
+ * const table = new PwTable(page);
198
+ * await table.assertColumns(["ccy", "exchange", "lotSize"]);
199
+ * });
200
+ */
201
+ assertColumns(columnNames: string[]): Promise<void>;
31
202
  /**
32
203
  * The total row count, read from aria-rowcount on the element with role="table".
33
204
  * This is the dataSource size, not the number of rendered rows.
@@ -39,6 +210,32 @@ export declare class PwTable extends PwComponent {
39
210
  * });
40
211
  */
41
212
  rowCount(): Promise<number>;
213
+ /**
214
+ * Wait until the table has loaded data, i.e. rowCount is greater than zero.
215
+ *
216
+ * @example
217
+ * test("table loads", async ({ mount, page }) => {
218
+ * await mount("Filters/FilterContainer/FilterContainerWithTable");
219
+ * const table = new PwTable(page);
220
+ * await table.dataLoaded;
221
+ * });
222
+ */
223
+ get dataLoaded(): Promise<void>;
224
+ /**
225
+ * The text content of the data cells in a column, exactly as formatted in
226
+ * the table, in row order. These are the cells in data rows with the same
227
+ * aria-colindex as the column header. The table is virtualised, so only
228
+ * rendered rows are included.
229
+ *
230
+ * @example
231
+ * test("filter on currency", async ({ page }) => {
232
+ * const table = new PwTable(page);
233
+ * await expect
234
+ * .poll(async () => new Set(await table.columnValues("currency")))
235
+ * .toEqual(new Set(["GBP"]));
236
+ * });
237
+ */
238
+ columnValues(columnName: string): Promise<string[]>;
42
239
  /**
43
240
  * Locate the icon with the given data-icon name within a cell.
44
241
  *
@@ -60,10 +257,21 @@ export declare class PwTable extends PwComponent {
60
257
  * });
61
258
  */
62
259
  cellsWithIcon(iconName: string): Locator;
260
+ /**
261
+ * The column header cell for the named column.
262
+ *
263
+ * @example
264
+ * test("currency column not selected", async ({ page }) => {
265
+ * const table = new PwTable(page);
266
+ * expect(await table.headerCell("currency").isSelected).toBe(false);
267
+ * });
268
+ */
63
269
  headerCell(columnName: string): PwTableHeaderCell;
64
270
  /**
65
271
  * Right click a cell to invoke the context menu. Resolves once the menu is
66
- * visible. Rejects if no context menu is shown.
272
+ * visible and has focus, so keyboard navigation can begin immediately.
273
+ * Waits until the test timeout if no context menu is shown, use
274
+ * assertNoContextMenu to test for that.
67
275
  *
68
276
  * @param columnName name of the column
69
277
  * @param ariaRowIndex 1-based aria row index, including headers (with one header, the first data row is 2)
@@ -77,31 +285,174 @@ export declare class PwTable extends PwComponent {
77
285
  */
78
286
  showContextMenu(columnName: string, ariaRowIndex: number): Promise<PwContextMenu>;
79
287
  /**
80
- * Locate cell by cell contents
81
- * @param text
288
+ * Right click a cell and assert that no context menu is shown and the cell
289
+ * is not marked as having an open context menu.
290
+ *
291
+ * A negative assertion passes as soon as the menu is absent, which may be
292
+ * before a menu would have rendered. To avoid false passes we wait for two
293
+ * animation frames after the right click, allowing React to commit and the
294
+ * menu to be positioned, before asserting.
295
+ *
296
+ * @param columnName name of the column
297
+ * @param ariaRowIndex 1-based aria row index, including headers (with one header, the first data row is 2)
298
+ *
299
+ * @example
300
+ * test("no menu when none configured", async ({ page }) => {
301
+ * const table = new PwTable(page);
302
+ * await table.assertNoContextMenu("ccy", 2);
303
+ * });
304
+ */
305
+ assertNoContextMenu(columnName: string, ariaRowIndex: number): Promise<void>;
306
+ /**
307
+ * Locate cell(s) by accessible name (cell contents). May match several cells.
308
+ *
309
+ * @example
310
+ * test("right click ric", async ({ page }) => {
311
+ * const table = new PwTable(page);
312
+ * await table.locateCell("AAOP N").click({ button: "right" });
313
+ * });
82
314
  */
83
315
  locateCell(text: string): Locator;
84
316
  /**
85
- * Locate cell by aria row index and aria col index
86
- * @param text
317
+ * Locate cell by 1-based aria row index and 1-based aria col index.
318
+ *
319
+ * @example
320
+ * test("down arrow from header focuses first cell", async ({ page }) => {
321
+ * const table = new PwTable(page);
322
+ * await table.locateColumnHeader(1).press("ArrowDown");
323
+ * await expect(table.locateCell(2, 1)).toBeFocused();
324
+ * });
87
325
  */
88
326
  locateCell(row: number, column: number): Locator;
89
327
  /**
90
- * Locate cell by aria row index and column name
328
+ * Locate cell by 1-based aria row index and column name. Prefer cell(columnName, ariaRowIndex).
329
+ *
330
+ * @example
331
+ * test("first ric", async ({ page }) => {
332
+ * const table = new PwTable(page);
333
+ * await expect(await table.locateCell(2, "ric")).toHaveText("AAOO.L");
334
+ * });
91
335
  */
92
336
  locateCell(row: number, columnName: string): Promise<Locator>;
337
+ /**
338
+ * Locate a column header cell by 1-based aria col index.
339
+ *
340
+ * @example
341
+ * test("click header focuses it", async ({ page }) => {
342
+ * const table = new PwTable(page);
343
+ * const header = table.locateColumnHeader(1);
344
+ * await header.click();
345
+ * await expect(header).toBeFocused();
346
+ * });
347
+ */
93
348
  locateColumnHeader(column: number): Locator;
349
+ /**
350
+ * When isEditable is true, assert the cell is editable but not currently in
351
+ * edit mode and, if value is passed, that its textbox has that value. When
352
+ * isEditable is false or undefined, nothing is asserted.
353
+ *
354
+ * @example
355
+ * test("first cell is editable", async ({ mount, page }) => {
356
+ * await mount("Table/Editing/EditableInstruments");
357
+ * await page.getByRole("radio", { name: "Edit" }).click();
358
+ * const table = new PwTable(page);
359
+ * await table.assertCellIsEditable(2, 1, true, "AAOO L");
360
+ * });
361
+ */
94
362
  assertCellIsEditable(row: number, column: number, isEditable: boolean | undefined, value?: string): Promise<void>;
95
363
  private getCellText;
364
+ /**
365
+ * Assert the value of a cell input (toHaveValue). Pass a role to target an
366
+ * element within the cell, e.g. "textbox" for an editable cell.
367
+ *
368
+ * @example
369
+ * test("editable cell value", async ({ mount, page }) => {
370
+ * await mount("Table/Editing/EditableInstruments");
371
+ * await page.getByRole("radio", { name: "Edit" }).click();
372
+ * const table = new PwTable(page);
373
+ * await table.assertCellValue(2, 1, "AAOO L", "textbox");
374
+ * await table.assertCellValue(table.locateCell(2, 1), "AAOO L", "textbox");
375
+ * });
376
+ */
96
377
  assertCellValue(cell: Locator, text: string, role?: AriaRole): Promise<void>;
97
378
  assertCellValue(row: number, column: number, text: string, role?: AriaRole): Promise<void>;
379
+ /**
380
+ * Assert the text content of a cell (toHaveText). Pass a role to target an
381
+ * element within the cell, e.g. "combobox" for a dropdown cell.
382
+ *
383
+ * @example
384
+ * test("first cell text", async ({ page }) => {
385
+ * const table = new PwTable(page);
386
+ * await table.assertCellContent(2, 1, "AAOO L");
387
+ * await table.assertCellContent(table.locateCell(2, 1), "AAOO L");
388
+ * });
389
+ */
98
390
  assertCellContent(cell: Locator, text: string, role?: AriaRole): Promise<void>;
99
391
  assertCellContent(row: number, column: number, text: string, role?: AriaRole): Promise<void>;
392
+ /**
393
+ * Assert a cell, or an element within it with the given role, is focused.
394
+ *
395
+ * @example
396
+ * test("click focuses editable cell", async ({ mount, page }) => {
397
+ * await mount("Table/Editing/EditableInstruments");
398
+ * await page.getByRole("radio", { name: "Edit" }).click();
399
+ * const table = new PwTable(page);
400
+ * const cell = table.locateCell(3, 3);
401
+ * await cell.click();
402
+ * await table.assertCellIsFocused(cell, "textbox");
403
+ * await table.assertCellIsFocused(3, 3, "textbox");
404
+ * });
405
+ */
100
406
  assertCellIsFocused(cell: Locator, role?: AriaRole): Promise<void>;
101
407
  assertCellIsFocused(row: number, column: number, role?: AriaRole): Promise<void>;
408
+ /**
409
+ * Assert a cell is (default) or is not in edit mode.
410
+ *
411
+ * @example
412
+ * test("Enter starts editing, Escape stops", async ({ mount, page }) => {
413
+ * await mount("Table/Editing/EditableInstruments");
414
+ * await page.getByRole("radio", { name: "Edit" }).click();
415
+ * const table = new PwTable(page);
416
+ * const cell = table.locateCell(3, 3);
417
+ * await cell.click();
418
+ * await cell.press("Enter");
419
+ * await table.assertCellIsEditing(cell);
420
+ * await cell.press("Escape");
421
+ * await table.assertCellIsEditing(cell, false);
422
+ * });
423
+ */
102
424
  assertCellIsEditing(cell: Locator, isEditing?: boolean): Promise<void>;
103
425
  assertCellIsEditing(row: number, column: number, isEditing?: boolean): Promise<void>;
426
+ /**
427
+ * Assert which rows are rendered, given the visible range of data rows.
428
+ * Checks the rendered row count includes the leading and trailing buffers
429
+ * and that buffered rows beyond the viewport are not in view.
430
+ *
431
+ * @param range the visible range of (0-based) data rows
432
+ * @param renderBufferSize number of rows rendered beyond each end of the viewport
433
+ * @param totalRowCount size of the dataSource
434
+ * @param headerCount number of header rows, default 1
435
+ *
436
+ * @example
437
+ * test("initial render", async ({ page }) => {
438
+ * const table = new PwTable(page);
439
+ * await table.assertRenderedRows({ from: 0, to: 30 }, 5, 1000);
440
+ * });
441
+ */
104
442
  assertRenderedRows({ from, to }: VuuRange, renderBufferSize: number, totalRowCount: number, headerCount?: number): Promise<void>;
443
+ /**
444
+ * Assert which columns are rendered and which are in the viewport, checked
445
+ * against the first data row. Ranges are inclusive, 1-based aria col indexes.
446
+ *
447
+ * @example
448
+ * test("column virtualization", async ({ page }) => {
449
+ * const table = new PwTable(page);
450
+ * await table.assertRenderedColumns({
451
+ * rendered: { from: 1, to: 8 },
452
+ * visible: { from: 1, to: 6 },
453
+ * });
454
+ * });
455
+ */
105
456
  assertRenderedColumns({ rendered, visible, }: {
106
457
  rendered: VuuRange;
107
458
  visible: VuuRange;
@@ -0,0 +1,40 @@
1
+ import type { Locator } from "@playwright/test";
2
+ import { PwColumnPicker } from "./PwColumnPicker";
3
+ import { PwComponent } from "./PwComponent";
4
+ /**
5
+ * A TableFooter, usually rendered below a Table. Holds DataSourceStats and
6
+ * a tray of actions, e.g. the ColumnPickerAction.
7
+ *
8
+ * @example
9
+ * test("footer shows column picker", async ({ page }) => {
10
+ * const footer = new PwTableFooter(page);
11
+ * const picker = await footer.showColumnPicker();
12
+ * await picker.assertVisible();
13
+ * });
14
+ */
15
+ export declare class PwTableFooter extends PwComponent {
16
+ protected readonly defaultCssSelector = ".vuuTableFooter";
17
+ /**
18
+ * An action button in the footer, by accessible name.
19
+ *
20
+ * @example
21
+ * test("column picker action", async ({ page }) => {
22
+ * const footer = new PwTableFooter(page);
23
+ * await expect(footer.action("Show column picker")).toBeVisible();
24
+ * });
25
+ */
26
+ action(name: string): Locator;
27
+ /**
28
+ * Click the ColumnPickerAction. Resolves with the ColumnPicker once it is
29
+ * visible.
30
+ *
31
+ * @param name accessible name of the action, if not the default
32
+ *
33
+ * @example
34
+ * test("open column picker", async ({ page }) => {
35
+ * const picker = await new PwTableFooter(page).showColumnPicker();
36
+ * expect(await picker.selectedColumns()).toContain("bbg");
37
+ * });
38
+ */
39
+ showColumnPicker(name?: string): Promise<PwColumnPicker>;
40
+ }
@@ -1,5 +1,15 @@
1
1
  import { type Page } from "@playwright/test";
2
2
  import { PwComponent, type PwComponentTarget } from "./PwComponent";
3
+ import { PwContextMenu } from "./PwContextMenu";
4
+ /**
5
+ * A Table column header cell. Usually obtained from PwTable.headerCell.
6
+ *
7
+ * @example
8
+ * test("currency header", async ({ page }) => {
9
+ * const header = new PwTable(page).headerCell("currency");
10
+ * await expect(header.locator).toHaveText("Currency");
11
+ * });
12
+ */
3
13
  export declare class PwTableHeaderCell extends PwComponent {
4
14
  protected readonly defaultCssSelector = ".vuuTableHeaderCell";
5
15
  /**
@@ -7,6 +17,39 @@ export declare class PwTableHeaderCell extends PwComponent {
7
17
  * @param target css selector or Locator for the header cell
8
18
  */
9
19
  constructor(page: Page, target: PwComponentTarget);
20
+ /**
21
+ * Does the header cell have the selected class. Does not wait or retry,
22
+ * use assertSelected after an action that selects the column.
23
+ *
24
+ * @example
25
+ * test("no column initially selected", async ({ page }) => {
26
+ * const table = new PwTable(page);
27
+ * expect(await table.headerCell("currency").isSelected).toBe(false);
28
+ * });
29
+ */
10
30
  get isSelected(): Promise<boolean>;
31
+ /**
32
+ * Assert the header cell is selected, retrying until the expect timeout.
33
+ *
34
+ * @example
35
+ * test("column is selected", async ({ page }) => {
36
+ * const table = new PwTable(page);
37
+ * // ...select the currency column
38
+ * await table.headerCell("currency").assertSelected();
39
+ * });
40
+ */
11
41
  assertSelected(): Promise<void>;
42
+ /**
43
+ * Click the column menu button in the header cell. Resolves with the column
44
+ * menu once it is visible.
45
+ *
46
+ * @example
47
+ * test("remove column from column menu", async ({ page }) => {
48
+ * const table = new PwTable(page);
49
+ * const menu = await table.headerCell("exchange").showColumnMenu();
50
+ * await menu.menuItem("Remove exchange column").click();
51
+ * await table.assertColumns(["bbg", "currency"]);
52
+ * });
53
+ */
54
+ showColumnMenu(): Promise<PwContextMenu>;
12
55
  }
package/types/index.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  export { PlaywrightPageObject } from "./PlaywrightPageObject";
2
+ export { PwChart } from "./PwChart";
3
+ export { type ChartDataPointInfo, PwChartDataPoint } from "./PwChartDataPoint";
4
+ export { PwColumnPicker } from "./PwColumnPicker";
2
5
  export { PwComponent, type PwComponentTarget } from "./PwComponent";
3
6
  export { PwContextMenu } from "./PwContextMenu";
4
7
  export { PwMenuItem } from "./PwMenuItem";
5
- export { PwTable } from "./PwTable";
8
+ export { PwTable, type ScrollDistance, type ScrollPosition, type VerticalScrollDistance, } from "./PwTable";
9
+ export { PwTableFooter } from "./PwTableFooter";
6
10
  export { PwTableHeaderCell } from "./PwTableHeaderCell";