@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.
Files changed (128) hide show
  1. package/README.md +1 -1
  2. package/dist/act.d.ts +1 -3
  3. package/dist/act.d.ts.map +1 -1
  4. package/dist/act.js +1 -1
  5. package/dist/act.js.map +1 -1
  6. package/dist/build-queries.d.ts +1 -1
  7. package/dist/build-queries.js +1 -1
  8. package/dist/build-queries.js.map +1 -1
  9. package/dist/config.d.ts +8 -1
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +1 -0
  12. package/dist/config.js.map +1 -1
  13. package/dist/frame-sync.d.ts +2 -2
  14. package/dist/frame-sync.d.ts.map +1 -1
  15. package/dist/frame-sync.js +10 -10
  16. package/dist/frame-sync.js.map +1 -1
  17. package/dist/harness-window.d.ts +2 -1
  18. package/dist/harness-window.d.ts.map +1 -1
  19. package/dist/harness-window.js +12 -1
  20. package/dist/harness-window.js.map +1 -1
  21. package/dist/internal.d.ts +1 -0
  22. package/dist/internal.d.ts.map +1 -1
  23. package/dist/internal.js +1 -0
  24. package/dist/internal.js.map +1 -1
  25. package/dist/matchers.d.ts.map +1 -1
  26. package/dist/matchers.js +22 -2
  27. package/dist/matchers.js.map +1 -1
  28. package/dist/pretty-widget.d.ts.map +1 -1
  29. package/dist/pretty-widget.js +2 -1
  30. package/dist/pretty-widget.js.map +1 -1
  31. package/dist/production.d.ts.map +1 -1
  32. package/dist/production.js +2 -2
  33. package/dist/production.js.map +1 -1
  34. package/dist/render.d.ts +12 -1
  35. package/dist/render.d.ts.map +1 -1
  36. package/dist/render.js +45 -11
  37. package/dist/render.js.map +1 -1
  38. package/dist/role-helpers.d.ts.map +1 -1
  39. package/dist/role-helpers.js +2 -1
  40. package/dist/role-helpers.js.map +1 -1
  41. package/dist/screenshot.d.ts.map +1 -1
  42. package/dist/screenshot.js +8 -14
  43. package/dist/screenshot.js.map +1 -1
  44. package/dist/timers.d.ts +9 -0
  45. package/dist/timers.d.ts.map +1 -0
  46. package/dist/timers.js +21 -0
  47. package/dist/timers.js.map +1 -0
  48. package/dist/traversal.d.ts +4 -1
  49. package/dist/traversal.d.ts.map +1 -1
  50. package/dist/traversal.js +16 -1
  51. package/dist/traversal.js.map +1 -1
  52. package/dist/types.d.ts +6 -1
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/types.js.map +1 -1
  55. package/dist/user-event/adjustment.d.ts.map +1 -1
  56. package/dist/user-event/adjustment.js +2 -1
  57. package/dist/user-event/adjustment.js.map +1 -1
  58. package/dist/user-event/click.d.ts +5 -4
  59. package/dist/user-event/click.d.ts.map +1 -1
  60. package/dist/user-event/click.js +5 -4
  61. package/dist/user-event/click.js.map +1 -1
  62. package/dist/user-event/column-header-click.d.ts.map +1 -1
  63. package/dist/user-event/column-header-click.js +2 -12
  64. package/dist/user-event/column-header-click.js.map +1 -1
  65. package/dist/user-event/event-wrapper.d.ts.map +1 -1
  66. package/dist/user-event/event-wrapper.js +34 -21
  67. package/dist/user-event/event-wrapper.js.map +1 -1
  68. package/dist/user-event/index.d.ts +36 -10
  69. package/dist/user-event/index.d.ts.map +1 -1
  70. package/dist/user-event/index.js +4 -3
  71. package/dist/user-event/index.js.map +1 -1
  72. package/dist/user-event/keyboard.d.ts.map +1 -1
  73. package/dist/user-event/keyboard.js +93 -38
  74. package/dist/user-event/keyboard.js.map +1 -1
  75. package/dist/user-event/list-row-click.d.ts.map +1 -1
  76. package/dist/user-event/list-row-click.js +4 -5
  77. package/dist/user-event/list-row-click.js.map +1 -1
  78. package/dist/user-event/native-click.d.ts.map +1 -1
  79. package/dist/user-event/native-click.js +4 -0
  80. package/dist/user-event/native-click.js.map +1 -1
  81. package/dist/user-event/notebook-tab-click.d.ts +5 -0
  82. package/dist/user-event/notebook-tab-click.d.ts.map +1 -0
  83. package/dist/user-event/notebook-tab-click.js +39 -0
  84. package/dist/user-event/notebook-tab-click.js.map +1 -0
  85. package/dist/user-event/text.d.ts +6 -3
  86. package/dist/user-event/text.d.ts.map +1 -1
  87. package/dist/user-event/text.js +88 -33
  88. package/dist/user-event/text.js.map +1 -1
  89. package/dist/wait-for.d.ts.map +1 -1
  90. package/dist/wait-for.js +4 -3
  91. package/dist/wait-for.js.map +1 -1
  92. package/dist/widget-getters.d.ts +4 -1
  93. package/dist/widget-getters.d.ts.map +1 -1
  94. package/dist/widget-getters.js +29 -2
  95. package/dist/widget-getters.js.map +1 -1
  96. package/dist/window-state.d.ts +10 -0
  97. package/dist/window-state.d.ts.map +1 -0
  98. package/dist/window-state.js +41 -0
  99. package/dist/window-state.js.map +1 -0
  100. package/package.json +15 -7
  101. package/src/act.ts +1 -1
  102. package/src/build-queries.ts +1 -1
  103. package/src/config.ts +10 -1
  104. package/src/frame-sync.ts +15 -10
  105. package/src/harness-window.ts +15 -1
  106. package/src/internal.ts +1 -0
  107. package/src/matchers.ts +29 -2
  108. package/src/pretty-widget.ts +2 -1
  109. package/src/production.ts +2 -3
  110. package/src/render.tsx +67 -12
  111. package/src/role-helpers.ts +2 -1
  112. package/src/screenshot.ts +10 -17
  113. package/src/timers.ts +31 -0
  114. package/src/traversal.ts +22 -0
  115. package/src/types.ts +6 -1
  116. package/src/user-event/adjustment.ts +2 -1
  117. package/src/user-event/click.ts +5 -4
  118. package/src/user-event/column-header-click.ts +2 -16
  119. package/src/user-event/event-wrapper.ts +41 -25
  120. package/src/user-event/index.ts +40 -13
  121. package/src/user-event/keyboard.ts +124 -54
  122. package/src/user-event/list-row-click.ts +4 -7
  123. package/src/user-event/native-click.ts +5 -0
  124. package/src/user-event/notebook-tab-click.ts +57 -0
  125. package/src/user-event/text.ts +113 -38
  126. package/src/wait-for.ts +4 -4
  127. package/src/widget-getters.ts +55 -2
  128. package/src/window-state.ts +70 -0
@@ -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 insertEditableText = (widget: EditableTarget, text: string): void => {
49
+ const deleteSelection = (widget: EditableTarget): void => {
29
50
  if (widget instanceof Gtk.TextView) {
30
- widget.emit("insert-at-cursor", text);
51
+ widget.getBuffer().deleteSelection(false, true);
31
52
 
32
53
  return;
33
54
  }
34
55
 
35
- const target = getEditableDelegate(widget) ?? widget;
56
+ widget.deleteSelection();
57
+ };
36
58
 
37
- if (target instanceof Gtk.Text) {
38
- target.emit("insert-at-cursor", text);
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
- const position = widget.getPosition();
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 readSelection = (widget: EditableTarget): string => getWidgetSelection(widget) ?? "";
49
-
50
- const deleteSelection = (widget: EditableTarget): void => {
72
+ const focusEditable = (widget: EditableTarget): void => {
51
73
  if (widget instanceof Gtk.TextView) {
52
- widget.getBuffer().deleteSelection(false, true);
74
+ widget.grabFocus();
53
75
 
54
76
  return;
55
77
  }
56
78
 
57
- widget.deleteSelection();
58
- };
79
+ const target = getDelegateText(widget);
59
80
 
60
- const deleteAllText = (widget: EditableTarget): void => {
61
- if (widget instanceof Gtk.TextView) {
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
- widget.deleteText(0, -1);
87
+ target.grabFocusWithoutSelecting();
88
+
89
+ if (!hasSelectedText(widget)) {
90
+ widget.setPosition(TEXT_END_POSITION);
91
+ }
69
92
  };
70
93
 
71
- const applyTextViewSelection = (widget: Gtk.TextView, start: number, end: number): void => {
94
+ const insertTextViewText = (widget: Gtk.TextView, text: string, kind: InsertKind): void => {
72
95
  const buffer = widget.getBuffer();
73
- buffer.selectRange(buffer.getIterAtOffset(start), buffer.getIterAtOffset(end));
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
- if (end !== start) {
76
- deleteSelection(widget);
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 applyEditableSelection = (widget: Gtk.Editable, start: number, end: number): void => {
81
- widget.selectRegion(start, end);
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
- if (end !== start) {
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.setPosition(start);
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
- applyTextViewSelection(widget, start, end);
172
+ const buffer = widget.getBuffer();
173
+ buffer.selectRange(buffer.getIterAtOffset(start), buffer.getIterAtOffset(end));
100
174
 
101
175
  return;
102
176
  }
103
177
 
104
- applyEditableSelection(widget, start, end);
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 text
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.grabFocus();
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.grabFocus();
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 = Date.now();
40
+ const startTime = now();
42
41
  let lastError: Error | null = null;
43
42
 
44
- while (Date.now() - startTime < timeout) {
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
  }
@@ -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 { callBooleanGetter, callStringGetter, getCallableMethod, getWidgetTypeName, hasWidgetMethod };
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
+ };