@gtkx/testing 0.21.0 → 1.0.0-rc.2
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 +137 -34
- package/dist/act.d.ts +19 -0
- package/dist/act.d.ts.map +1 -0
- package/dist/act.js +56 -0
- package/dist/act.js.map +1 -0
- package/dist/bound-queries.d.ts +33 -0
- package/dist/bound-queries.d.ts.map +1 -0
- package/dist/bound-queries.js +2 -0
- package/dist/bound-queries.js.map +1 -0
- package/dist/build-queries.d.ts +16 -0
- package/dist/build-queries.d.ts.map +1 -0
- package/dist/build-queries.js +115 -0
- package/dist/build-queries.js.map +1 -0
- package/dist/cleanup-registry.d.ts +5 -0
- package/dist/cleanup-registry.d.ts.map +1 -0
- package/dist/cleanup-registry.js +21 -0
- package/dist/cleanup-registry.js.map +1 -0
- package/dist/config.d.ts +20 -52
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -46
- package/dist/config.js.map +1 -1
- package/dist/editable.d.ts +8 -0
- package/dist/editable.d.ts.map +1 -0
- package/dist/editable.js +26 -0
- package/dist/editable.js.map +1 -0
- package/dist/errors.d.ts +30 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +135 -0
- package/dist/errors.js.map +1 -0
- package/dist/fire-event.d.ts +8 -25
- package/dist/fire-event.d.ts.map +1 -1
- package/dist/fire-event.js +11 -30
- package/dist/fire-event.js.map +1 -1
- package/dist/frame-sync.d.ts +4 -0
- package/dist/frame-sync.d.ts.map +1 -0
- package/dist/frame-sync.js +37 -0
- package/dist/frame-sync.js.map +1 -0
- package/dist/index.d.ts +17 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -7
- package/dist/index.js.map +1 -1
- package/dist/matchers.d.ts +59 -0
- package/dist/matchers.d.ts.map +1 -0
- package/dist/matchers.js +117 -0
- package/dist/matchers.js.map +1 -0
- package/dist/pretty-widget.d.ts +24 -26
- package/dist/pretty-widget.d.ts.map +1 -1
- package/dist/pretty-widget.js +111 -117
- package/dist/pretty-widget.js.map +1 -1
- package/dist/queries.d.ts +190 -180
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +291 -320
- package/dist/queries.js.map +1 -1
- package/dist/register-test-hooks.d.ts +2 -0
- package/dist/register-test-hooks.d.ts.map +1 -0
- package/dist/register-test-hooks.js +16 -0
- package/dist/register-test-hooks.js.map +1 -0
- package/dist/render-hook.d.ts +15 -35
- package/dist/render-hook.d.ts.map +1 -1
- package/dist/render-hook.js +9 -45
- package/dist/render-hook.js.map +1 -1
- package/dist/render.d.ts +13 -42
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +138 -121
- package/dist/render.js.map +1 -1
- package/dist/role-helpers.d.ts +19 -53
- package/dist/role-helpers.d.ts.map +1 -1
- package/dist/role-helpers.js +48 -63
- package/dist/role-helpers.js.map +1 -1
- package/dist/screen.d.ts +7 -63
- package/dist/screen.d.ts.map +1 -1
- package/dist/screen.js +24 -110
- package/dist/screen.js.map +1 -1
- package/dist/screenshot.d.ts +21 -23
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +98 -50
- package/dist/screenshot.js.map +1 -1
- package/dist/suggestions.d.ts +27 -0
- package/dist/suggestions.d.ts.map +1 -0
- package/dist/suggestions.js +61 -0
- package/dist/suggestions.js.map +1 -0
- package/dist/traversal.d.ts +12 -10
- package/dist/traversal.d.ts.map +1 -1
- package/dist/traversal.js +36 -15
- package/dist/traversal.js.map +1 -1
- package/dist/types.d.ts +115 -169
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/user-event/adjustment.d.ts +10 -0
- package/dist/user-event/adjustment.d.ts.map +1 -0
- package/dist/user-event/adjustment.js +35 -0
- package/dist/user-event/adjustment.js.map +1 -0
- package/dist/user-event/click.d.ts +8 -0
- package/dist/user-event/click.d.ts.map +1 -0
- package/dist/user-event/click.js +73 -0
- package/dist/user-event/click.js.map +1 -0
- package/dist/user-event/controller.d.ts +8 -0
- package/dist/user-event/controller.d.ts.map +1 -0
- package/dist/user-event/controller.js +31 -0
- package/dist/user-event/controller.js.map +1 -0
- package/dist/user-event/dispatch.d.ts +7 -0
- package/dist/user-event/dispatch.d.ts.map +1 -0
- package/dist/user-event/dispatch.js +11 -0
- package/dist/user-event/dispatch.js.map +1 -0
- package/dist/user-event/event-wrapper.d.ts +4 -0
- package/dist/user-event/event-wrapper.d.ts.map +1 -0
- package/dist/user-event/event-wrapper.js +59 -0
- package/dist/user-event/event-wrapper.js.map +1 -0
- package/dist/user-event/gesture.d.ts +35 -0
- package/dist/user-event/gesture.d.ts.map +1 -0
- package/dist/user-event/gesture.js +138 -0
- package/dist/user-event/gesture.js.map +1 -0
- package/dist/user-event/index.d.ts +51 -0
- package/dist/user-event/index.d.ts.map +1 -0
- package/dist/user-event/index.js +42 -0
- package/dist/user-event/index.js.map +1 -0
- package/dist/user-event/keyboard.d.ts +10 -0
- package/dist/user-event/keyboard.d.ts.map +1 -0
- package/dist/user-event/keyboard.js +204 -0
- package/dist/user-event/keyboard.js.map +1 -0
- package/dist/user-event/pointer.d.ts +10 -0
- package/dist/user-event/pointer.d.ts.map +1 -0
- package/dist/user-event/pointer.js +30 -0
- package/dist/user-event/pointer.js.map +1 -0
- package/dist/user-event/selection.d.ts +5 -0
- package/dist/user-event/selection.d.ts.map +1 -0
- package/dist/user-event/selection.js +105 -0
- package/dist/user-event/selection.js.map +1 -0
- package/dist/user-event/state.d.ts +7 -0
- package/dist/user-event/state.d.ts.map +1 -0
- package/dist/user-event/state.js +3 -0
- package/dist/user-event/state.js.map +1 -0
- package/dist/user-event/text.d.ts +18 -0
- package/dist/user-event/text.d.ts.map +1 -0
- package/dist/user-event/text.js +113 -0
- package/dist/user-event/text.js.map +1 -0
- package/dist/wait-for.d.ts +16 -36
- package/dist/wait-for.d.ts.map +1 -1
- package/dist/wait-for.js +74 -70
- package/dist/wait-for.js.map +1 -1
- package/dist/widget-accessible-properties.d.ts +34 -0
- package/dist/widget-accessible-properties.d.ts.map +1 -0
- package/dist/widget-accessible-properties.js +260 -0
- package/dist/widget-accessible-properties.js.map +1 -0
- package/dist/within.d.ts +10 -26
- package/dist/within.d.ts.map +1 -1
- package/dist/within.js +16 -25
- package/dist/within.js.map +1 -1
- package/package.json +23 -12
- package/src/act.ts +77 -0
- package/src/bound-queries.ts +37 -0
- package/src/build-queries.ts +226 -0
- package/src/cleanup-registry.ts +27 -0
- package/src/config.ts +35 -62
- package/src/editable.ts +37 -0
- package/src/errors.ts +191 -0
- package/src/fire-event.ts +13 -38
- package/src/frame-sync.ts +51 -0
- package/src/index.ts +58 -15
- package/src/matchers.ts +251 -0
- package/src/pretty-widget.ts +163 -130
- package/src/queries.ts +485 -396
- package/src/register-test-hooks.ts +19 -0
- package/src/render-hook.tsx +38 -45
- package/src/render.tsx +192 -155
- package/src/role-helpers.ts +65 -72
- package/src/screen.ts +26 -114
- package/src/screenshot.ts +128 -56
- package/src/suggestions.ts +106 -0
- package/src/traversal.ts +56 -22
- package/src/types.ts +144 -172
- package/src/user-event/adjustment.ts +62 -0
- package/src/user-event/click.ts +99 -0
- package/src/user-event/controller.ts +58 -0
- package/src/user-event/dispatch.ts +25 -0
- package/src/user-event/event-wrapper.ts +80 -0
- package/src/user-event/gesture.ts +239 -0
- package/src/user-event/index.ts +81 -0
- package/src/user-event/keyboard.ts +290 -0
- package/src/user-event/pointer.ts +46 -0
- package/src/user-event/selection.ts +159 -0
- package/src/user-event/state.ts +8 -0
- package/src/user-event/text.ts +172 -0
- package/src/wait-for.ts +117 -78
- package/src/widget-accessible-properties.ts +388 -0
- package/src/within.ts +29 -27
- package/dist/bind-queries.d.ts +0 -14
- package/dist/bind-queries.d.ts.map +0 -1
- package/dist/bind-queries.js +0 -29
- package/dist/bind-queries.js.map +0 -1
- package/dist/error-builder.d.ts +0 -28
- package/dist/error-builder.d.ts.map +0 -1
- package/dist/error-builder.js +0 -81
- package/dist/error-builder.js.map +0 -1
- package/dist/timing.d.ts +0 -18
- package/dist/timing.d.ts.map +0 -1
- package/dist/timing.js +0 -18
- package/dist/timing.js.map +0 -1
- package/dist/user-event.d.ts +0 -138
- package/dist/user-event.d.ts.map +0 -1
- package/dist/user-event.js +0 -411
- package/dist/user-event.js.map +0 -1
- package/dist/widget-text.d.ts +0 -69
- package/dist/widget-text.d.ts.map +0 -1
- package/dist/widget-text.js +0 -177
- package/dist/widget-text.js.map +0 -1
- package/dist/widget.d.ts +0 -3
- package/dist/widget.d.ts.map +0 -1
- package/dist/widget.js +0 -13
- package/dist/widget.js.map +0 -1
- package/src/bind-queries.ts +0 -52
- package/src/error-builder.ts +0 -102
- package/src/timing.ts +0 -17
- package/src/user-event.ts +0 -492
- package/src/widget-text.ts +0 -202
- package/src/widget.ts +0 -15
package/src/user-event.ts
DELETED
|
@@ -1,492 +0,0 @@
|
|
|
1
|
-
import * as Gdk from "@gtkx/ffi/gdk";
|
|
2
|
-
import { type Object as GObject, signalEmitv, signalLookup, typeFromName, Value } from "@gtkx/ffi/gobject";
|
|
3
|
-
import * as Gtk from "@gtkx/ffi/gtk";
|
|
4
|
-
import { fireEvent } from "./fire-event.js";
|
|
5
|
-
import { tick } from "./timing.js";
|
|
6
|
-
import { isEditable } from "./widget.js";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Options for tab navigation.
|
|
10
|
-
*/
|
|
11
|
-
export type TabOptions = {
|
|
12
|
-
/** Navigate backwards (Shift+Tab) instead of forwards */
|
|
13
|
-
shift?: boolean;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const click = async (element: Gtk.Widget): Promise<void> => {
|
|
17
|
-
element.activate();
|
|
18
|
-
await tick();
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const emitClickSequence = async (element: Gtk.Widget, nPress: number): Promise<void> => {
|
|
22
|
-
const controller = getOrCreateController(element, Gtk.GestureClick);
|
|
23
|
-
|
|
24
|
-
for (let i = 1; i <= nPress; i++) {
|
|
25
|
-
const args = [
|
|
26
|
-
Value.newFromObject(controller),
|
|
27
|
-
Value.newFromInt(i),
|
|
28
|
-
Value.newFromDouble(0),
|
|
29
|
-
Value.newFromDouble(0),
|
|
30
|
-
];
|
|
31
|
-
signalEmitv(args, getSignalId(controller, "pressed"), 0);
|
|
32
|
-
signalEmitv(args, getSignalId(controller, "released"), 0);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
await tick();
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const dblClick = async (element: Gtk.Widget): Promise<void> => {
|
|
39
|
-
await emitClickSequence(element, 2);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const tripleClick = async (element: Gtk.Widget): Promise<void> => {
|
|
43
|
-
await emitClickSequence(element, 3);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const tab = async (element: Gtk.Widget, options?: TabOptions): Promise<void> => {
|
|
47
|
-
const direction = options?.shift ? Gtk.DirectionType.TAB_BACKWARD : Gtk.DirectionType.TAB_FORWARD;
|
|
48
|
-
const root = element.getRoot();
|
|
49
|
-
|
|
50
|
-
if (root && root instanceof Gtk.Widget) {
|
|
51
|
-
root.childFocus(direction);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
await tick();
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const type = async (element: Gtk.Widget, text: string): Promise<void> => {
|
|
58
|
-
if (!isEditable(element)) {
|
|
59
|
-
throw new Error("Cannot type into element: expected editable widget (TEXT_BOX, SEARCH_BOX, or SPIN_BUTTON)");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const currentText = element.getText();
|
|
63
|
-
element.setText(currentText + text);
|
|
64
|
-
|
|
65
|
-
await tick();
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const clear = async (element: Gtk.Widget): Promise<void> => {
|
|
69
|
-
if (!isEditable(element)) {
|
|
70
|
-
throw new Error("Cannot clear element: expected editable widget (TEXT_BOX, SEARCH_BOX, or SPIN_BUTTON)");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
element.setText("");
|
|
74
|
-
|
|
75
|
-
await tick();
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const SELECTABLE_ROLES = new Set([Gtk.AccessibleRole.COMBO_BOX, Gtk.AccessibleRole.LIST]);
|
|
79
|
-
|
|
80
|
-
const isSelectable = (widget: Gtk.Widget): boolean => {
|
|
81
|
-
if (!widget) return false;
|
|
82
|
-
return SELECTABLE_ROLES.has(widget.getAccessibleRole());
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const selectListViewItems = (selectionModel: Gtk.SelectionModel, positions: number[], exclusive: boolean): void => {
|
|
86
|
-
if (positions.length === 0) {
|
|
87
|
-
selectionModel.unselectRange(0, selectionModel.getNItems());
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (exclusive && positions.length === 1) {
|
|
92
|
-
selectionModel.selectItem(positions[0] as number, true);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const nItems = selectionModel.getNItems();
|
|
97
|
-
const selected = new Gtk.Bitset();
|
|
98
|
-
const mask = Gtk.Bitset.newRange(0, nItems);
|
|
99
|
-
|
|
100
|
-
for (const pos of positions) {
|
|
101
|
-
selected.add(pos);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
selectionModel.setSelection(selected, mask);
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
const isListView = (widget: Gtk.Widget): widget is Gtk.ListView | Gtk.GridView | Gtk.ColumnView => {
|
|
108
|
-
return widget instanceof Gtk.ListView || widget instanceof Gtk.GridView || widget instanceof Gtk.ColumnView;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const selectOptions = async (element: Gtk.Widget, values: number | number[]): Promise<void> => {
|
|
112
|
-
const valueArray = Array.isArray(values) ? values : [values];
|
|
113
|
-
|
|
114
|
-
if (isListView(element)) {
|
|
115
|
-
const selectionModel = element.getModel() as Gtk.SelectionModel;
|
|
116
|
-
const isMultiSelection = selectionModel instanceof Gtk.MultiSelection;
|
|
117
|
-
selectListViewItems(selectionModel, valueArray, !isMultiSelection);
|
|
118
|
-
await tick();
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (!isSelectable(element)) {
|
|
123
|
-
throw new Error("Cannot select options: expected selectable widget (COMBO_BOX or LIST)");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const role = element.getAccessibleRole();
|
|
127
|
-
|
|
128
|
-
if (role === Gtk.AccessibleRole.COMBO_BOX) {
|
|
129
|
-
if (Array.isArray(values) && values.length > 1) {
|
|
130
|
-
throw new Error("Cannot select multiple options: ComboBox only supports single selection");
|
|
131
|
-
}
|
|
132
|
-
if (element instanceof Gtk.DropDown) {
|
|
133
|
-
(element as Gtk.DropDown).setSelected(valueArray[0] as number);
|
|
134
|
-
} else {
|
|
135
|
-
(element as Gtk.ComboBox).setActive(valueArray[0] as number);
|
|
136
|
-
}
|
|
137
|
-
} else if (role === Gtk.AccessibleRole.LIST) {
|
|
138
|
-
const listBox = element as Gtk.ListBox;
|
|
139
|
-
|
|
140
|
-
for (const value of valueArray) {
|
|
141
|
-
const row = listBox.getRowAtIndex(value);
|
|
142
|
-
|
|
143
|
-
if (row) {
|
|
144
|
-
listBox.selectRow(row);
|
|
145
|
-
row.activate();
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
await tick();
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
const deselectOptions = async (element: Gtk.Widget, values: number | number[]): Promise<void> => {
|
|
154
|
-
const valueArray = Array.isArray(values) ? values : [values];
|
|
155
|
-
|
|
156
|
-
if (isListView(element)) {
|
|
157
|
-
const selectionModel = element.getModel() as Gtk.SelectionModel;
|
|
158
|
-
|
|
159
|
-
for (const pos of valueArray) {
|
|
160
|
-
selectionModel.unselectItem(pos);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
await tick();
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const role = element.getAccessibleRole();
|
|
168
|
-
|
|
169
|
-
if (role !== Gtk.AccessibleRole.LIST) {
|
|
170
|
-
throw new Error("Cannot deselect options: only ListBox supports deselection");
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const listBox = element as Gtk.ListBox;
|
|
174
|
-
|
|
175
|
-
for (const value of valueArray) {
|
|
176
|
-
const row = listBox.getRowAtIndex(value);
|
|
177
|
-
|
|
178
|
-
if (row) {
|
|
179
|
-
listBox.unselectRow(row as Gtk.ListBoxRow);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
await tick();
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
const getOrCreateController = <T extends Gtk.EventController>(element: Gtk.Widget, controllerType: new () => T): T => {
|
|
187
|
-
const controllers = element.observeControllers();
|
|
188
|
-
const nItems = controllers.getNItems();
|
|
189
|
-
|
|
190
|
-
for (let i = 0; i < nItems; i++) {
|
|
191
|
-
const controller = controllers.getObject(i);
|
|
192
|
-
if (controller instanceof controllerType) {
|
|
193
|
-
return controller as T;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const controller = new controllerType();
|
|
198
|
-
element.addController(controller);
|
|
199
|
-
return controller;
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const getSignalId = (target: Gtk.EventController, signalName: string): number => {
|
|
203
|
-
const gtype = typeFromName((target.constructor as typeof GObject).glibTypeName);
|
|
204
|
-
return signalLookup(signalName, gtype);
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
const hover = async (element: Gtk.Widget): Promise<void> => {
|
|
208
|
-
const controller = getOrCreateController(element, Gtk.EventControllerMotion);
|
|
209
|
-
signalEmitv(
|
|
210
|
-
[Value.newFromObject(controller), Value.newFromDouble(0), Value.newFromDouble(0)],
|
|
211
|
-
getSignalId(controller, "enter"),
|
|
212
|
-
0,
|
|
213
|
-
);
|
|
214
|
-
await tick();
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
const unhover = async (element: Gtk.Widget): Promise<void> => {
|
|
218
|
-
const controller = getOrCreateController(element, Gtk.EventControllerMotion);
|
|
219
|
-
signalEmitv([Value.newFromObject(controller)], getSignalId(controller, "leave"), 0);
|
|
220
|
-
await tick();
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
const KEY_MAP: Record<string, number> = {
|
|
224
|
-
Enter: Gdk.KEY_Return,
|
|
225
|
-
Tab: Gdk.KEY_Tab,
|
|
226
|
-
Escape: Gdk.KEY_Escape,
|
|
227
|
-
Backspace: Gdk.KEY_BackSpace,
|
|
228
|
-
Delete: Gdk.KEY_Delete,
|
|
229
|
-
ArrowUp: Gdk.KEY_Up,
|
|
230
|
-
ArrowDown: Gdk.KEY_Down,
|
|
231
|
-
ArrowLeft: Gdk.KEY_Left,
|
|
232
|
-
ArrowRight: Gdk.KEY_Right,
|
|
233
|
-
Home: Gdk.KEY_Home,
|
|
234
|
-
End: Gdk.KEY_End,
|
|
235
|
-
PageUp: Gdk.KEY_Page_Up,
|
|
236
|
-
PageDown: Gdk.KEY_Page_Down,
|
|
237
|
-
Space: Gdk.KEY_space,
|
|
238
|
-
Shift: Gdk.KEY_Shift_L,
|
|
239
|
-
Control: Gdk.KEY_Control_L,
|
|
240
|
-
Alt: Gdk.KEY_Alt_L,
|
|
241
|
-
Meta: Gdk.KEY_Meta_L,
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
const parseKeyboardInput = (input: string): Array<{ keyval: number; press: boolean }> => {
|
|
245
|
-
const actions: Array<{ keyval: number; press: boolean }> = [];
|
|
246
|
-
let i = 0;
|
|
247
|
-
|
|
248
|
-
while (i < input.length) {
|
|
249
|
-
if (input[i] === "{") {
|
|
250
|
-
const endBrace = input.indexOf("}", i);
|
|
251
|
-
if (endBrace === -1) break;
|
|
252
|
-
|
|
253
|
-
let keyName = input.slice(i + 1, endBrace);
|
|
254
|
-
let press = true;
|
|
255
|
-
let release = true;
|
|
256
|
-
|
|
257
|
-
if (keyName.startsWith("/")) {
|
|
258
|
-
keyName = keyName.slice(1);
|
|
259
|
-
press = false;
|
|
260
|
-
} else if (keyName.endsWith(">")) {
|
|
261
|
-
keyName = keyName.slice(0, -1);
|
|
262
|
-
release = false;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const keyval = KEY_MAP[keyName];
|
|
266
|
-
if (keyval === undefined) {
|
|
267
|
-
throw new Error(`Unknown key: {${keyName}}`);
|
|
268
|
-
}
|
|
269
|
-
if (press) actions.push({ keyval, press: true });
|
|
270
|
-
if (release) actions.push({ keyval, press: false });
|
|
271
|
-
|
|
272
|
-
i = endBrace + 1;
|
|
273
|
-
} else {
|
|
274
|
-
const keyval = input.charCodeAt(i);
|
|
275
|
-
actions.push({ keyval, press: true });
|
|
276
|
-
actions.push({ keyval, press: false });
|
|
277
|
-
i++;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
return actions;
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
const MODIFIER_KEYVAL_TO_MASK: Record<number, number> = {
|
|
285
|
-
[Gdk.KEY_Shift_L]: Gdk.ModifierType.SHIFT_MASK,
|
|
286
|
-
[Gdk.KEY_Shift_R]: Gdk.ModifierType.SHIFT_MASK,
|
|
287
|
-
[Gdk.KEY_Control_L]: Gdk.ModifierType.CONTROL_MASK,
|
|
288
|
-
[Gdk.KEY_Control_R]: Gdk.ModifierType.CONTROL_MASK,
|
|
289
|
-
[Gdk.KEY_Alt_L]: Gdk.ModifierType.ALT_MASK,
|
|
290
|
-
[Gdk.KEY_Alt_R]: Gdk.ModifierType.ALT_MASK,
|
|
291
|
-
[Gdk.KEY_Meta_L]: Gdk.ModifierType.META_MASK,
|
|
292
|
-
[Gdk.KEY_Meta_R]: Gdk.ModifierType.META_MASK,
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
let gdkModifierType: number | null = null;
|
|
296
|
-
|
|
297
|
-
const keyboard = async (element: Gtk.Widget, input: string): Promise<void> => {
|
|
298
|
-
gdkModifierType ??= typeFromName("GdkModifierType");
|
|
299
|
-
const controller = getOrCreateController(element, Gtk.EventControllerKey);
|
|
300
|
-
const actions = parseKeyboardInput(input);
|
|
301
|
-
let modifierState = 0;
|
|
302
|
-
|
|
303
|
-
for (const action of actions) {
|
|
304
|
-
const mask = MODIFIER_KEYVAL_TO_MASK[action.keyval];
|
|
305
|
-
|
|
306
|
-
if (mask) {
|
|
307
|
-
if (action.press) {
|
|
308
|
-
modifierState |= mask;
|
|
309
|
-
} else {
|
|
310
|
-
modifierState &= ~mask;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const signalName = action.press ? "key-pressed" : "key-released";
|
|
315
|
-
const returnValue = action.press ? Value.newFromBoolean(false) : undefined;
|
|
316
|
-
signalEmitv(
|
|
317
|
-
[
|
|
318
|
-
Value.newFromObject(controller),
|
|
319
|
-
Value.newFromUint(action.keyval),
|
|
320
|
-
Value.newFromUint(0),
|
|
321
|
-
Value.newFromFlags(gdkModifierType, modifierState),
|
|
322
|
-
],
|
|
323
|
-
getSignalId(controller, signalName),
|
|
324
|
-
0,
|
|
325
|
-
returnValue,
|
|
326
|
-
);
|
|
327
|
-
|
|
328
|
-
if (action.press && action.keyval === Gdk.KEY_Return && isEditable(element)) {
|
|
329
|
-
await fireEvent(element, "activate");
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
await tick();
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Pointer input actions for simulating mouse interactions.
|
|
338
|
-
*
|
|
339
|
-
* - `"click"` or `"[MouseLeft]"`: Full click (press + release)
|
|
340
|
-
* - `"down"` or `"[MouseLeft>]"`: Press and hold
|
|
341
|
-
* - `"up"` or `"[/MouseLeft]"`: Release
|
|
342
|
-
*/
|
|
343
|
-
export type PointerInput = "click" | "down" | "up" | "[MouseLeft]" | "[MouseLeft>]" | "[/MouseLeft]";
|
|
344
|
-
|
|
345
|
-
const pointer = async (element: Gtk.Widget, input: PointerInput): Promise<void> => {
|
|
346
|
-
const controller = getOrCreateController(element, Gtk.GestureClick);
|
|
347
|
-
const pressedArgs = [
|
|
348
|
-
Value.newFromObject(controller),
|
|
349
|
-
Value.newFromInt(1),
|
|
350
|
-
Value.newFromDouble(0),
|
|
351
|
-
Value.newFromDouble(0),
|
|
352
|
-
];
|
|
353
|
-
const releasedArgs = [
|
|
354
|
-
Value.newFromObject(controller),
|
|
355
|
-
Value.newFromInt(1),
|
|
356
|
-
Value.newFromDouble(0),
|
|
357
|
-
Value.newFromDouble(0),
|
|
358
|
-
];
|
|
359
|
-
|
|
360
|
-
if (input === "[MouseLeft]" || input === "click") {
|
|
361
|
-
signalEmitv(pressedArgs, getSignalId(controller, "pressed"), 0);
|
|
362
|
-
signalEmitv(releasedArgs, getSignalId(controller, "released"), 0);
|
|
363
|
-
} else if (input === "[MouseLeft>]" || input === "down") {
|
|
364
|
-
signalEmitv(pressedArgs, getSignalId(controller, "pressed"), 0);
|
|
365
|
-
} else if (input === "[/MouseLeft]" || input === "up") {
|
|
366
|
-
signalEmitv(releasedArgs, getSignalId(controller, "released"), 0);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
await tick();
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* User interaction utilities for testing.
|
|
374
|
-
*
|
|
375
|
-
* Simulates user actions like clicking, typing, and selecting.
|
|
376
|
-
* All methods are async and wait for GTK event processing.
|
|
377
|
-
*
|
|
378
|
-
* @example
|
|
379
|
-
* ```tsx
|
|
380
|
-
* import { render, screen, userEvent } from "@gtkx/testing";
|
|
381
|
-
*
|
|
382
|
-
* test("form submission", async () => {
|
|
383
|
-
* await render(<LoginForm />);
|
|
384
|
-
*
|
|
385
|
-
* const input = await screen.findByRole(Gtk.AccessibleRole.TEXT_BOX);
|
|
386
|
-
* await userEvent.type(input, "username");
|
|
387
|
-
*
|
|
388
|
-
* const button = await screen.findByRole(Gtk.AccessibleRole.BUTTON);
|
|
389
|
-
* await userEvent.click(button);
|
|
390
|
-
* });
|
|
391
|
-
* ```
|
|
392
|
-
*/
|
|
393
|
-
export const userEvent = {
|
|
394
|
-
/**
|
|
395
|
-
* Activates a widget.
|
|
396
|
-
*
|
|
397
|
-
* Uses GTK's native `Gtk.Widget.activate()` to trigger the widget's
|
|
398
|
-
* default action — clicking buttons, toggling checkboxes/switches, etc.
|
|
399
|
-
*/
|
|
400
|
-
click,
|
|
401
|
-
/**
|
|
402
|
-
* Double-clicks a widget.
|
|
403
|
-
*
|
|
404
|
-
* Emits pressed/released signals with n_press=1, then n_press=2.
|
|
405
|
-
*/
|
|
406
|
-
dblClick,
|
|
407
|
-
/**
|
|
408
|
-
* Triple-clicks a widget.
|
|
409
|
-
*
|
|
410
|
-
* Emits pressed/released signals with n_press=1, 2, then 3. Useful for text selection.
|
|
411
|
-
*/
|
|
412
|
-
tripleClick,
|
|
413
|
-
/**
|
|
414
|
-
* Simulates Tab key navigation.
|
|
415
|
-
*
|
|
416
|
-
* @param element - Starting element
|
|
417
|
-
* @param options - Use `shift: true` for backwards navigation
|
|
418
|
-
*/
|
|
419
|
-
tab,
|
|
420
|
-
/**
|
|
421
|
-
* Types text into an editable widget.
|
|
422
|
-
*
|
|
423
|
-
* Appends text to the current content. Works with Entry, SearchEntry,
|
|
424
|
-
* and SpinButton widgets.
|
|
425
|
-
*
|
|
426
|
-
* @param element - The editable widget
|
|
427
|
-
* @param text - Text to type
|
|
428
|
-
*/
|
|
429
|
-
type,
|
|
430
|
-
/**
|
|
431
|
-
* Clears an editable widget's content.
|
|
432
|
-
*
|
|
433
|
-
* Sets the text to empty string.
|
|
434
|
-
*/
|
|
435
|
-
clear,
|
|
436
|
-
/**
|
|
437
|
-
* Selects options in a dropdown or list.
|
|
438
|
-
*
|
|
439
|
-
* Works with DropDown, ComboBox, ListBox, ListView, GridView, and ColumnView.
|
|
440
|
-
*
|
|
441
|
-
* @param element - The selectable widget
|
|
442
|
-
* @param values - Index or array of indices to select
|
|
443
|
-
*/
|
|
444
|
-
selectOptions,
|
|
445
|
-
/**
|
|
446
|
-
* Deselects options in a list.
|
|
447
|
-
*
|
|
448
|
-
* Works with ListBox and multi-selection list views.
|
|
449
|
-
*
|
|
450
|
-
* @param element - The selectable widget
|
|
451
|
-
* @param values - Index or array of indices to deselect
|
|
452
|
-
*/
|
|
453
|
-
deselectOptions,
|
|
454
|
-
/**
|
|
455
|
-
* Simulates mouse entering a widget (hover).
|
|
456
|
-
*
|
|
457
|
-
* Triggers the "enter" signal on the widget's EventControllerMotion.
|
|
458
|
-
*/
|
|
459
|
-
hover,
|
|
460
|
-
/**
|
|
461
|
-
* Simulates mouse leaving a widget (unhover).
|
|
462
|
-
*
|
|
463
|
-
* Triggers the "leave" signal on the widget's EventControllerMotion.
|
|
464
|
-
*/
|
|
465
|
-
unhover,
|
|
466
|
-
/**
|
|
467
|
-
* Simulates keyboard input.
|
|
468
|
-
*
|
|
469
|
-
* Supports special keys in braces: `{Enter}`, `{Tab}`, `{Escape}`, etc.
|
|
470
|
-
* Use `{Key>}` to hold a key down, `{/Key}` to release.
|
|
471
|
-
*
|
|
472
|
-
* @example
|
|
473
|
-
* ```tsx
|
|
474
|
-
* await userEvent.keyboard(element, "hello");
|
|
475
|
-
* await userEvent.keyboard(element, "{Enter}");
|
|
476
|
-
* await userEvent.keyboard(element, "{Shift>}A{/Shift}");
|
|
477
|
-
* ```
|
|
478
|
-
*/
|
|
479
|
-
keyboard,
|
|
480
|
-
/**
|
|
481
|
-
* Simulates pointer (mouse) input.
|
|
482
|
-
*
|
|
483
|
-
* Supports: `"click"`, `"[MouseLeft]"`, `"down"`, `"up"`.
|
|
484
|
-
*
|
|
485
|
-
* @example
|
|
486
|
-
* ```tsx
|
|
487
|
-
* await userEvent.pointer(element, "click");
|
|
488
|
-
* await userEvent.pointer(element, "[MouseLeft]");
|
|
489
|
-
* ```
|
|
490
|
-
*/
|
|
491
|
-
pointer,
|
|
492
|
-
};
|
package/src/widget-text.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { getNativeInterface } from "@gtkx/ffi";
|
|
2
|
-
import * as Gtk from "@gtkx/ffi/gtk";
|
|
3
|
-
|
|
4
|
-
const getLabelText = (widget: Gtk.Widget): string | null => {
|
|
5
|
-
const asLabel = widget as Gtk.Label;
|
|
6
|
-
const asInscription = widget as Gtk.Inscription;
|
|
7
|
-
return asLabel.getLabel?.() ?? asInscription.getText?.() ?? null;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
const getDefaultText = (widget: Gtk.Widget): string | null => {
|
|
11
|
-
if ("getLabel" in widget && typeof widget.getLabel === "function") {
|
|
12
|
-
return (widget.getLabel() as string) || null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if ("getText" in widget && typeof widget.getText === "function") {
|
|
16
|
-
return (widget.getText() as string) || null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if ("getTitle" in widget && typeof widget.getTitle === "function") {
|
|
20
|
-
return (widget.getTitle() as string) || null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return getNativeInterface(widget, Gtk.Editable)?.getText() || null;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const collectDirectChildLabels = (widget: Gtk.Widget): string[] => {
|
|
27
|
-
const labels: string[] = [];
|
|
28
|
-
let child = widget.getFirstChild();
|
|
29
|
-
|
|
30
|
-
while (child) {
|
|
31
|
-
if (child.getAccessibleRole() === Gtk.AccessibleRole.LABEL) {
|
|
32
|
-
const labelText = getLabelText(child);
|
|
33
|
-
if (labelText) labels.push(labelText);
|
|
34
|
-
}
|
|
35
|
-
child = child.getNextSibling();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return labels;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const collectChildLabels = (widget: Gtk.Widget): string[] => {
|
|
42
|
-
const labels: string[] = [];
|
|
43
|
-
let child = widget.getFirstChild();
|
|
44
|
-
|
|
45
|
-
while (child) {
|
|
46
|
-
if (child.getAccessibleRole() === Gtk.AccessibleRole.LABEL) {
|
|
47
|
-
const labelText = getLabelText(child);
|
|
48
|
-
if (labelText) labels.push(labelText);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
labels.push(...collectChildLabels(child));
|
|
52
|
-
child = child.getNextSibling();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return labels;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Returns the text content of a widget, analogous to RTL's getNodeText.
|
|
60
|
-
*
|
|
61
|
-
* Collects direct child GtkLabel text (treated as text nodes) and joins
|
|
62
|
-
* them into a single string. Widget properties like getLabel(), getTitle(),
|
|
63
|
-
* getText() are NOT included — those are attributes, not text content.
|
|
64
|
-
*
|
|
65
|
-
* @param widget - The widget to extract text from
|
|
66
|
-
* @returns The joined text content or null if none found
|
|
67
|
-
*/
|
|
68
|
-
export const getWidgetText = (widget: Gtk.Widget): string | null => {
|
|
69
|
-
const role = widget.getAccessibleRole();
|
|
70
|
-
if (role === undefined) return null;
|
|
71
|
-
|
|
72
|
-
const childLabels = collectDirectChildLabels(widget);
|
|
73
|
-
return childLabels.length > 0 ? childLabels.join("") : null;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Returns the widget's own text from properties (getLabel, getText, getTitle).
|
|
78
|
-
*
|
|
79
|
-
* Used for display/debugging purposes (e.g., prettyWidget). This is
|
|
80
|
-
* analogous to HTML attributes like aria-label, not text content.
|
|
81
|
-
*
|
|
82
|
-
* @param widget - The widget to extract property text from
|
|
83
|
-
* @returns The property text or null if none found
|
|
84
|
-
*/
|
|
85
|
-
export const getWidgetPropertyText = (widget: Gtk.Widget): string | null => {
|
|
86
|
-
return getDefaultText(widget);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Computes the accessible name of a widget for role-based queries.
|
|
91
|
-
*
|
|
92
|
-
* Uses the widget's own text properties first, then recursively collects
|
|
93
|
-
* all descendant label text. This mirrors how ARIA accessible name
|
|
94
|
-
* computation works in the DOM.
|
|
95
|
-
*
|
|
96
|
-
* @param widget - The widget to compute the accessible name for
|
|
97
|
-
* @returns The accessible name or null if none found
|
|
98
|
-
*/
|
|
99
|
-
export const getWidgetAccessibleName = (widget: Gtk.Widget): string | null => {
|
|
100
|
-
const role = widget.getAccessibleRole();
|
|
101
|
-
if (role === undefined) return null;
|
|
102
|
-
|
|
103
|
-
if (role === Gtk.AccessibleRole.TAB_PANEL) {
|
|
104
|
-
const parent = widget.getParent();
|
|
105
|
-
if (parent) {
|
|
106
|
-
const stack = parent as Gtk.Stack;
|
|
107
|
-
const page = stack.getPage?.(widget);
|
|
108
|
-
if (page) {
|
|
109
|
-
return page.getTitle() ?? null;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const ownText = getDefaultText(widget);
|
|
116
|
-
if (ownText) return ownText;
|
|
117
|
-
|
|
118
|
-
const childLabels = collectChildLabels(widget);
|
|
119
|
-
return childLabels.length > 0 ? childLabels.join(" ") : null;
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Gets the widget name (gtk_widget_get_name).
|
|
124
|
-
*
|
|
125
|
-
* @param widget - The widget to get the name from
|
|
126
|
-
* @returns The widget name or null if not set
|
|
127
|
-
*/
|
|
128
|
-
export const getWidgetName = (widget: Gtk.Widget): string | null => {
|
|
129
|
-
return widget.getName();
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Gets the checked state from toggle-like widgets.
|
|
134
|
-
*
|
|
135
|
-
* @param widget - The widget to get the checked state from
|
|
136
|
-
* @returns The checked state or null if not applicable
|
|
137
|
-
*/
|
|
138
|
-
export const getWidgetCheckedState = (widget: Gtk.Widget): boolean | null => {
|
|
139
|
-
const role = widget.getAccessibleRole();
|
|
140
|
-
|
|
141
|
-
switch (role) {
|
|
142
|
-
case Gtk.AccessibleRole.CHECKBOX:
|
|
143
|
-
case Gtk.AccessibleRole.RADIO:
|
|
144
|
-
return (widget as Gtk.CheckButton).getActive();
|
|
145
|
-
case Gtk.AccessibleRole.TOGGLE_BUTTON:
|
|
146
|
-
return (widget as Gtk.ToggleButton).getActive();
|
|
147
|
-
case Gtk.AccessibleRole.SWITCH:
|
|
148
|
-
return (widget as Gtk.Switch).getActive();
|
|
149
|
-
default:
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Gets the pressed state from toggle button widgets.
|
|
156
|
-
*
|
|
157
|
-
* @param widget - The widget to get the pressed state from
|
|
158
|
-
* @returns The pressed state or null if not applicable
|
|
159
|
-
*/
|
|
160
|
-
export const getWidgetPressedState = (widget: Gtk.Widget): boolean | null => {
|
|
161
|
-
const role = widget.getAccessibleRole();
|
|
162
|
-
|
|
163
|
-
if (role === Gtk.AccessibleRole.TOGGLE_BUTTON) {
|
|
164
|
-
return (widget as Gtk.ToggleButton).getActive();
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return null;
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Gets the expanded state from expander widgets.
|
|
172
|
-
*
|
|
173
|
-
* @param widget - The widget to get the expanded state from
|
|
174
|
-
* @returns The expanded state or null if not applicable
|
|
175
|
-
*/
|
|
176
|
-
export const getWidgetExpandedState = (widget: Gtk.Widget): boolean | null => {
|
|
177
|
-
if (widget instanceof Gtk.Expander) {
|
|
178
|
-
return widget.getExpanded();
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (widget instanceof Gtk.TreeExpander) {
|
|
182
|
-
return widget.getListRow()?.getExpanded() ?? null;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return null;
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Gets the selected state from selectable widgets.
|
|
190
|
-
*
|
|
191
|
-
* @param widget - The widget to get the selected state from
|
|
192
|
-
* @returns The selected state or null if not applicable
|
|
193
|
-
*/
|
|
194
|
-
export const getWidgetSelectedState = (widget: Gtk.Widget): boolean | null => {
|
|
195
|
-
const role = widget.getAccessibleRole();
|
|
196
|
-
|
|
197
|
-
if (role === Gtk.AccessibleRole.ROW) {
|
|
198
|
-
return (widget as Gtk.ListBoxRow).isSelected();
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return null;
|
|
202
|
-
};
|
package/src/widget.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import * as Gtk from "@gtkx/ffi/gtk";
|
|
2
|
-
|
|
3
|
-
const EDITABLE_ROLES = new Set([
|
|
4
|
-
Gtk.AccessibleRole.TEXT_BOX,
|
|
5
|
-
Gtk.AccessibleRole.SEARCH_BOX,
|
|
6
|
-
Gtk.AccessibleRole.SPIN_BUTTON,
|
|
7
|
-
]);
|
|
8
|
-
|
|
9
|
-
export const isEditable = (widget: unknown): widget is Gtk.Editable => {
|
|
10
|
-
if (!(widget instanceof Gtk.Widget)) {
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return EDITABLE_ROLES.has(widget.getAccessibleRole());
|
|
15
|
-
};
|