@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
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
2
2
|
import { formatRoleList } from "../role-helpers.js";
|
|
3
|
+
import { getCallableMethod } from "../widget-getters.js";
|
|
3
4
|
import { wrapEvent } from "./event-wrapper.js";
|
|
5
|
+
import {
|
|
6
|
+
getChildAtIndex,
|
|
7
|
+
hasIndexedChildren,
|
|
8
|
+
isChildSelected,
|
|
9
|
+
selectContainerChild,
|
|
10
|
+
unselectContainerChild,
|
|
11
|
+
} from "./indexed-children.js";
|
|
4
12
|
|
|
5
13
|
type CollectionWidget = Gtk.ListView | Gtk.GridView | Gtk.ColumnView;
|
|
6
|
-
type
|
|
14
|
+
type IndexedChildAction = (container: Gtk.Widget, child: Gtk.Widget) => void;
|
|
7
15
|
|
|
8
|
-
const SELECTABLE_ROLES: Set<Gtk.AccessibleRole> = new Set([
|
|
16
|
+
const SELECTABLE_ROLES: Set<Gtk.AccessibleRole> = new Set([
|
|
17
|
+
Gtk.AccessibleRole.COMBO_BOX,
|
|
18
|
+
Gtk.AccessibleRole.GRID,
|
|
19
|
+
Gtk.AccessibleRole.LIST,
|
|
20
|
+
]);
|
|
9
21
|
|
|
10
22
|
const isSelectable = (widget: Gtk.Widget): boolean => {
|
|
11
23
|
return SELECTABLE_ROLES.has(widget.getAccessibleRole());
|
|
@@ -44,42 +56,40 @@ const selectListViewItems = (
|
|
|
44
56
|
const isListView = (widget: Gtk.Widget): widget is CollectionWidget =>
|
|
45
57
|
widget instanceof Gtk.ListView || widget instanceof Gtk.GridView || widget instanceof Gtk.ColumnView;
|
|
46
58
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
throw new Error("Cannot select multiple options: ComboBox only supports single selection");
|
|
50
|
-
}
|
|
59
|
+
const getIndexSelector = (widget: Gtk.Widget): ((position: number) => void) | null =>
|
|
60
|
+
getCallableMethod<[number], unknown>(widget, "setSelected");
|
|
51
61
|
|
|
52
|
-
|
|
62
|
+
const selectDropDownOption = (widget: Gtk.Widget, valueArray: number[]): void => {
|
|
63
|
+
const setSelected = getIndexSelector(widget);
|
|
53
64
|
|
|
54
|
-
if (
|
|
55
|
-
|
|
65
|
+
if (!setSelected) {
|
|
66
|
+
throw new TypeError("Cannot select options: the COMBO_BOX role needs a widget exposing setSelected");
|
|
56
67
|
}
|
|
57
68
|
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
} else if (widget instanceof Gtk.ComboBox) {
|
|
61
|
-
widget.setActive(selection);
|
|
69
|
+
if (valueArray.length > 1) {
|
|
70
|
+
throw new Error("Cannot select multiple options: a drop-down only supports single selection");
|
|
62
71
|
}
|
|
63
|
-
};
|
|
64
72
|
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
const [selection] = valueArray;
|
|
74
|
+
|
|
75
|
+
if (selection !== undefined) {
|
|
76
|
+
setSelected(selection);
|
|
77
|
+
}
|
|
68
78
|
};
|
|
69
79
|
|
|
70
|
-
const
|
|
71
|
-
|
|
80
|
+
const unselectChildRow = (container: Gtk.Widget, child: Gtk.Widget): void => {
|
|
81
|
+
if (isChildSelected(child)) {
|
|
82
|
+
unselectContainerChild(container, child);
|
|
83
|
+
}
|
|
72
84
|
};
|
|
73
85
|
|
|
74
|
-
const
|
|
86
|
+
const applyIndexedChildren = (widget: Gtk.Widget, valueArray: number[], apply: IndexedChildAction): void => {
|
|
75
87
|
for (const value of valueArray) {
|
|
76
|
-
const
|
|
88
|
+
const child = getChildAtIndex(widget, value);
|
|
77
89
|
|
|
78
|
-
if (
|
|
79
|
-
|
|
90
|
+
if (child) {
|
|
91
|
+
apply(widget, child);
|
|
80
92
|
}
|
|
81
|
-
|
|
82
|
-
apply(listBox, row);
|
|
83
93
|
}
|
|
84
94
|
};
|
|
85
95
|
|
|
@@ -102,18 +112,29 @@ const selectInListView = (widget: CollectionWidget, valueArray: number[]): void
|
|
|
102
112
|
selectListViewItems(selectionModel, valueArray, !isMultiSelection);
|
|
103
113
|
};
|
|
104
114
|
|
|
105
|
-
const
|
|
115
|
+
const requireSelectableRole = (widget: Gtk.Widget, verb: string): void => {
|
|
106
116
|
if (!isSelectable(widget)) {
|
|
107
|
-
throw new Error(
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Cannot ${verb} options: expected selectable widget (${formatRoleList(SELECTABLE_ROLES)})`,
|
|
119
|
+
);
|
|
108
120
|
}
|
|
121
|
+
};
|
|
109
122
|
|
|
123
|
+
const selectByRole = (widget: Gtk.Widget, valueArray: number[]): void => {
|
|
124
|
+
requireSelectableRole(widget, "select");
|
|
110
125
|
const role = widget.getAccessibleRole();
|
|
111
126
|
|
|
112
127
|
if (role === Gtk.AccessibleRole.COMBO_BOX) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
selectDropDownOption(widget, valueArray);
|
|
129
|
+
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!hasIndexedChildren(widget)) {
|
|
134
|
+
throw new TypeError("Cannot select options: the widget exposes no children to select by index");
|
|
116
135
|
}
|
|
136
|
+
|
|
137
|
+
applyIndexedChildren(widget, valueArray, selectContainerChild);
|
|
117
138
|
};
|
|
118
139
|
|
|
119
140
|
const runSelectionEvent = (
|
|
@@ -134,6 +155,16 @@ const runSelectionEvent = (
|
|
|
134
155
|
byRole(widget, valueArray);
|
|
135
156
|
});
|
|
136
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Selects the items at the given positions through a list, grid, or column view's selection model,
|
|
160
|
+
* by setting the selected position of a widget with the COMBO_BOX role, or by selecting the indexed
|
|
161
|
+
* children of a list box or flow box through their container. An empty array clears a view's
|
|
162
|
+
* selection. The children are never activated, so no row-activated handler runs.
|
|
163
|
+
*
|
|
164
|
+
* @throws When a view has no selection model, when a COMBO_BOX-role widget is given more than one
|
|
165
|
+
* position or exposes no `setSelected`, or when the widget's role is none of COMBO_BOX, GRID, or
|
|
166
|
+
* LIST.
|
|
167
|
+
*/
|
|
137
168
|
const selectOptions = (widget: Gtk.Widget, values: number | number[]): Promise<void> =>
|
|
138
169
|
runSelectionEvent(widget, values, selectInListView, selectByRole);
|
|
139
170
|
|
|
@@ -146,13 +177,23 @@ const deselectInListView = (widget: Gtk.ListView | Gtk.GridView | Gtk.ColumnView
|
|
|
146
177
|
};
|
|
147
178
|
|
|
148
179
|
const deselectByRole = (widget: Gtk.Widget, valueArray: number[]): void => {
|
|
149
|
-
|
|
150
|
-
|
|
180
|
+
requireSelectableRole(widget, "deselect");
|
|
181
|
+
|
|
182
|
+
if (!hasIndexedChildren(widget)) {
|
|
183
|
+
throw new TypeError("Cannot deselect options: the widget exposes no children to deselect by index");
|
|
151
184
|
}
|
|
152
185
|
|
|
153
|
-
|
|
186
|
+
applyIndexedChildren(widget, valueArray, unselectChildRow);
|
|
154
187
|
};
|
|
155
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Unselects the items at the given positions in a list, grid, or column view, or unselects the
|
|
191
|
+
* indexed children of a list box or flow box through their container. A child that is not selected
|
|
192
|
+
* is left alone, and no child is activated.
|
|
193
|
+
*
|
|
194
|
+
* @throws When a view has no selection model, when the widget's role is none of COMBO_BOX, GRID, or
|
|
195
|
+
* LIST, or when it exposes no indexed children.
|
|
196
|
+
*/
|
|
156
197
|
const deselectOptions = (widget: Gtk.Widget, values: number | number[]): Promise<void> =>
|
|
157
198
|
runSelectionEvent(widget, values, deselectInListView, deselectByRole);
|
|
158
199
|
|
package/src/user-event/state.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
type UserEventState = {
|
|
2
2
|
modifierState: number;
|
|
3
|
-
|
|
3
|
+
isMouseLeftDown: boolean;
|
|
4
4
|
};
|
|
5
5
|
|
|
6
|
-
const createInitialState = (): UserEventState => ({ modifierState: 0,
|
|
6
|
+
const createInitialState = (): UserEventState => ({ modifierState: 0, isMouseLeftDown: false });
|
|
7
7
|
|
|
8
8
|
export { createInitialState, type UserEventState };
|
package/src/user-event/text.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import * as Gdk from "@gtkx/gi/gdk";
|
|
2
2
|
import * as GObject from "@gtkx/gi/gobject";
|
|
3
3
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
4
|
-
import { EDITABLE_ROLES, type EditableTarget, getEditableDelegate, isEditable } from "../editable.js";
|
|
4
|
+
import { EDITABLE_ROLES, type EditableTarget, getEditableDelegate, isEditable, readEditableText } from "../editable.js";
|
|
5
5
|
import { formatRoleList } from "../role-helpers.js";
|
|
6
|
+
import { getWidgetSelection } from "../widget-accessible-properties.js";
|
|
7
|
+
import { callBooleanGetter } from "../widget-getters.js";
|
|
6
8
|
import { wrapEvent } from "./event-wrapper.js";
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
type SignalWatchTarget = {
|
|
11
|
+
on(signal: string, handler: (...args: unknown[]) => unknown, isAfter?: boolean): unknown;
|
|
12
|
+
off(signal: string, handler: (...args: unknown[]) => unknown): unknown;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Options for `userEvent.type`. */
|
|
9
16
|
type TypeOptions = {
|
|
10
|
-
/**
|
|
11
|
-
|
|
17
|
+
/** Focus the widget before typing; defaults to true. */
|
|
18
|
+
shouldFocus?: boolean | undefined;
|
|
12
19
|
/** Select from this offset before typing, replacing the selected range with the typed text. */
|
|
13
20
|
initialSelectionStart?: number | undefined;
|
|
14
21
|
/** End offset of the initial selection (defaults to the start offset). */
|
|
@@ -16,6 +23,7 @@ type TypeOptions = {
|
|
|
16
23
|
};
|
|
17
24
|
|
|
18
25
|
const EDITABLE_REQUIRED = `expected editable widget (${formatRoleList(EDITABLE_ROLES)})`;
|
|
26
|
+
const CLEAR_REFUSED = "Cannot clear element: the widget refuses to delete part of its text";
|
|
19
27
|
|
|
20
28
|
const insertEditableText = (widget: EditableTarget, text: string): void => {
|
|
21
29
|
if (widget instanceof Gtk.TextView) {
|
|
@@ -37,18 +45,7 @@ const insertEditableText = (widget: EditableTarget, text: string): void => {
|
|
|
37
45
|
widget.setPosition(newPosition);
|
|
38
46
|
};
|
|
39
47
|
|
|
40
|
-
const readSelection = (widget: EditableTarget): string =>
|
|
41
|
-
if (widget instanceof Gtk.TextView) {
|
|
42
|
-
const buffer = widget.getBuffer();
|
|
43
|
-
const [hasSelection, start, end] = buffer.getSelectionBounds();
|
|
44
|
-
|
|
45
|
-
return hasSelection ? buffer.getText(start, end, true) : "";
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const [hasSelection, start, end] = widget.getSelectionBounds();
|
|
49
|
-
|
|
50
|
-
return hasSelection ? widget.getChars(start, end) : "";
|
|
51
|
-
};
|
|
48
|
+
const readSelection = (widget: EditableTarget): string => getWidgetSelection(widget) ?? "";
|
|
52
49
|
|
|
53
50
|
const deleteSelection = (widget: EditableTarget): void => {
|
|
54
51
|
if (widget instanceof Gtk.TextView) {
|
|
@@ -60,14 +57,15 @@ const deleteSelection = (widget: EditableTarget): void => {
|
|
|
60
57
|
widget.deleteSelection();
|
|
61
58
|
};
|
|
62
59
|
|
|
63
|
-
const
|
|
60
|
+
const deleteAllText = (widget: EditableTarget): void => {
|
|
64
61
|
if (widget instanceof Gtk.TextView) {
|
|
65
|
-
widget.getBuffer()
|
|
62
|
+
const buffer = widget.getBuffer();
|
|
63
|
+
buffer.deleteInteractive(buffer.getStartIter(), buffer.getEndIter(), widget.getEditable());
|
|
66
64
|
|
|
67
65
|
return;
|
|
68
66
|
}
|
|
69
67
|
|
|
70
|
-
widget.
|
|
68
|
+
widget.deleteText(0, -1);
|
|
71
69
|
};
|
|
72
70
|
|
|
73
71
|
const applyTextViewSelection = (widget: Gtk.TextView, start: number, end: number): void => {
|
|
@@ -131,9 +129,15 @@ const runEditableEvent = (
|
|
|
131
129
|
action(widget);
|
|
132
130
|
});
|
|
133
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Focuses the widget unless `shouldFocus` is false, applies any initial selection, and inserts the text
|
|
134
|
+
* at the cursor.
|
|
135
|
+
*
|
|
136
|
+
* @throws When the widget is neither a Gtk.Editable nor a Gtk.TextView.
|
|
137
|
+
*/
|
|
134
138
|
const type = (widget: Gtk.Widget, text: string, options?: TypeOptions): Promise<void> =>
|
|
135
139
|
runEditableEvent(widget, "Cannot type into element", (editable) => {
|
|
136
|
-
if (
|
|
140
|
+
if (options?.shouldFocus ?? true) {
|
|
137
141
|
editable.grabFocus();
|
|
138
142
|
}
|
|
139
143
|
|
|
@@ -141,22 +145,116 @@ const type = (widget: Gtk.Widget, text: string, options?: TypeOptions): Promise<
|
|
|
141
145
|
insertEditableText(editable, text);
|
|
142
146
|
});
|
|
143
147
|
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
const isWidgetEditable = (widget: Gtk.Widget): boolean => callBooleanGetter(widget, "getEditable") ?? true;
|
|
149
|
+
|
|
150
|
+
const hasNonEditableChar = (iter: Gtk.TextIter, isDefaultEditable: boolean): boolean => {
|
|
151
|
+
while (!iter.isEnd()) {
|
|
152
|
+
if (!iter.editable(isDefaultEditable)) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!iter.forwardChar()) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return false;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const hasProtectedText = (widget: EditableTarget): boolean =>
|
|
165
|
+
widget instanceof Gtk.TextView && hasNonEditableChar(widget.getBuffer().getStartIter(), widget.getEditable());
|
|
166
|
+
|
|
167
|
+
const requireClearableText = (editable: EditableTarget): void => {
|
|
168
|
+
if (!isWidgetEditable(editable)) {
|
|
169
|
+
throw new Error("Cannot clear element: the widget is not editable");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (hasProtectedText(editable)) {
|
|
173
|
+
throw new Error(CLEAR_REFUSED);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const deletionSignalFor = (widget: EditableTarget): [SignalWatchTarget, string] =>
|
|
178
|
+
widget instanceof Gtk.TextView ? [widget.getBuffer(), "delete-range"] : [widget, "delete-text"];
|
|
179
|
+
|
|
180
|
+
const isDeletionApplied = (emitter: SignalWatchTarget, signal: string, apply: () => void): boolean => {
|
|
181
|
+
let isApplied = false;
|
|
182
|
+
|
|
183
|
+
const watch = (): void => {
|
|
184
|
+
isApplied = true;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
emitter.on(signal, watch, true);
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
apply();
|
|
191
|
+
} finally {
|
|
192
|
+
emitter.off(signal, watch);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return isApplied;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const didDeleteAllText = (widget: EditableTarget): boolean => {
|
|
199
|
+
const [emitter, signal] = deletionSignalFor(widget);
|
|
200
|
+
|
|
201
|
+
return isDeletionApplied(emitter, signal, () => {
|
|
202
|
+
deleteAllText(widget);
|
|
147
203
|
});
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const clearEditable = (editable: EditableTarget): void => {
|
|
207
|
+
requireClearableText(editable);
|
|
208
|
+
editable.grabFocus();
|
|
209
|
+
|
|
210
|
+
if (readEditableText(editable) === "") {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!didDeleteAllText(editable)) {
|
|
215
|
+
throw new Error(CLEAR_REFUSED);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Focuses an editable widget and deletes its whole text through the widget's own editing API, so the
|
|
221
|
+
* deletion emits the signals an edit made by hand emits. Text a tag protects is detected before the
|
|
222
|
+
* deletion runs, leaving such a widget untouched. A widget that writes a new value back in response
|
|
223
|
+
* to the deletion, as an input mask does, is cleared successfully and keeps whatever it wrote.
|
|
224
|
+
*
|
|
225
|
+
* @throws When the widget is neither a Gtk.Editable nor a Gtk.TextView, when it refuses edits, when
|
|
226
|
+
* a tag protects part of its text, or when a handler blocks the deletion.
|
|
227
|
+
*/
|
|
228
|
+
const clear = (widget: Gtk.Widget): Promise<void> =>
|
|
229
|
+
runEditableEvent(widget, "Cannot clear element", clearEditable);
|
|
148
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Writes an editable widget's selected text to its clipboard, or the empty string when nothing is selected.
|
|
233
|
+
*
|
|
234
|
+
* @throws When the widget is neither a Gtk.Editable nor a Gtk.TextView.
|
|
235
|
+
*/
|
|
149
236
|
const copy = (widget: Gtk.Widget): Promise<void> =>
|
|
150
237
|
runEditableEvent(widget, "Cannot copy", (editable) => {
|
|
151
238
|
writeClipboardText(editable, readSelection(editable));
|
|
152
239
|
});
|
|
153
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Writes an editable widget's selected text to its clipboard, then deletes that selection.
|
|
243
|
+
*
|
|
244
|
+
* @throws When the widget is neither a Gtk.Editable nor a Gtk.TextView.
|
|
245
|
+
*/
|
|
154
246
|
const cut = (widget: Gtk.Widget): Promise<void> =>
|
|
155
247
|
runEditableEvent(widget, "Cannot cut", (editable) => {
|
|
156
248
|
writeClipboardText(editable, readSelection(editable));
|
|
157
249
|
deleteSelection(editable);
|
|
158
250
|
});
|
|
159
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Inserts the given text at an editable widget's cursor, reading the clipboard instead when no text
|
|
254
|
+
* is given.
|
|
255
|
+
*
|
|
256
|
+
* @throws When the widget is neither a Gtk.Editable nor a Gtk.TextView.
|
|
257
|
+
*/
|
|
160
258
|
const paste = async (widget: Gtk.Widget, text?: string): Promise<void> => {
|
|
161
259
|
if (!isEditable(widget)) {
|
|
162
260
|
throw new Error(`Cannot paste: ${EDITABLE_REQUIRED}`);
|
package/src/wait-for.ts
CHANGED
|
@@ -5,7 +5,9 @@ import { getConfig } from "./config.js";
|
|
|
5
5
|
import { timeoutError } from "./errors.js";
|
|
6
6
|
|
|
7
7
|
type PollResult<T> = { status: "resolved"; value: T } | { status: "timedout"; lastError: Error | null };
|
|
8
|
+
/** Widgets watched for removal; null counts as already removed. */
|
|
8
9
|
type RemovalTarget = Gtk.Widget | Gtk.Widget[] | null;
|
|
10
|
+
/** The widgets to watch for removal, or a function re-read on every poll to locate them. */
|
|
9
11
|
type ElementOrCallback = Gtk.Widget | Gtk.Widget[] | (() => RemovalTarget);
|
|
10
12
|
|
|
11
13
|
const DEFAULT_INTERVAL = 50;
|