@gtkx/testing 1.0.0 → 1.2.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 +1 -1
- package/dist/act.d.ts +1 -3
- package/dist/act.d.ts.map +1 -1
- package/dist/act.js +1 -1
- package/dist/act.js.map +1 -1
- package/dist/build-queries.d.ts +1 -1
- package/dist/build-queries.js +1 -1
- package/dist/build-queries.js.map +1 -1
- package/dist/config.d.ts +8 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/frame-sync.d.ts +2 -2
- package/dist/frame-sync.d.ts.map +1 -1
- package/dist/frame-sync.js +10 -10
- package/dist/frame-sync.js.map +1 -1
- package/dist/harness-window.d.ts +2 -1
- package/dist/harness-window.d.ts.map +1 -1
- package/dist/harness-window.js +12 -1
- package/dist/harness-window.js.map +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -0
- package/dist/internal.js.map +1 -1
- package/dist/matchers.d.ts.map +1 -1
- package/dist/matchers.js +22 -2
- package/dist/matchers.js.map +1 -1
- package/dist/pretty-widget.d.ts.map +1 -1
- package/dist/pretty-widget.js +2 -1
- package/dist/pretty-widget.js.map +1 -1
- package/dist/production.d.ts.map +1 -1
- package/dist/production.js +2 -2
- package/dist/production.js.map +1 -1
- package/dist/render.d.ts +12 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +45 -11
- package/dist/render.js.map +1 -1
- package/dist/role-helpers.d.ts.map +1 -1
- package/dist/role-helpers.js +2 -1
- package/dist/role-helpers.js.map +1 -1
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +8 -14
- package/dist/screenshot.js.map +1 -1
- package/dist/timers.d.ts +9 -0
- package/dist/timers.d.ts.map +1 -0
- package/dist/timers.js +21 -0
- package/dist/timers.js.map +1 -0
- package/dist/traversal.d.ts +4 -1
- package/dist/traversal.d.ts.map +1 -1
- package/dist/traversal.js +16 -1
- package/dist/traversal.js.map +1 -1
- package/dist/types.d.ts +6 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/user-event/adjustment.d.ts.map +1 -1
- package/dist/user-event/adjustment.js +2 -1
- package/dist/user-event/adjustment.js.map +1 -1
- package/dist/user-event/click.d.ts +5 -4
- package/dist/user-event/click.d.ts.map +1 -1
- package/dist/user-event/click.js +5 -4
- package/dist/user-event/click.js.map +1 -1
- package/dist/user-event/column-header-click.d.ts.map +1 -1
- package/dist/user-event/column-header-click.js +2 -12
- package/dist/user-event/column-header-click.js.map +1 -1
- package/dist/user-event/event-wrapper.d.ts.map +1 -1
- package/dist/user-event/event-wrapper.js +34 -21
- package/dist/user-event/event-wrapper.js.map +1 -1
- package/dist/user-event/index.d.ts +36 -10
- package/dist/user-event/index.d.ts.map +1 -1
- package/dist/user-event/index.js +4 -3
- package/dist/user-event/index.js.map +1 -1
- package/dist/user-event/keyboard.d.ts.map +1 -1
- package/dist/user-event/keyboard.js +93 -38
- package/dist/user-event/keyboard.js.map +1 -1
- package/dist/user-event/list-row-click.d.ts.map +1 -1
- package/dist/user-event/list-row-click.js +4 -5
- package/dist/user-event/list-row-click.js.map +1 -1
- package/dist/user-event/native-click.d.ts.map +1 -1
- package/dist/user-event/native-click.js +4 -0
- package/dist/user-event/native-click.js.map +1 -1
- package/dist/user-event/notebook-tab-click.d.ts +5 -0
- package/dist/user-event/notebook-tab-click.d.ts.map +1 -0
- package/dist/user-event/notebook-tab-click.js +39 -0
- package/dist/user-event/notebook-tab-click.js.map +1 -0
- package/dist/user-event/text.d.ts +6 -3
- package/dist/user-event/text.d.ts.map +1 -1
- package/dist/user-event/text.js +88 -33
- package/dist/user-event/text.js.map +1 -1
- package/dist/wait-for.d.ts.map +1 -1
- package/dist/wait-for.js +4 -3
- package/dist/wait-for.js.map +1 -1
- package/dist/widget-getters.d.ts +4 -1
- package/dist/widget-getters.d.ts.map +1 -1
- package/dist/widget-getters.js +29 -2
- package/dist/widget-getters.js.map +1 -1
- package/dist/window-state.d.ts +10 -0
- package/dist/window-state.d.ts.map +1 -0
- package/dist/window-state.js +41 -0
- package/dist/window-state.js.map +1 -0
- package/package.json +15 -7
- package/src/act.ts +1 -1
- package/src/build-queries.ts +1 -1
- package/src/config.ts +10 -1
- package/src/frame-sync.ts +15 -10
- package/src/harness-window.ts +15 -1
- package/src/internal.ts +1 -0
- package/src/matchers.ts +29 -2
- package/src/pretty-widget.ts +2 -1
- package/src/production.ts +2 -3
- package/src/render.tsx +67 -12
- package/src/role-helpers.ts +2 -1
- package/src/screenshot.ts +10 -17
- package/src/timers.ts +31 -0
- package/src/traversal.ts +22 -0
- package/src/types.ts +6 -1
- package/src/user-event/adjustment.ts +2 -1
- package/src/user-event/click.ts +5 -4
- package/src/user-event/column-header-click.ts +2 -16
- package/src/user-event/event-wrapper.ts +41 -25
- package/src/user-event/index.ts +40 -13
- package/src/user-event/keyboard.ts +124 -54
- package/src/user-event/list-row-click.ts +4 -7
- package/src/user-event/native-click.ts +5 -0
- package/src/user-event/notebook-tab-click.ts +57 -0
- package/src/user-event/text.ts +113 -38
- package/src/wait-for.ts +4 -4
- package/src/widget-getters.ts +55 -2
- package/src/window-state.ts +70 -0
package/src/user-event/text.ts
CHANGED
|
@@ -12,6 +12,8 @@ type SignalWatchTarget = {
|
|
|
12
12
|
off(signal: string, handler: (...args: unknown[]) => unknown): unknown;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
type InsertKind = "typing" | "pasting";
|
|
16
|
+
|
|
15
17
|
/** Options for `userEvent.type`. */
|
|
16
18
|
type TypeOptions = {
|
|
17
19
|
/** Focus the widget before typing; defaults to true. */
|
|
@@ -24,67 +26,138 @@ type TypeOptions = {
|
|
|
24
26
|
|
|
25
27
|
const EDITABLE_REQUIRED = `expected editable widget (${formatRoleList(EDITABLE_ROLES)})`;
|
|
26
28
|
const CLEAR_REFUSED = "Cannot clear element: the widget refuses to delete part of its text";
|
|
29
|
+
const SELECTION_DELETE_COUNT = 1;
|
|
30
|
+
const SINGLE_CHARACTER_LENGTH = 1;
|
|
31
|
+
const TEXT_END_POSITION = -1;
|
|
32
|
+
|
|
33
|
+
const isWidgetEditable = (widget: Gtk.Widget): boolean => callBooleanGetter(widget, "getEditable") ?? true;
|
|
34
|
+
const readSelection = (widget: EditableTarget): string => getWidgetSelection(widget) ?? "";
|
|
35
|
+
const hasSelectedText = (widget: Gtk.Editable): boolean => widget.getSelectionBounds()[0];
|
|
36
|
+
|
|
37
|
+
const getDelegateText = (widget: Gtk.Editable): Gtk.Text | null => {
|
|
38
|
+
const delegate = getEditableDelegate(widget) ?? widget;
|
|
39
|
+
|
|
40
|
+
return delegate instanceof Gtk.Text ? delegate : null;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const getSelectionLength = (buffer: Gtk.TextBuffer): number => {
|
|
44
|
+
const [isSelected, start, end] = buffer.getSelectionBounds();
|
|
45
|
+
|
|
46
|
+
return isSelected ? end.getOffset() - start.getOffset() : 0;
|
|
47
|
+
};
|
|
27
48
|
|
|
28
|
-
const
|
|
49
|
+
const deleteSelection = (widget: EditableTarget): void => {
|
|
29
50
|
if (widget instanceof Gtk.TextView) {
|
|
30
|
-
widget.
|
|
51
|
+
widget.getBuffer().deleteSelection(false, true);
|
|
31
52
|
|
|
32
53
|
return;
|
|
33
54
|
}
|
|
34
55
|
|
|
35
|
-
|
|
56
|
+
widget.deleteSelection();
|
|
57
|
+
};
|
|
36
58
|
|
|
37
|
-
|
|
38
|
-
|
|
59
|
+
const focusPlainEditable = (widget: Gtk.Editable): void => {
|
|
60
|
+
const [isSelected, start, end] = widget.getSelectionBounds();
|
|
61
|
+
widget.grabFocus();
|
|
62
|
+
|
|
63
|
+
if (isSelected) {
|
|
64
|
+
widget.selectRegion(start, end);
|
|
39
65
|
|
|
40
66
|
return;
|
|
41
67
|
}
|
|
42
68
|
|
|
43
|
-
|
|
44
|
-
const newPosition = widget.insertText(text, text.length, position);
|
|
45
|
-
widget.setPosition(newPosition);
|
|
69
|
+
widget.setPosition(TEXT_END_POSITION);
|
|
46
70
|
};
|
|
47
71
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
const deleteSelection = (widget: EditableTarget): void => {
|
|
72
|
+
const focusEditable = (widget: EditableTarget): void => {
|
|
51
73
|
if (widget instanceof Gtk.TextView) {
|
|
52
|
-
widget.
|
|
74
|
+
widget.grabFocus();
|
|
53
75
|
|
|
54
76
|
return;
|
|
55
77
|
}
|
|
56
78
|
|
|
57
|
-
widget
|
|
58
|
-
};
|
|
79
|
+
const target = getDelegateText(widget);
|
|
59
80
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const buffer = widget.getBuffer();
|
|
63
|
-
buffer.deleteInteractive(buffer.getStartIter(), buffer.getEndIter(), widget.getEditable());
|
|
81
|
+
if (target === null) {
|
|
82
|
+
focusPlainEditable(widget);
|
|
64
83
|
|
|
65
84
|
return;
|
|
66
85
|
}
|
|
67
86
|
|
|
68
|
-
|
|
87
|
+
target.grabFocusWithoutSelecting();
|
|
88
|
+
|
|
89
|
+
if (!hasSelectedText(widget)) {
|
|
90
|
+
widget.setPosition(TEXT_END_POSITION);
|
|
91
|
+
}
|
|
69
92
|
};
|
|
70
93
|
|
|
71
|
-
const
|
|
94
|
+
const insertTextViewText = (widget: Gtk.TextView, text: string, kind: InsertKind): void => {
|
|
72
95
|
const buffer = widget.getBuffer();
|
|
73
|
-
|
|
96
|
+
const length = getSelectionLength(buffer);
|
|
97
|
+
buffer.beginUserAction();
|
|
98
|
+
|
|
99
|
+
if (length > 0) {
|
|
100
|
+
widget.emit("delete-from-cursor", Gtk.DeleteType.CHARS, SELECTION_DELETE_COUNT);
|
|
74
101
|
|
|
75
|
-
|
|
76
|
-
|
|
102
|
+
if (kind === "typing" && length > SINGLE_CHARACTER_LENGTH) {
|
|
103
|
+
buffer.endUserAction();
|
|
104
|
+
buffer.beginUserAction();
|
|
105
|
+
}
|
|
77
106
|
}
|
|
107
|
+
|
|
108
|
+
widget.emit("insert-at-cursor", text);
|
|
109
|
+
buffer.endUserAction();
|
|
78
110
|
};
|
|
79
111
|
|
|
80
|
-
const
|
|
81
|
-
|
|
112
|
+
const insertDelegateText = (target: Gtk.Text, text: string): void => {
|
|
113
|
+
if (hasSelectedText(target)) {
|
|
114
|
+
target.emit("delete-from-cursor", Gtk.DeleteType.CHARS, SELECTION_DELETE_COUNT);
|
|
115
|
+
}
|
|
82
116
|
|
|
83
|
-
|
|
117
|
+
target.emit("insert-at-cursor", text);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const insertPlainText = (widget: Gtk.Editable, text: string): void => {
|
|
121
|
+
if (!isWidgetEditable(widget)) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (hasSelectedText(widget)) {
|
|
84
126
|
widget.deleteSelection();
|
|
85
127
|
}
|
|
86
128
|
|
|
87
|
-
widget.
|
|
129
|
+
const position = widget.getPosition();
|
|
130
|
+
const newPosition = widget.insertText(text, text.length, position);
|
|
131
|
+
widget.setPosition(newPosition);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const insertEditableText = (widget: EditableTarget, text: string, kind: InsertKind): void => {
|
|
135
|
+
if (widget instanceof Gtk.TextView) {
|
|
136
|
+
insertTextViewText(widget, text, kind);
|
|
137
|
+
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const target = getDelegateText(widget);
|
|
142
|
+
|
|
143
|
+
if (target === null) {
|
|
144
|
+
insertPlainText(widget, text);
|
|
145
|
+
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
insertDelegateText(target, text);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const deleteAllText = (widget: EditableTarget): void => {
|
|
153
|
+
if (widget instanceof Gtk.TextView) {
|
|
154
|
+
const buffer = widget.getBuffer();
|
|
155
|
+
buffer.deleteInteractive(buffer.getStartIter(), buffer.getEndIter(), widget.getEditable());
|
|
156
|
+
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
widget.deleteText(0, -1);
|
|
88
161
|
};
|
|
89
162
|
|
|
90
163
|
const applyInitialSelection = (widget: EditableTarget, options: TypeOptions): void => {
|
|
@@ -96,12 +169,13 @@ const applyInitialSelection = (widget: EditableTarget, options: TypeOptions): vo
|
|
|
96
169
|
const end = options.initialSelectionEnd ?? start;
|
|
97
170
|
|
|
98
171
|
if (widget instanceof Gtk.TextView) {
|
|
99
|
-
|
|
172
|
+
const buffer = widget.getBuffer();
|
|
173
|
+
buffer.selectRange(buffer.getIterAtOffset(start), buffer.getIterAtOffset(end));
|
|
100
174
|
|
|
101
175
|
return;
|
|
102
176
|
}
|
|
103
177
|
|
|
104
|
-
|
|
178
|
+
widget.selectRegion(start, end);
|
|
105
179
|
};
|
|
106
180
|
|
|
107
181
|
const writeClipboardText = (widget: Gtk.Widget, text: string): void => {
|
|
@@ -130,23 +204,24 @@ const runEditableEvent = (
|
|
|
130
204
|
});
|
|
131
205
|
|
|
132
206
|
/**
|
|
133
|
-
* Focuses the widget unless `shouldFocus` is false, applies any initial selection, and inserts the
|
|
134
|
-
* at the cursor
|
|
207
|
+
* Focuses the widget unless `shouldFocus` is false, applies any initial selection, and inserts the
|
|
208
|
+
* text at the cursor, deleting the text the widget has selected first, the way typing over a
|
|
209
|
+
* selection does in GTK4. Focusing works the way clicking into the widget does, leaving its text
|
|
210
|
+
* unselected: a `Gtk.Editable` takes its caret to the end of its text, a `Gtk.TextView` keeps the
|
|
211
|
+
* caret where it stands, and the replaced text stays on the widget's undo stack either way.
|
|
135
212
|
*
|
|
136
213
|
* @throws When the widget is neither a Gtk.Editable nor a Gtk.TextView.
|
|
137
214
|
*/
|
|
138
215
|
const type = (widget: Gtk.Widget, text: string, options?: TypeOptions): Promise<void> =>
|
|
139
216
|
runEditableEvent(widget, "Cannot type into element", (editable) => {
|
|
140
217
|
if (options?.shouldFocus ?? true) {
|
|
141
|
-
editable
|
|
218
|
+
focusEditable(editable);
|
|
142
219
|
}
|
|
143
220
|
|
|
144
221
|
applyInitialSelection(editable, options ?? {});
|
|
145
|
-
insertEditableText(editable, text);
|
|
222
|
+
insertEditableText(editable, text, "typing");
|
|
146
223
|
});
|
|
147
224
|
|
|
148
|
-
const isWidgetEditable = (widget: Gtk.Widget): boolean => callBooleanGetter(widget, "getEditable") ?? true;
|
|
149
|
-
|
|
150
225
|
const hasNonEditableChar = (iter: Gtk.TextIter, isDefaultEditable: boolean): boolean => {
|
|
151
226
|
while (!iter.isEnd()) {
|
|
152
227
|
if (!iter.editable(isDefaultEditable)) {
|
|
@@ -205,7 +280,7 @@ const didDeleteAllText = (widget: EditableTarget): boolean => {
|
|
|
205
280
|
|
|
206
281
|
const clearEditable = (editable: EditableTarget): void => {
|
|
207
282
|
requireClearableText(editable);
|
|
208
|
-
editable
|
|
283
|
+
focusEditable(editable);
|
|
209
284
|
|
|
210
285
|
if (readEditableText(editable) === "") {
|
|
211
286
|
return;
|
|
@@ -251,7 +326,7 @@ const cut = (widget: Gtk.Widget): Promise<void> =>
|
|
|
251
326
|
|
|
252
327
|
/**
|
|
253
328
|
* Inserts the given text at an editable widget's cursor, reading the clipboard instead when no text
|
|
254
|
-
* is given.
|
|
329
|
+
* is given, and deleting the text the widget has selected first, the way a paste does in GTK4.
|
|
255
330
|
*
|
|
256
331
|
* @throws When the widget is neither a Gtk.Editable nor a Gtk.TextView.
|
|
257
332
|
*/
|
|
@@ -263,7 +338,7 @@ const paste = async (widget: Gtk.Widget, text?: string): Promise<void> => {
|
|
|
263
338
|
const content = text ?? (await widget.getClipboard().readTextAsync(null)) ?? "";
|
|
264
339
|
|
|
265
340
|
await wrapEvent(widget, () => {
|
|
266
|
-
insertEditableText(widget, content);
|
|
341
|
+
insertEditableText(widget, content, "pasting");
|
|
267
342
|
});
|
|
268
343
|
};
|
|
269
344
|
|
package/src/wait-for.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { WaitForOptions } from "./types.js";
|
|
|
3
3
|
import { runWithActEnvironment } from "./act.js";
|
|
4
4
|
import { getConfig } from "./config.js";
|
|
5
5
|
import { timeoutError } from "./errors.js";
|
|
6
|
+
import { advanceFakeClock, delay, now } from "./timers.js";
|
|
6
7
|
|
|
7
8
|
type PollResult<T> = { status: "resolved"; value: T } | { status: "timedout"; lastError: Error | null };
|
|
8
9
|
/** Widgets watched for removal; null counts as already removed. */
|
|
@@ -13,8 +14,6 @@ type ElementOrCallback = Gtk.Widget | Gtk.Widget[] | (() => RemovalTarget);
|
|
|
13
14
|
const DEFAULT_INTERVAL = 50;
|
|
14
15
|
const ELEMENT_NOT_REMOVED = new Error("Element not yet removed");
|
|
15
16
|
|
|
16
|
-
const delay = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
|
|
17
|
-
|
|
18
17
|
const copyStackTrace = (target: Error, source: Error): void => {
|
|
19
18
|
const { stack } = source;
|
|
20
19
|
|
|
@@ -38,10 +37,10 @@ const pollUntilSuccess = async <T>(
|
|
|
38
37
|
timeout: number,
|
|
39
38
|
interval: number,
|
|
40
39
|
): Promise<PollResult<T>> => {
|
|
41
|
-
const startTime =
|
|
40
|
+
const startTime = now();
|
|
42
41
|
let lastError: Error | null = null;
|
|
43
42
|
|
|
44
|
-
while (
|
|
43
|
+
while (now() - startTime < timeout) {
|
|
45
44
|
try {
|
|
46
45
|
const result = await callback();
|
|
47
46
|
await delay(0);
|
|
@@ -49,6 +48,7 @@ const pollUntilSuccess = async <T>(
|
|
|
49
48
|
return { status: "resolved", value: result };
|
|
50
49
|
} catch (error) {
|
|
51
50
|
lastError = error as Error;
|
|
51
|
+
await advanceFakeClock(interval);
|
|
52
52
|
await delay(interval);
|
|
53
53
|
}
|
|
54
54
|
}
|
package/src/widget-getters.ts
CHANGED
|
@@ -1,8 +1,53 @@
|
|
|
1
|
+
import type * as GObject from "@gtkx/gi/gobject";
|
|
1
2
|
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
-
import { getInstanceType, typeName } from "@gtkx/runtime";
|
|
3
|
+
import { getClassType, getInstanceType, TYPE_INVALID, typeName } from "@gtkx/runtime";
|
|
4
|
+
import { resolveWrapperClass } from "@gtkx/runtime/internal";
|
|
5
|
+
import { type AnyClass, walkClassChain } from "@gtkx/utils";
|
|
6
|
+
|
|
7
|
+
const UNNAMED_TYPE_TAG = "Object";
|
|
3
8
|
|
|
4
9
|
const getWidgetMethod = (widget: Gtk.Widget, name: string): unknown => Reflect.get(widget, name);
|
|
5
10
|
const getWidgetTypeName = (widget: Gtk.Widget): string | null => typeName(getInstanceType(widget));
|
|
11
|
+
const isClass = (value: unknown): value is AnyClass => typeof value === "function";
|
|
12
|
+
|
|
13
|
+
const nonEmpty = (value: unknown): string | undefined =>
|
|
14
|
+
typeof value === "string" && value.length > 0 ? value : undefined;
|
|
15
|
+
|
|
16
|
+
const getObjectClass = (object: GObject.Object): AnyClass | null => {
|
|
17
|
+
const cls: unknown = object.constructor;
|
|
18
|
+
|
|
19
|
+
return isClass(cls) ? cls : null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const getNearestClassName = (object: GObject.Object): string | undefined =>
|
|
23
|
+
walkClassChain(getObjectClass(object), (ancestor) => nonEmpty(ancestor.name));
|
|
24
|
+
|
|
25
|
+
const getUntypedClassName = (object: GObject.Object): string | undefined => {
|
|
26
|
+
const cls = getObjectClass(object);
|
|
27
|
+
|
|
28
|
+
return cls !== null && getClassType(cls) === TYPE_INVALID ? nonEmpty(cls.name) : undefined;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const getExactWrapperName = (type: bigint): string | undefined => {
|
|
32
|
+
const wrapper = resolveWrapperClass(type);
|
|
33
|
+
|
|
34
|
+
return wrapper !== null && getClassType(wrapper) === type ? nonEmpty(wrapper.name) : undefined;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const getTypeTag = (object: GObject.Object): string => {
|
|
38
|
+
const type = getInstanceType(object);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
getUntypedClassName(object) ??
|
|
42
|
+
getExactWrapperName(type) ??
|
|
43
|
+
nonEmpty(typeName(type)) ??
|
|
44
|
+
getNearestClassName(object) ??
|
|
45
|
+
UNNAMED_TYPE_TAG
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const isDefaultWidgetName = (widget: Gtk.Widget, name: string): boolean =>
|
|
50
|
+
name.length === 0 || name === getWidgetTypeName(widget);
|
|
6
51
|
|
|
7
52
|
const hasWidgetMethod = (widget: Gtk.Widget, name: string): boolean =>
|
|
8
53
|
typeof getWidgetMethod(widget, name) === "function";
|
|
@@ -29,4 +74,12 @@ const callStringGetter = (widget: Gtk.Widget, method: string): string | null =>
|
|
|
29
74
|
return typeof value === "string" ? value : null;
|
|
30
75
|
};
|
|
31
76
|
|
|
32
|
-
export {
|
|
77
|
+
export {
|
|
78
|
+
callBooleanGetter,
|
|
79
|
+
callStringGetter,
|
|
80
|
+
getCallableMethod,
|
|
81
|
+
getTypeTag,
|
|
82
|
+
getWidgetTypeName,
|
|
83
|
+
hasWidgetMethod,
|
|
84
|
+
isDefaultWidgetName,
|
|
85
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
|
|
3
|
+
const WINDOW_NOT_ALLOCATED = "it was never allocated a size";
|
|
4
|
+
const WINDOW_NOT_ACTIVATED = "it never became active";
|
|
5
|
+
const NO_WINDOW_ACTIVATED = "no window of the application ever became active";
|
|
6
|
+
|
|
7
|
+
const canDisplayDeliverActivation = (window: Gtk.Window): boolean => window.getDisplay().getDefaultSeat() !== null;
|
|
8
|
+
const isWindow = (widget: Gtk.Widget): widget is Gtk.Window => widget instanceof Gtk.Window;
|
|
9
|
+
|
|
10
|
+
const mappedToplevels = (): Gtk.Window[] =>
|
|
11
|
+
Gtk.Window.listToplevels().filter((widget) => isWindow(widget)).filter((window) => window.getMapped());
|
|
12
|
+
|
|
13
|
+
const isWindowAllocated = (window: Gtk.Window): boolean => {
|
|
14
|
+
const [isComputed, allocation] = window.computeBounds(window);
|
|
15
|
+
|
|
16
|
+
return isComputed && allocation.getWidth() > 0;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const activeToplevel = (): Gtk.Window | null => mappedToplevels().find((toplevel) => toplevel.isActive()) ?? null;
|
|
20
|
+
|
|
21
|
+
const isWindowActivated = (window: Gtk.Window): boolean =>
|
|
22
|
+
!canDisplayDeliverActivation(window) || window.isActive();
|
|
23
|
+
|
|
24
|
+
const isApplicationActivated = (window: Gtk.Window): boolean =>
|
|
25
|
+
!canDisplayDeliverActivation(window) || activeToplevel() !== null;
|
|
26
|
+
|
|
27
|
+
const isTransientDescendant = (window: Gtk.Window, ancestor: Gtk.Window): boolean => {
|
|
28
|
+
for (let parent = window.getTransientFor(); parent !== null; parent = parent.getTransientFor()) {
|
|
29
|
+
if (parent === ancestor) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return false;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const isModalGrabHeldOver = (modal: Gtk.Window, window: Gtk.Window): boolean =>
|
|
38
|
+
modal.getModal() &&
|
|
39
|
+
modal !== window &&
|
|
40
|
+
modal.getGroup() === window.getGroup() &&
|
|
41
|
+
!isTransientDescendant(window, modal);
|
|
42
|
+
|
|
43
|
+
const isWindowBlockedByModal = (window: Gtk.Window): boolean =>
|
|
44
|
+
mappedToplevels().some((toplevel) => isModalGrabHeldOver(toplevel, window));
|
|
45
|
+
|
|
46
|
+
const findPresentedWindowFailure = (window: Gtk.Window): string | null => {
|
|
47
|
+
if (!isWindowAllocated(window)) {
|
|
48
|
+
return WINDOW_NOT_ALLOCATED;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return isWindowActivated(window) ? null : WINDOW_NOT_ACTIVATED;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const findRenderedWindowFailure = (window: Gtk.Window): string | null => {
|
|
55
|
+
if (!isWindowAllocated(window)) {
|
|
56
|
+
return WINDOW_NOT_ALLOCATED;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return isApplicationActivated(window) ? null : NO_WINDOW_ACTIVATED;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export {
|
|
63
|
+
activeToplevel,
|
|
64
|
+
findPresentedWindowFailure,
|
|
65
|
+
findRenderedWindowFailure,
|
|
66
|
+
isWindowActivated,
|
|
67
|
+
isWindowAllocated,
|
|
68
|
+
isWindowBlockedByModal,
|
|
69
|
+
mappedToplevels,
|
|
70
|
+
};
|