@gtkx/testing 1.0.0-rc.3 → 1.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 +8 -9
- package/dist/accessible-native.d.ts +16 -0
- package/dist/accessible-native.d.ts.map +1 -0
- package/dist/accessible-native.js +206 -0
- package/dist/accessible-native.js.map +1 -0
- package/dist/act.d.ts +2 -0
- package/dist/act.d.ts.map +1 -1
- package/dist/act.js.map +1 -1
- package/dist/build-queries.d.ts +27 -10
- package/dist/build-queries.d.ts.map +1 -1
- package/dist/build-queries.js +39 -30
- package/dist/build-queries.js.map +1 -1
- package/dist/cleanup-registry.d.ts +1 -1
- package/dist/cleanup-registry.d.ts.map +1 -1
- package/dist/cleanup-registry.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +10 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +12 -3
- package/dist/errors.js.map +1 -1
- package/dist/fire-event.d.ts +35 -6
- package/dist/fire-event.d.ts.map +1 -1
- package/dist/fire-event.js +23 -7
- package/dist/fire-event.js.map +1 -1
- package/dist/frame-sync.d.ts +2 -1
- package/dist/frame-sync.d.ts.map +1 -1
- package/dist/frame-sync.js +15 -5
- package/dist/frame-sync.js.map +1 -1
- package/dist/harness-window.d.ts +4 -0
- package/dist/harness-window.d.ts.map +1 -0
- package/dist/harness-window.js +6 -0
- package/dist/harness-window.js.map +1 -0
- package/dist/index.d.ts +14 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +4 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +4 -0
- package/dist/internal.js.map +1 -0
- package/dist/matchers.d.ts +125 -39
- package/dist/matchers.d.ts.map +1 -1
- package/dist/matchers.js +156 -18
- package/dist/matchers.js.map +1 -1
- package/dist/pretty-format.d.ts +2 -0
- package/dist/pretty-format.d.ts.map +1 -0
- package/dist/pretty-format.js +2 -0
- package/dist/pretty-format.js.map +1 -0
- package/dist/pretty-widget.d.ts +7 -3
- package/dist/pretty-widget.d.ts.map +1 -1
- package/dist/pretty-widget.js +74 -57
- package/dist/pretty-widget.js.map +1 -1
- package/dist/production.d.ts +8 -0
- package/dist/production.d.ts.map +1 -0
- package/dist/production.js +34 -0
- package/dist/production.js.map +1 -0
- package/dist/queries.d.ts +99 -56
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +123 -83
- package/dist/queries.js.map +1 -1
- package/dist/query-helpers.d.ts +44 -0
- package/dist/query-helpers.d.ts.map +1 -0
- package/dist/query-helpers.js +57 -0
- package/dist/query-helpers.js.map +1 -0
- package/dist/render-children.d.ts +9 -0
- package/dist/render-children.d.ts.map +1 -0
- package/dist/render-children.js +11 -0
- package/dist/render-children.js.map +1 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +13 -11
- package/dist/render.js.map +1 -1
- package/dist/role-helpers.d.ts +12 -3
- package/dist/role-helpers.d.ts.map +1 -1
- package/dist/role-helpers.js +13 -4
- package/dist/role-helpers.js.map +1 -1
- package/dist/role-naming.d.ts +5 -0
- package/dist/role-naming.d.ts.map +1 -0
- package/dist/role-naming.js +96 -0
- package/dist/role-naming.js.map +1 -0
- package/dist/screenshot.d.ts +12 -20
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +183 -91
- package/dist/screenshot.js.map +1 -1
- package/dist/suggestions.d.ts +3 -0
- package/dist/suggestions.d.ts.map +1 -1
- package/dist/suggestions.js.map +1 -1
- package/dist/traversal.d.ts +9 -1
- package/dist/traversal.d.ts.map +1 -1
- package/dist/traversal.js +26 -6
- package/dist/traversal.js.map +1 -1
- package/dist/types.d.ts +67 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/user-event/adjustment.d.ts +16 -0
- package/dist/user-event/adjustment.d.ts.map +1 -1
- package/dist/user-event/adjustment.js +34 -2
- package/dist/user-event/adjustment.js.map +1 -1
- package/dist/user-event/click.d.ts +45 -3
- package/dist/user-event/click.d.ts.map +1 -1
- package/dist/user-event/click.js +177 -39
- package/dist/user-event/click.js.map +1 -1
- package/dist/user-event/column-header-click.d.ts +4 -0
- package/dist/user-event/column-header-click.d.ts.map +1 -0
- package/dist/user-event/column-header-click.js +65 -0
- package/dist/user-event/column-header-click.js.map +1 -0
- package/dist/user-event/controller.d.ts +29 -0
- package/dist/user-event/controller.d.ts.map +1 -1
- package/dist/user-event/controller.js +28 -0
- package/dist/user-event/controller.js.map +1 -1
- package/dist/user-event/event-wrapper.d.ts.map +1 -1
- package/dist/user-event/event-wrapper.js +2 -1
- package/dist/user-event/event-wrapper.js.map +1 -1
- package/dist/user-event/gesture.d.ts +51 -0
- package/dist/user-event/gesture.d.ts.map +1 -1
- package/dist/user-event/gesture.js +47 -4
- package/dist/user-event/gesture.js.map +1 -1
- package/dist/user-event/index.d.ts +70 -2
- package/dist/user-event/index.d.ts.map +1 -1
- package/dist/user-event/index.js +43 -26
- package/dist/user-event/index.js.map +1 -1
- package/dist/user-event/indexed-children.d.ts +10 -0
- package/dist/user-event/indexed-children.d.ts.map +1 -0
- package/dist/user-event/indexed-children.js +50 -0
- package/dist/user-event/indexed-children.js.map +1 -0
- package/dist/user-event/keyboard.d.ts +4 -2
- package/dist/user-event/keyboard.d.ts.map +1 -1
- package/dist/user-event/keyboard.js +46 -16
- package/dist/user-event/keyboard.js.map +1 -1
- package/dist/user-event/list-row-click.d.ts +4 -0
- package/dist/user-event/list-row-click.d.ts.map +1 -0
- package/dist/user-event/list-row-click.js +39 -0
- package/dist/user-event/list-row-click.js.map +1 -0
- package/dist/user-event/native-click.d.ts +6 -0
- package/dist/user-event/native-click.d.ts.map +1 -0
- package/dist/user-event/native-click.js +32 -0
- package/dist/user-event/native-click.js.map +1 -0
- package/dist/user-event/pointer.d.ts +1 -1
- package/dist/user-event/pointer.d.ts.map +1 -1
- package/dist/user-event/pointer.js +37 -13
- package/dist/user-event/pointer.js.map +1 -1
- package/dist/user-event/selection.d.ts +18 -0
- package/dist/user-event/selection.d.ts.map +1 -1
- package/dist/user-event/selection.js +56 -30
- package/dist/user-event/selection.js.map +1 -1
- package/dist/user-event/state.d.ts +1 -1
- package/dist/user-event/state.d.ts.map +1 -1
- package/dist/user-event/state.js +1 -1
- package/dist/user-event/state.js.map +1 -1
- package/dist/user-event/text.d.ts +34 -3
- package/dist/user-event/text.d.ts.map +1 -1
- package/dist/user-event/text.js +94 -17
- package/dist/user-event/text.js.map +1 -1
- package/dist/wait-for.d.ts +2 -0
- package/dist/wait-for.d.ts.map +1 -1
- package/dist/wait-for.js.map +1 -1
- package/dist/widget-accessible-properties.d.ts +14 -10
- package/dist/widget-accessible-properties.d.ts.map +1 -1
- package/dist/widget-accessible-properties.js +135 -173
- package/dist/widget-accessible-properties.js.map +1 -1
- package/dist/widget-getters.d.ts +8 -0
- package/dist/widget-getters.d.ts.map +1 -0
- package/dist/widget-getters.js +19 -0
- package/dist/widget-getters.js.map +1 -0
- package/dist/within.d.ts +3 -2
- package/dist/within.d.ts.map +1 -1
- package/dist/within.js +3 -2
- package/dist/within.js.map +1 -1
- package/package.json +13 -8
- package/src/accessible-native.ts +333 -0
- package/src/act.ts +2 -0
- package/src/build-queries.ts +66 -48
- package/src/cleanup-registry.ts +1 -1
- package/src/config.ts +4 -3
- package/src/errors.ts +14 -3
- package/src/fire-event.ts +61 -8
- package/src/frame-sync.ts +21 -5
- package/src/harness-window.ts +9 -0
- package/src/index.ts +18 -11
- package/src/internal.ts +11 -0
- package/src/matchers.ts +466 -64
- package/src/pretty-format.ts +1 -0
- package/src/pretty-widget.ts +100 -72
- package/src/production.ts +52 -0
- package/src/queries.ts +128 -89
- package/src/query-helpers.ts +97 -0
- package/src/render-children.ts +25 -0
- package/src/render.tsx +18 -13
- package/src/role-helpers.ts +14 -4
- package/src/role-naming.ts +99 -0
- package/src/screenshot.ts +272 -103
- package/src/suggestions.ts +3 -0
- package/src/traversal.ts +49 -6
- package/src/types.ts +69 -19
- package/src/user-event/adjustment.ts +42 -2
- package/src/user-event/click.ts +248 -46
- package/src/user-event/column-header-click.ts +90 -0
- package/src/user-event/controller.ts +29 -0
- package/src/user-event/event-wrapper.ts +3 -1
- package/src/user-event/gesture.ts +57 -6
- package/src/user-event/index.ts +119 -29
- package/src/user-event/indexed-children.ts +75 -0
- package/src/user-event/keyboard.ts +70 -20
- package/src/user-event/list-row-click.ts +57 -0
- package/src/user-event/native-click.ts +46 -0
- package/src/user-event/pointer.ts +52 -13
- package/src/user-event/selection.ts +74 -33
- package/src/user-event/state.ts +2 -2
- package/src/user-event/text.ts +121 -23
- package/src/wait-for.ts +2 -0
- package/src/widget-accessible-properties.ts +183 -214
- package/src/widget-getters.ts +32 -0
- package/src/within.ts +3 -2
package/src/role-helpers.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
2
2
|
import { sortStringsBy } from "@gtkx/utils";
|
|
3
3
|
import { type Container, traverse } from "./traversal.js";
|
|
4
|
-
import { getWidgetAccessibleName } from "./widget-accessible-properties.js";
|
|
4
|
+
import { getWidgetAccessibleName, getWidgetLevel } from "./widget-accessible-properties.js";
|
|
5
5
|
|
|
6
6
|
const ROLE_NAMES_BY_VALUE = enumNamesByValue(Gtk.AccessibleRole);
|
|
7
7
|
|
|
@@ -47,10 +47,11 @@ const formatRoleList = (roles: Iterable<Gtk.AccessibleRole>): string => {
|
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
* Groups every widget in a container's tree by its accessible role name.
|
|
50
|
+
* Groups every mapped widget in a container's tree by its accessible role name. Widgets that are not
|
|
51
|
+
* mapped are left out.
|
|
51
52
|
*
|
|
52
53
|
* @param container The scope to traverse.
|
|
53
|
-
* @returns A map from role name to the widgets that have that role.
|
|
54
|
+
* @returns A map from role name to the mapped widgets that have that role.
|
|
54
55
|
*/
|
|
55
56
|
const getRoles = (container: Container): Map<string, Gtk.Widget[]> => {
|
|
56
57
|
const roles: Map<string, Gtk.Widget[]> = new Map();
|
|
@@ -80,6 +81,7 @@ const formatWidgetPreview = (widget: Gtk.Widget, name: string | null): string =>
|
|
|
80
81
|
/**
|
|
81
82
|
* Formats the accessible roles in a container's tree as a readable string,
|
|
82
83
|
* listing each role together with its widgets and their accessible names.
|
|
84
|
+
* Widgets that are not mapped are left out.
|
|
83
85
|
*
|
|
84
86
|
* @param container The scope to inspect.
|
|
85
87
|
*/
|
|
@@ -116,4 +118,12 @@ const logRoles = (container: Container): void => {
|
|
|
116
118
|
console.log(prettyRoles(container));
|
|
117
119
|
};
|
|
118
120
|
|
|
119
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Returns the heading or hierarchy level GTK reports for a widget, or undefined when it declares
|
|
123
|
+
* none.
|
|
124
|
+
*
|
|
125
|
+
* @param widget The widget to read the level from.
|
|
126
|
+
*/
|
|
127
|
+
const computeHeadingLevel = (widget: Gtk.Widget): number | undefined => getWidgetLevel(widget) ?? undefined;
|
|
128
|
+
|
|
129
|
+
export { computeHeadingLevel, formatRole, formatRoleList, getRoles, prettyRoles, logRoles };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
|
|
3
|
+
const FROM_AUTHOR = 1;
|
|
4
|
+
const FROM_CONTENT = 2;
|
|
5
|
+
const PROHIBITED = 4;
|
|
6
|
+
|
|
7
|
+
const NAMING: Partial<Record<Gtk.AccessibleRole, number>> = {
|
|
8
|
+
[Gtk.AccessibleRole.ALERT]: FROM_AUTHOR,
|
|
9
|
+
[Gtk.AccessibleRole.ALERT_DIALOG]: FROM_AUTHOR,
|
|
10
|
+
[Gtk.AccessibleRole.APPLICATION]: FROM_AUTHOR,
|
|
11
|
+
[Gtk.AccessibleRole.ARTICLE]: FROM_AUTHOR,
|
|
12
|
+
[Gtk.AccessibleRole.BANNER]: FROM_AUTHOR,
|
|
13
|
+
[Gtk.AccessibleRole.BLOCK_QUOTE]: FROM_AUTHOR,
|
|
14
|
+
[Gtk.AccessibleRole.BUTTON]: FROM_AUTHOR | FROM_CONTENT,
|
|
15
|
+
[Gtk.AccessibleRole.CAPTION]: PROHIBITED,
|
|
16
|
+
[Gtk.AccessibleRole.CELL]: FROM_AUTHOR | FROM_CONTENT,
|
|
17
|
+
[Gtk.AccessibleRole.CHECKBOX]: FROM_AUTHOR | FROM_CONTENT,
|
|
18
|
+
[Gtk.AccessibleRole.COLUMN_HEADER]: FROM_AUTHOR | FROM_CONTENT,
|
|
19
|
+
[Gtk.AccessibleRole.COMBO_BOX]: FROM_AUTHOR,
|
|
20
|
+
[Gtk.AccessibleRole.COMMAND]: PROHIBITED,
|
|
21
|
+
[Gtk.AccessibleRole.COMMENT]: FROM_AUTHOR | FROM_CONTENT,
|
|
22
|
+
[Gtk.AccessibleRole.COMPOSITE]: PROHIBITED,
|
|
23
|
+
[Gtk.AccessibleRole.DIALOG]: FROM_AUTHOR,
|
|
24
|
+
[Gtk.AccessibleRole.DOCUMENT]: FROM_AUTHOR,
|
|
25
|
+
[Gtk.AccessibleRole.FEED]: FROM_AUTHOR,
|
|
26
|
+
[Gtk.AccessibleRole.FORM]: FROM_AUTHOR,
|
|
27
|
+
[Gtk.AccessibleRole.GENERIC]: PROHIBITED,
|
|
28
|
+
[Gtk.AccessibleRole.GRID]: FROM_AUTHOR,
|
|
29
|
+
[Gtk.AccessibleRole.GRID_CELL]: FROM_AUTHOR | FROM_CONTENT,
|
|
30
|
+
[Gtk.AccessibleRole.GROUP]: FROM_AUTHOR,
|
|
31
|
+
[Gtk.AccessibleRole.HEADING]: FROM_AUTHOR | FROM_CONTENT,
|
|
32
|
+
[Gtk.AccessibleRole.IMG]: FROM_AUTHOR,
|
|
33
|
+
[Gtk.AccessibleRole.INPUT]: PROHIBITED,
|
|
34
|
+
[Gtk.AccessibleRole.LABEL]: FROM_AUTHOR | FROM_CONTENT,
|
|
35
|
+
[Gtk.AccessibleRole.LANDMARK]: PROHIBITED,
|
|
36
|
+
[Gtk.AccessibleRole.LEGEND]: PROHIBITED,
|
|
37
|
+
[Gtk.AccessibleRole.LINK]: FROM_AUTHOR | FROM_CONTENT,
|
|
38
|
+
[Gtk.AccessibleRole.LIST]: FROM_AUTHOR,
|
|
39
|
+
[Gtk.AccessibleRole.LIST_BOX]: FROM_AUTHOR,
|
|
40
|
+
[Gtk.AccessibleRole.LIST_ITEM]: FROM_AUTHOR,
|
|
41
|
+
[Gtk.AccessibleRole.LOG]: FROM_AUTHOR,
|
|
42
|
+
[Gtk.AccessibleRole.MAIN]: FROM_AUTHOR,
|
|
43
|
+
[Gtk.AccessibleRole.MARQUEE]: FROM_AUTHOR,
|
|
44
|
+
[Gtk.AccessibleRole.MATH]: FROM_AUTHOR,
|
|
45
|
+
[Gtk.AccessibleRole.METER]: FROM_AUTHOR,
|
|
46
|
+
[Gtk.AccessibleRole.MENU]: FROM_AUTHOR,
|
|
47
|
+
[Gtk.AccessibleRole.MENU_BAR]: FROM_AUTHOR,
|
|
48
|
+
[Gtk.AccessibleRole.MENU_ITEM]: FROM_AUTHOR | FROM_CONTENT,
|
|
49
|
+
[Gtk.AccessibleRole.MENU_ITEM_CHECKBOX]: FROM_AUTHOR | FROM_CONTENT,
|
|
50
|
+
[Gtk.AccessibleRole.MENU_ITEM_RADIO]: FROM_AUTHOR | FROM_CONTENT,
|
|
51
|
+
[Gtk.AccessibleRole.NAVIGATION]: FROM_AUTHOR,
|
|
52
|
+
[Gtk.AccessibleRole.NONE]: PROHIBITED,
|
|
53
|
+
[Gtk.AccessibleRole.NOTE]: FROM_AUTHOR,
|
|
54
|
+
[Gtk.AccessibleRole.OPTION]: FROM_AUTHOR | FROM_CONTENT,
|
|
55
|
+
[Gtk.AccessibleRole.PARAGRAPH]: PROHIBITED,
|
|
56
|
+
[Gtk.AccessibleRole.PRESENTATION]: PROHIBITED,
|
|
57
|
+
[Gtk.AccessibleRole.PROGRESS_BAR]: FROM_AUTHOR,
|
|
58
|
+
[Gtk.AccessibleRole.RADIO]: FROM_AUTHOR | FROM_CONTENT,
|
|
59
|
+
[Gtk.AccessibleRole.RADIO_GROUP]: FROM_AUTHOR,
|
|
60
|
+
[Gtk.AccessibleRole.RANGE]: PROHIBITED,
|
|
61
|
+
[Gtk.AccessibleRole.REGION]: FROM_AUTHOR,
|
|
62
|
+
[Gtk.AccessibleRole.ROW]: FROM_AUTHOR | FROM_CONTENT,
|
|
63
|
+
[Gtk.AccessibleRole.ROW_GROUP]: FROM_AUTHOR,
|
|
64
|
+
[Gtk.AccessibleRole.ROW_HEADER]: FROM_AUTHOR | FROM_CONTENT,
|
|
65
|
+
[Gtk.AccessibleRole.SCROLLBAR]: FROM_AUTHOR,
|
|
66
|
+
[Gtk.AccessibleRole.SEARCH]: FROM_AUTHOR,
|
|
67
|
+
[Gtk.AccessibleRole.SEARCH_BOX]: FROM_AUTHOR,
|
|
68
|
+
[Gtk.AccessibleRole.SECTION]: PROHIBITED,
|
|
69
|
+
[Gtk.AccessibleRole.SECTION_HEAD]: PROHIBITED,
|
|
70
|
+
[Gtk.AccessibleRole.SELECT]: PROHIBITED,
|
|
71
|
+
[Gtk.AccessibleRole.SEPARATOR]: FROM_AUTHOR,
|
|
72
|
+
[Gtk.AccessibleRole.SLIDER]: FROM_AUTHOR,
|
|
73
|
+
[Gtk.AccessibleRole.SPIN_BUTTON]: FROM_AUTHOR,
|
|
74
|
+
[Gtk.AccessibleRole.STATUS]: FROM_AUTHOR,
|
|
75
|
+
[Gtk.AccessibleRole.STRUCTURE]: PROHIBITED,
|
|
76
|
+
[Gtk.AccessibleRole.SWITCH]: FROM_AUTHOR | FROM_CONTENT,
|
|
77
|
+
[Gtk.AccessibleRole.TAB]: FROM_AUTHOR | FROM_CONTENT,
|
|
78
|
+
[Gtk.AccessibleRole.TABLE]: FROM_AUTHOR,
|
|
79
|
+
[Gtk.AccessibleRole.TAB_LIST]: FROM_AUTHOR,
|
|
80
|
+
[Gtk.AccessibleRole.TAB_PANEL]: FROM_AUTHOR,
|
|
81
|
+
[Gtk.AccessibleRole.TERMINAL]: FROM_AUTHOR,
|
|
82
|
+
[Gtk.AccessibleRole.TEXT_BOX]: FROM_AUTHOR,
|
|
83
|
+
[Gtk.AccessibleRole.TIME]: FROM_AUTHOR,
|
|
84
|
+
[Gtk.AccessibleRole.TIMER]: FROM_AUTHOR,
|
|
85
|
+
[Gtk.AccessibleRole.TOGGLE_BUTTON]: FROM_AUTHOR,
|
|
86
|
+
[Gtk.AccessibleRole.TOOLBAR]: FROM_AUTHOR,
|
|
87
|
+
[Gtk.AccessibleRole.TOOLTIP]: FROM_AUTHOR | FROM_CONTENT,
|
|
88
|
+
[Gtk.AccessibleRole.TREE]: FROM_AUTHOR,
|
|
89
|
+
[Gtk.AccessibleRole.TREE_GRID]: FROM_AUTHOR,
|
|
90
|
+
[Gtk.AccessibleRole.TREE_ITEM]: FROM_AUTHOR | FROM_CONTENT,
|
|
91
|
+
[Gtk.AccessibleRole.WIDGET]: FROM_AUTHOR | FROM_CONTENT,
|
|
92
|
+
[Gtk.AccessibleRole.WINDOW]: FROM_AUTHOR,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const namingFor = (role: Gtk.AccessibleRole): number => NAMING[role] ?? 0;
|
|
96
|
+
const isNameProhibited = (role: Gtk.AccessibleRole): boolean => (namingFor(role) & PROHIBITED) !== 0;
|
|
97
|
+
const isNameFromAuthor = (role: Gtk.AccessibleRole): boolean => (namingFor(role) & FROM_AUTHOR) !== 0;
|
|
98
|
+
|
|
99
|
+
export { isNameFromAuthor, isNameProhibited };
|
package/src/screenshot.ts
CHANGED
|
@@ -1,12 +1,49 @@
|
|
|
1
|
+
import * as Gdk from "@gtkx/gi/gdk";
|
|
2
|
+
import * as Graphene from "@gtkx/gi/graphene";
|
|
1
3
|
import * as Gsk from "@gtkx/gi/gsk";
|
|
2
4
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { dirname } from "node:path";
|
|
7
|
+
import type { ScreenshotOptions, ScreenshotResult } from "./types.js";
|
|
8
|
+
import { getConfig } from "./config.js";
|
|
9
|
+
import { descendants } from "./traversal.js";
|
|
7
10
|
import { waitFor } from "./wait-for.js";
|
|
8
11
|
|
|
12
|
+
type FrameProbe = {
|
|
13
|
+
counter: bigint | null;
|
|
14
|
+
startedAt: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type CaptureFailure = "no-size" | "no-content";
|
|
18
|
+
|
|
19
|
+
type CaptureState = {
|
|
20
|
+
probe: FrameProbe;
|
|
21
|
+
failure: CaptureFailure | null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type CaptureResult =
|
|
25
|
+
{ status: "captured"; result: ScreenshotResult } |
|
|
26
|
+
{ status: "failed"; failure: CaptureFailure };
|
|
27
|
+
|
|
28
|
+
type CaptureOutcome =
|
|
29
|
+
{ status: "captured"; result: ScreenshotResult } |
|
|
30
|
+
{ status: "stalled"; failure: CaptureFailure };
|
|
31
|
+
|
|
9
32
|
const DEFAULT_SCREENSHOT_INTERVAL = 10;
|
|
33
|
+
const PRESENTATION_PROBE_MS = 250;
|
|
34
|
+
|
|
35
|
+
const NOT_PRESENTING_MESSAGE =
|
|
36
|
+
"Screenshot failed: the display is not presenting frames to this window, so GTK has no rendered " +
|
|
37
|
+
"content to capture. The window is hidden, minimized, or its compositor is not presenting it; this " +
|
|
38
|
+
"is a display problem, not a widget problem.";
|
|
39
|
+
|
|
40
|
+
const EMPTY_WIDGET_HINT =
|
|
41
|
+
"The display is presenting frames to this window, so the widget itself is empty: it painted nothing.";
|
|
42
|
+
|
|
43
|
+
const OTHER_FAILURE_HINT =
|
|
44
|
+
"The display is presenting frames to this window, so the capture failed for another reason.";
|
|
45
|
+
|
|
46
|
+
const NOTHING_ON_SCREEN_MESSAGE = "Nothing is on screen to capture: no toplevel window is mapped";
|
|
10
47
|
|
|
11
48
|
const bytesToBase64 = (bytes: number[]): string => {
|
|
12
49
|
return Buffer.from(bytes).toString("base64");
|
|
@@ -16,162 +53,294 @@ const describeWidgetState = (widget: Gtk.Widget): string =>
|
|
|
16
53
|
`realized=${String(widget.getRealized())} mapped=${String(widget.getMapped())} ` +
|
|
17
54
|
`visible=${String(widget.getVisible())}`;
|
|
18
55
|
|
|
19
|
-
const
|
|
20
|
-
const
|
|
56
|
+
const getMappedPopovers = (widget: Gtk.Widget): Gtk.Popover[] => {
|
|
57
|
+
const popovers: Gtk.Popover[] = [];
|
|
58
|
+
|
|
59
|
+
for (const descendant of descendants(widget)) {
|
|
60
|
+
if (descendant instanceof Gtk.Popover && descendant.getMapped()) {
|
|
61
|
+
popovers.push(descendant);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return popovers;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const appendPopover = (snapshot: Gtk.Snapshot, target: Gtk.Widget, popover: Gtk.Popover): void => {
|
|
69
|
+
const [hasBounds, bounds] = popover.computeBounds(target);
|
|
70
|
+
const paintable = new Gtk.WidgetPaintable({ widget: popover });
|
|
21
71
|
const width = paintable.getIntrinsicWidth();
|
|
22
72
|
const height = paintable.getIntrinsicHeight();
|
|
23
73
|
|
|
24
|
-
if (width <= 0 || height <= 0) {
|
|
25
|
-
|
|
74
|
+
if (!hasBounds || width <= 0 || height <= 0) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
snapshot.save();
|
|
79
|
+
snapshot.translate(new Graphene.Point({ x: bounds.origin.x, y: bounds.origin.y }));
|
|
80
|
+
paintable.snapshot(snapshot, width, height);
|
|
81
|
+
snapshot.restore();
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const appendPopovers = (snapshot: Gtk.Snapshot, target: Gtk.Widget): void => {
|
|
85
|
+
for (const popover of getMappedPopovers(target)) {
|
|
86
|
+
appendPopover(snapshot, target, popover);
|
|
26
87
|
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const getSurface = (widget: Gtk.Widget): Gdk.Surface | null => widget.getNative()?.getSurface() ?? null;
|
|
91
|
+
const isSurfaceOnScreen = (widget: Gtk.Widget): boolean => getSurface(widget)?.getMapped() ?? false;
|
|
92
|
+
|
|
93
|
+
const getFrameClock = (widget: Gtk.Widget): Gdk.FrameClock | null =>
|
|
94
|
+
widget.getRoot()?.getFrameClock() ?? widget.getFrameClock();
|
|
95
|
+
|
|
96
|
+
const getFrameCounter = (widget: Gtk.Widget): bigint | null => getFrameClock(widget)?.getFrameCounter() ?? null;
|
|
97
|
+
|
|
98
|
+
const describePresentation = (widget: Gtk.Widget): string =>
|
|
99
|
+
`${describeWidgetState(widget)} surfaceMapped=${String(isSurfaceOnScreen(widget))} ` +
|
|
100
|
+
`frameCounter=${String(getFrameCounter(widget))}`;
|
|
101
|
+
|
|
102
|
+
const requestFrame = (widget: Gtk.Widget): void => {
|
|
103
|
+
getFrameClock(widget)?.requestPhase(Gdk.FrameClockPhase.UPDATE);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const startFrameProbe = (widget: Gtk.Widget): FrameProbe => {
|
|
107
|
+
requestFrame(widget);
|
|
27
108
|
|
|
109
|
+
return { counter: getFrameCounter(widget), startedAt: Date.now() };
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const updateFrameProbe = (widget: Gtk.Widget, probe: FrameProbe): void => {
|
|
113
|
+
requestFrame(widget);
|
|
114
|
+
|
|
115
|
+
if (probe.counter !== null) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const counter = getFrameCounter(widget);
|
|
120
|
+
|
|
121
|
+
if (counter !== null) {
|
|
122
|
+
probe.counter = counter;
|
|
123
|
+
probe.startedAt = Date.now();
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const hasFrameAdvanced = (widget: Gtk.Widget, probe: FrameProbe): boolean => {
|
|
128
|
+
const counter = getFrameCounter(widget);
|
|
129
|
+
|
|
130
|
+
return counter !== null && probe.counter !== null && counter > probe.counter;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const isPresenting = (widget: Gtk.Widget, probe: FrameProbe): boolean =>
|
|
134
|
+
isSurfaceOnScreen(widget) && hasFrameAdvanced(widget, probe);
|
|
135
|
+
|
|
136
|
+
const isPresentationStalled = (widget: Gtk.Widget, probe: FrameProbe): boolean =>
|
|
137
|
+
Date.now() - probe.startedAt >= PRESENTATION_PROBE_MS && !isPresenting(widget, probe);
|
|
138
|
+
|
|
139
|
+
const allocateRoot = (widget: Gtk.Widget): void => {
|
|
140
|
+
const root = widget.getRoot();
|
|
141
|
+
|
|
142
|
+
if (!root) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const width = root.getWidth();
|
|
147
|
+
const height = root.getHeight();
|
|
148
|
+
|
|
149
|
+
if (width > 0 && height > 0) {
|
|
150
|
+
root.allocate(width, height, -1, null);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const isDirectlyPaintable = (child: Gtk.Widget): boolean =>
|
|
155
|
+
child.getMapped() && child.getWidth() > 0 && !(child instanceof Gtk.Popover);
|
|
156
|
+
|
|
157
|
+
const getPaintableNode = (widget: Gtk.Widget, scale: number, width: number, height: number): Gsk.RenderNode | null => {
|
|
158
|
+
const paintable = new Gtk.WidgetPaintable({ widget });
|
|
28
159
|
const snapshot = new Gtk.Snapshot();
|
|
29
160
|
snapshot.scale(scale, scale);
|
|
30
161
|
paintable.snapshot(snapshot, width, height);
|
|
31
|
-
|
|
162
|
+
appendPopovers(snapshot, widget);
|
|
32
163
|
|
|
33
|
-
|
|
34
|
-
|
|
164
|
+
return snapshot.toNode();
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const getChildrenNode = (widget: Gtk.Widget, scale: number): Gsk.RenderNode | null => {
|
|
168
|
+
const snapshot = new Gtk.Snapshot();
|
|
169
|
+
snapshot.scale(scale, scale);
|
|
170
|
+
|
|
171
|
+
for (let child = widget.getFirstChild(); child !== null; child = child.getNextSibling()) {
|
|
172
|
+
if (isDirectlyPaintable(child)) {
|
|
173
|
+
widget.snapshotChild(child, snapshot);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
appendPopovers(snapshot, widget);
|
|
178
|
+
|
|
179
|
+
return snapshot.toNode();
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const getRenderNode = (widget: Gtk.Widget, scale: number, width: number, height: number): Gsk.RenderNode | null => {
|
|
183
|
+
const presented = getPaintableNode(widget, scale, width, height);
|
|
184
|
+
|
|
185
|
+
if (presented) {
|
|
186
|
+
return presented;
|
|
35
187
|
}
|
|
36
188
|
|
|
37
|
-
|
|
189
|
+
allocateRoot(widget);
|
|
190
|
+
|
|
191
|
+
return getPaintableNode(widget, scale, width, height) ?? getChildrenNode(widget, scale);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const renderToPng = (widget: Gtk.Widget, node: Gsk.RenderNode, width: number, height: number): ScreenshotResult => {
|
|
38
195
|
const renderer = new Gsk.CairoRenderer();
|
|
39
|
-
renderer.realizeForDisplay(
|
|
196
|
+
renderer.realizeForDisplay(widget.getDisplay());
|
|
40
197
|
|
|
41
198
|
try {
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const data =
|
|
199
|
+
const viewport = Graphene.Rect.alloc().init(0, 0, width, height);
|
|
200
|
+
const texture = renderer.renderTexture(node, viewport);
|
|
201
|
+
const data = texture.saveToPngBytes().getData();
|
|
45
202
|
|
|
46
203
|
if (!data) {
|
|
47
204
|
throw new Error("Failed to serialize screenshot to PNG");
|
|
48
205
|
}
|
|
49
206
|
|
|
50
|
-
return {
|
|
51
|
-
data: bytesToBase64(data),
|
|
52
|
-
mimeType: "image/png",
|
|
53
|
-
width: Math.round(width * scale),
|
|
54
|
-
height: Math.round(height * scale),
|
|
55
|
-
};
|
|
207
|
+
return { data: bytesToBase64(data), mimeType: "image/png", width, height };
|
|
56
208
|
} finally {
|
|
57
209
|
renderer.unrealize();
|
|
58
210
|
}
|
|
59
211
|
};
|
|
60
212
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
* @param options Optional scale, timeout, and retry interval.
|
|
66
|
-
* @returns The base64-encoded PNG data along with its mime type and dimensions.
|
|
67
|
-
*/
|
|
68
|
-
const screenshot = async (widget: Gtk.Widget, options?: ScreenshotOptions): Promise<ScreenshotResult> => {
|
|
69
|
-
const scale = options?.scale ?? 1;
|
|
213
|
+
const captureSnapshot = (widget: Gtk.Widget, scale: number): CaptureResult => {
|
|
214
|
+
const paintable = new Gtk.WidgetPaintable({ widget });
|
|
215
|
+
const width = paintable.getIntrinsicWidth();
|
|
216
|
+
const height = paintable.getIntrinsicHeight();
|
|
70
217
|
|
|
71
|
-
if (
|
|
72
|
-
|
|
218
|
+
if (width <= 0 || height <= 0) {
|
|
219
|
+
return { status: "failed", failure: "no-size" };
|
|
73
220
|
}
|
|
74
221
|
|
|
75
|
-
|
|
76
|
-
timeout: options?.timeout,
|
|
77
|
-
interval: options?.interval ?? DEFAULT_SCREENSHOT_INTERVAL,
|
|
78
|
-
});
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const firstToplevelWindow = (windows: Gtk.Widget[]): Gtk.Window => {
|
|
82
|
-
const [first] = windows;
|
|
222
|
+
const renderNode = getRenderNode(widget, scale, width, height);
|
|
83
223
|
|
|
84
|
-
if (!
|
|
85
|
-
|
|
224
|
+
if (!renderNode) {
|
|
225
|
+
return { status: "failed", failure: "no-content" };
|
|
86
226
|
}
|
|
87
227
|
|
|
88
|
-
|
|
228
|
+
const scaledWidth = Math.round(width * scale);
|
|
229
|
+
const scaledHeight = Math.round(height * scale);
|
|
230
|
+
|
|
231
|
+
return { status: "captured", result: renderToPng(widget, renderNode, scaledWidth, scaledHeight) };
|
|
89
232
|
};
|
|
90
233
|
|
|
91
|
-
const
|
|
92
|
-
|
|
234
|
+
const failureMessage = (widget: Gtk.Widget, failure: CaptureFailure): string =>
|
|
235
|
+
failure === "no-size"
|
|
236
|
+
? `Widget has no size: ensure it is realized and visible (${describeWidgetState(widget)})`
|
|
237
|
+
: `Widget produced no render content (${describeWidgetState(widget)})`;
|
|
93
238
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
239
|
+
const notPresentingMessage = (widget: Gtk.Widget): string =>
|
|
240
|
+
`${NOT_PRESENTING_MESSAGE} (${describePresentation(widget)})`;
|
|
97
241
|
|
|
98
|
-
|
|
99
|
-
}
|
|
242
|
+
const stalledMessage = (widget: Gtk.Widget, failure: CaptureFailure): string =>
|
|
243
|
+
`${notPresentingMessage(widget)}\n\n${failureMessage(widget, failure)}`;
|
|
100
244
|
|
|
101
|
-
const
|
|
245
|
+
const presentingHint = (failure: CaptureFailure | null): string =>
|
|
246
|
+
failure === "no-content" ? EMPTY_WIDGET_HINT : OTHER_FAILURE_HINT;
|
|
102
247
|
|
|
103
|
-
const
|
|
104
|
-
|
|
248
|
+
const timeoutHint = (widget: Gtk.Widget, state: CaptureState): string =>
|
|
249
|
+
isPresenting(widget, state.probe) ? presentingHint(state.failure) : notPresentingMessage(widget);
|
|
105
250
|
|
|
106
|
-
|
|
107
|
-
};
|
|
251
|
+
const explainTimeout = (error: Error, widget: Gtk.Widget, state: CaptureState): Error =>
|
|
252
|
+
getConfig().getElementError(`${error.message}\n\n${timeoutHint(widget, state)}`);
|
|
108
253
|
|
|
109
|
-
const
|
|
110
|
-
|
|
254
|
+
const attemptCapture = (widget: Gtk.Widget, scale: number, state: CaptureState): CaptureOutcome => {
|
|
255
|
+
const attempt = captureSnapshot(widget, scale);
|
|
111
256
|
|
|
112
|
-
|
|
113
|
-
|
|
257
|
+
if (attempt.status === "captured") {
|
|
258
|
+
return attempt;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
state.failure = attempt.failure;
|
|
262
|
+
updateFrameProbe(widget, state.probe);
|
|
114
263
|
|
|
115
|
-
if (
|
|
116
|
-
|
|
264
|
+
if (isPresentationStalled(widget, state.probe)) {
|
|
265
|
+
return { status: "stalled", failure: attempt.failure };
|
|
117
266
|
}
|
|
118
267
|
|
|
119
|
-
|
|
268
|
+
throw new Error(failureMessage(widget, attempt.failure));
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const writeScreenshot = (result: ScreenshotResult, path: string): void => {
|
|
272
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
273
|
+
writeFileSync(path, Buffer.from(result.data, "base64"));
|
|
120
274
|
};
|
|
121
275
|
|
|
122
|
-
const
|
|
123
|
-
|
|
276
|
+
const captureUntilPaintable = (
|
|
277
|
+
widget: Gtk.Widget,
|
|
278
|
+
scale: number,
|
|
279
|
+
options: ScreenshotOptions | undefined,
|
|
280
|
+
): Promise<CaptureOutcome> => {
|
|
281
|
+
const state: CaptureState = { probe: startFrameProbe(widget), failure: null };
|
|
282
|
+
|
|
283
|
+
return waitFor(() => attemptCapture(widget, scale, state), {
|
|
284
|
+
timeout: options?.timeout,
|
|
285
|
+
interval: options?.interval ?? DEFAULT_SCREENSHOT_INTERVAL,
|
|
286
|
+
onTimeout: (error) => explainTimeout(error, widget, state),
|
|
287
|
+
});
|
|
288
|
+
};
|
|
124
289
|
|
|
125
|
-
|
|
126
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Captures a PNG snapshot of a widget, retrying until it has a paintable size,
|
|
292
|
+
* and writes the image to `options.path` when one is given, creating any
|
|
293
|
+
* missing parent directories. Popovers open inside the widget render on their
|
|
294
|
+
* own surfaces, so they are composited into the image at their on-screen
|
|
295
|
+
* positions. A display that never presents a frame leaves GTK without a cached
|
|
296
|
+
* render node, so the widget's contents are then snapshotted directly; when
|
|
297
|
+
* even that yields nothing, the failure names the display instead of blaming
|
|
298
|
+
* the widget.
|
|
299
|
+
*
|
|
300
|
+
* @param widget The widget to render to an image.
|
|
301
|
+
* @param options Optional scale, timeout, retry interval, and output path.
|
|
302
|
+
* @returns The base64-encoded PNG data along with its mime type and dimensions.
|
|
303
|
+
*/
|
|
304
|
+
const screenshot = async (widget: Gtk.Widget, options?: ScreenshotOptions): Promise<ScreenshotResult> => {
|
|
305
|
+
const scale = options?.scale ?? 1;
|
|
306
|
+
|
|
307
|
+
if (!Number.isFinite(scale) || scale <= 0) {
|
|
308
|
+
throw new Error(`Screenshot scale must be a positive number, got ${String(scale)}`);
|
|
127
309
|
}
|
|
128
310
|
|
|
129
|
-
|
|
130
|
-
|
|
311
|
+
const outcome = await captureUntilPaintable(widget, scale, options);
|
|
312
|
+
|
|
313
|
+
if (outcome.status === "stalled") {
|
|
314
|
+
throw new Error(stalledMessage(widget, outcome.failure));
|
|
131
315
|
}
|
|
132
316
|
|
|
133
|
-
if (
|
|
134
|
-
|
|
317
|
+
if (options?.path) {
|
|
318
|
+
writeScreenshot(outcome.result, options.path);
|
|
135
319
|
}
|
|
136
320
|
|
|
137
|
-
return
|
|
321
|
+
return outcome.result;
|
|
138
322
|
};
|
|
139
323
|
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
writeFileSync(filepath, Buffer.from(result.data, "base64"));
|
|
324
|
+
const isWindow = (widget: Gtk.Widget): widget is Gtk.Window => widget instanceof Gtk.Window;
|
|
325
|
+
const isOnScreen = (window: Gtk.Window): boolean => window.getMapped();
|
|
326
|
+
const isActiveWindow = (window: Gtk.Window): boolean => window.isActive();
|
|
144
327
|
|
|
145
|
-
|
|
146
|
-
|
|
328
|
+
const activeToplevel = (): Gtk.Window => {
|
|
329
|
+
const toplevels = Gtk.Window.listToplevels().filter((widget) => isWindow(widget));
|
|
330
|
+
const onScreen = toplevels.filter((window) => isOnScreen(window));
|
|
331
|
+
const target = onScreen.find((window) => isActiveWindow(window)) ?? onScreen[0];
|
|
147
332
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const logScreenshotPath = (filepath: string): void => {
|
|
154
|
-
console.log(`Screenshot saved: file://${filepath}`);
|
|
333
|
+
if (!target) {
|
|
334
|
+
throw new Error(NOTHING_ON_SCREEN_MESSAGE);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return target;
|
|
155
338
|
};
|
|
156
339
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
* logs its path, and returns the image data.
|
|
160
|
-
*
|
|
161
|
-
* @param selector Chooses the window by index, title substring, or regular
|
|
162
|
-
* expression; defaults to the first toplevel.
|
|
163
|
-
* @param options Optional scale, timeout, and retry interval.
|
|
164
|
-
* @returns The base64-encoded PNG data along with its mime type and dimensions.
|
|
165
|
-
*/
|
|
166
|
-
const captureAndSaveScreenshot = async (
|
|
167
|
-
selector?: WindowSelector,
|
|
168
|
-
options?: ScreenshotOptions,
|
|
169
|
-
): Promise<ScreenshotResult> => {
|
|
170
|
-
const target = resolveWindow(selector);
|
|
171
|
-
const result = await screenshot(target, options);
|
|
172
|
-
logScreenshotPath(saveScreenshotToTempFile(result));
|
|
340
|
+
const captureScreen = async (options?: ScreenshotOptions): Promise<ScreenshotResult> => {
|
|
341
|
+
const target = activeToplevel();
|
|
173
342
|
|
|
174
|
-
return
|
|
343
|
+
return screenshot(target, options);
|
|
175
344
|
};
|
|
176
345
|
|
|
177
|
-
export {
|
|
346
|
+
export { captureScreen, screenshot };
|
package/src/suggestions.ts
CHANGED
|
@@ -20,10 +20,13 @@ type Method = "Role" | "LabelText" | "PlaceholderText" | "Text" | "DisplayValue"
|
|
|
20
20
|
* `toString` that renders the full call.
|
|
21
21
|
*/
|
|
22
22
|
type Suggestion = {
|
|
23
|
+
/** The query family, such as `Role`. */
|
|
23
24
|
queryName: Method;
|
|
24
25
|
/** The full query function name, such as `getByRole`. */
|
|
25
26
|
queryMethod: string;
|
|
27
|
+
/** The variant the suggestion was asked for, which prefixes `Suggestion.queryMethod`. */
|
|
26
28
|
variant: Variant;
|
|
29
|
+
/** Renders the suggested call with its arguments, such as `getByRole(Gtk.AccessibleRole.BUTTON)`. */
|
|
27
30
|
toString: () => string;
|
|
28
31
|
};
|
|
29
32
|
|