@gtkx/testing 2.0.0-beta.1 → 2.0.0-beta.10

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 (112) hide show
  1. package/README.md +43 -40
  2. package/dist/accessible-native.d.ts +1 -1
  3. package/dist/accessible-native.d.ts.map +1 -1
  4. package/dist/accessible-native.js +29 -8
  5. package/dist/accessible-native.js.map +1 -1
  6. package/dist/build-queries.d.ts +9 -9
  7. package/dist/build-queries.d.ts.map +1 -1
  8. package/dist/build-queries.js +2 -1
  9. package/dist/build-queries.js.map +1 -1
  10. package/dist/fire-event.d.ts +3 -3
  11. package/dist/fire-event.d.ts.map +1 -1
  12. package/dist/fire-event.js +14 -4
  13. package/dist/fire-event.js.map +1 -1
  14. package/dist/matchers.d.ts +52 -56
  15. package/dist/matchers.d.ts.map +1 -1
  16. package/dist/matchers.js +4 -16
  17. package/dist/matchers.js.map +1 -1
  18. package/dist/queries.d.ts +7 -7
  19. package/dist/queries.d.ts.map +1 -1
  20. package/dist/queries.js +18 -10
  21. package/dist/queries.js.map +1 -1
  22. package/dist/query-helpers.d.ts +2 -2
  23. package/dist/query-helpers.d.ts.map +1 -1
  24. package/dist/query-helpers.js +10 -3
  25. package/dist/query-helpers.js.map +1 -1
  26. package/dist/render.d.ts.map +1 -1
  27. package/dist/render.js +4 -0
  28. package/dist/render.js.map +1 -1
  29. package/dist/role-helpers.d.ts +1 -1
  30. package/dist/role-helpers.d.ts.map +1 -1
  31. package/dist/role-helpers.js +2 -1
  32. package/dist/role-helpers.js.map +1 -1
  33. package/dist/screenshot.d.ts +1 -1
  34. package/dist/screenshot.d.ts.map +1 -1
  35. package/dist/screenshot.js +25 -5
  36. package/dist/screenshot.js.map +1 -1
  37. package/dist/suggestions.d.ts +1 -1
  38. package/dist/suggestions.d.ts.map +1 -1
  39. package/dist/suggestions.js +3 -1
  40. package/dist/suggestions.js.map +1 -1
  41. package/dist/traversal.d.ts +4 -4
  42. package/dist/traversal.d.ts.map +1 -1
  43. package/dist/traversal.js +25 -7
  44. package/dist/traversal.js.map +1 -1
  45. package/dist/types.d.ts +10 -10
  46. package/dist/types.d.ts.map +1 -1
  47. package/dist/types.js.map +1 -1
  48. package/dist/user-event/click.d.ts +17 -4
  49. package/dist/user-event/click.d.ts.map +1 -1
  50. package/dist/user-event/click.js +28 -9
  51. package/dist/user-event/click.js.map +1 -1
  52. package/dist/user-event/controller.d.ts +5 -5
  53. package/dist/user-event/controller.d.ts.map +1 -1
  54. package/dist/user-event/controller.js +5 -2
  55. package/dist/user-event/controller.js.map +1 -1
  56. package/dist/user-event/index.d.ts +22 -22
  57. package/dist/user-event/index.d.ts.map +1 -1
  58. package/dist/user-event/index.js +28 -25
  59. package/dist/user-event/index.js.map +1 -1
  60. package/dist/user-event/keyboard.d.ts.map +1 -1
  61. package/dist/user-event/keyboard.js +23 -2
  62. package/dist/user-event/keyboard.js.map +1 -1
  63. package/dist/user-event/list-row-click.d.ts +1 -1
  64. package/dist/user-event/list-row-click.d.ts.map +1 -1
  65. package/dist/user-event/list-row-click.js +2 -1
  66. package/dist/user-event/list-row-click.js.map +1 -1
  67. package/dist/user-event/pointer.d.ts.map +1 -1
  68. package/dist/user-event/pointer.js +9 -11
  69. package/dist/user-event/pointer.js.map +1 -1
  70. package/dist/user-event/selection.d.ts +2 -2
  71. package/dist/user-event/selection.d.ts.map +1 -1
  72. package/dist/user-event/selection.js +8 -2
  73. package/dist/user-event/selection.js.map +1 -1
  74. package/dist/wait-for.d.ts +2 -2
  75. package/dist/wait-for.d.ts.map +1 -1
  76. package/dist/wait-for.js +6 -4
  77. package/dist/wait-for.js.map +1 -1
  78. package/dist/widget-accessible-properties.d.ts +4 -3
  79. package/dist/widget-accessible-properties.d.ts.map +1 -1
  80. package/dist/widget-accessible-properties.js +24 -17
  81. package/dist/widget-accessible-properties.js.map +1 -1
  82. package/dist/widget-getters.d.ts +7 -9
  83. package/dist/widget-getters.d.ts.map +1 -1
  84. package/dist/widget-getters.js.map +1 -1
  85. package/dist/widget-target.d.ts +4 -0
  86. package/dist/widget-target.d.ts.map +1 -0
  87. package/dist/widget-target.js +9 -0
  88. package/dist/widget-target.js.map +1 -0
  89. package/package.json +13 -10
  90. package/src/accessible-native.ts +45 -9
  91. package/src/build-queries.ts +60 -30
  92. package/src/fire-event.ts +32 -8
  93. package/src/matchers.ts +63 -85
  94. package/src/queries.ts +64 -30
  95. package/src/query-helpers.ts +17 -9
  96. package/src/render.tsx +7 -2
  97. package/src/role-helpers.ts +3 -1
  98. package/src/screenshot.ts +48 -5
  99. package/src/suggestions.ts +3 -1
  100. package/src/traversal.ts +35 -11
  101. package/src/types.ts +14 -10
  102. package/src/user-event/click.ts +37 -17
  103. package/src/user-event/controller.ts +8 -5
  104. package/src/user-event/index.ts +133 -47
  105. package/src/user-event/keyboard.ts +32 -3
  106. package/src/user-event/list-row-click.ts +2 -2
  107. package/src/user-event/pointer.ts +11 -15
  108. package/src/user-event/selection.ts +12 -4
  109. package/src/wait-for.ts +9 -6
  110. package/src/widget-accessible-properties.ts +39 -25
  111. package/src/widget-getters.ts +11 -13
  112. package/src/widget-target.ts +11 -0
@@ -1,8 +1,19 @@
1
1
  import type * as Gtk from "@gtkx/gi/gtk";
2
2
  import { delay } from "../timers.js";
3
+ import { requireWidget } from "../widget-target.js";
3
4
  import { scroll, slide } from "./adjustment.js";
4
5
  import { click, dblClick, tripleClick } from "./click.js";
5
- import { drag, dragAndDrop, drop, hover, longPress, rotate, swipe, unhover, zoom } from "./gesture.js";
6
+ import {
7
+ drag,
8
+ dragAndDrop,
9
+ drop,
10
+ hover,
11
+ longPress,
12
+ rotate,
13
+ swipe,
14
+ unhover,
15
+ zoom,
16
+ } from "./gesture.js";
6
17
  import { keyboard, tab } from "./keyboard.js";
7
18
  import { pointer, type PointerInput } from "./pointer.js";
8
19
  import { deselectOptions, selectOptions } from "./selection.js";
@@ -20,55 +31,126 @@ type UserEvent = {
20
31
  /** Creates an instance with shared keyboard and pointer state. */
21
32
  setup: (options?: UserEventOptions) => UserEvent;
22
33
  /** Activates or presses and releases a widget. */
23
- click: typeof click;
34
+ click: (
35
+ widget: Gtk.Accessible,
36
+ ...args: Parameters<typeof click> extends [Gtk.Widget, ...infer Args] ? Args : never
37
+ ) => Promise<void>;
24
38
  /** Delivers a double-click gesture. */
25
- dblClick: typeof dblClick;
39
+ dblClick: (
40
+ widget: Gtk.Accessible,
41
+ ...args: Parameters<typeof dblClick> extends [Gtk.Widget, ...infer Args] ? Args : never
42
+ ) => Promise<void>;
26
43
  /** Delivers a triple-click gesture. */
27
- tripleClick: typeof tripleClick;
44
+ tripleClick: (
45
+ widget: Gtk.Accessible,
46
+ ...args: Parameters<typeof tripleClick> extends [Gtk.Widget, ...infer Args] ? Args : never
47
+ ) => Promise<void>;
28
48
  /** Moves focus forward or backward within the root. */
29
- tab: typeof tab;
49
+ tab: (
50
+ widget: Gtk.Accessible,
51
+ ...args: Parameters<typeof tab> extends [Gtk.Widget, ...infer Args] ? Args : never
52
+ ) => Promise<void>;
30
53
  /** Types into an editable widget. */
31
- type: typeof type;
54
+ type: (
55
+ widget: Gtk.Accessible,
56
+ ...args: Parameters<typeof type> extends [Gtk.Widget, ...infer Args] ? Args : never
57
+ ) => Promise<void>;
32
58
  /** Deletes all text from an editable widget. */
33
- clear: typeof clear;
59
+ clear: (
60
+ widget: Gtk.Accessible,
61
+ ...args: Parameters<typeof clear> extends [Gtk.Widget, ...infer Args] ? Args : never
62
+ ) => Promise<void>;
34
63
  /** Copies the current selection. */
35
- copy: typeof copy;
64
+ copy: (
65
+ widget: Gtk.Accessible,
66
+ ...args: Parameters<typeof copy> extends [Gtk.Widget, ...infer Args] ? Args : never
67
+ ) => Promise<void>;
36
68
  /** Cuts the current selection. */
37
- cut: typeof cut;
69
+ cut: (
70
+ widget: Gtk.Accessible,
71
+ ...args: Parameters<typeof cut> extends [Gtk.Widget, ...infer Args] ? Args : never
72
+ ) => Promise<void>;
38
73
  /** Pastes text or clipboard contents. */
39
- paste: typeof paste;
74
+ paste: (
75
+ widget: Gtk.Accessible,
76
+ ...args: Parameters<typeof paste> extends [Gtk.Widget, ...infer Args] ? Args : never
77
+ ) => Promise<void>;
40
78
  /** Selects positions without activating them. */
41
79
  selectOptions: typeof selectOptions;
42
80
  /** Unselects positions without activating them. */
43
81
  deselectOptions: typeof deselectOptions;
44
82
  /** Emits a motion `enter` on the widget, adding a motion controller when it has none. */
45
- hover: typeof hover;
83
+ hover: (
84
+ widget: Gtk.Accessible,
85
+ ...args: Parameters<typeof hover> extends [Gtk.Widget, ...infer Args] ? Args : never
86
+ ) => Promise<void>;
46
87
  /** Emits a motion `leave` on the widget, adding a motion controller when it has none. */
47
- unhover: typeof unhover;
88
+ unhover: (
89
+ widget: Gtk.Accessible,
90
+ ...args: Parameters<typeof unhover> extends [Gtk.Widget, ...infer Args] ? Args : never
91
+ ) => Promise<void>;
48
92
  /** Emits `angle-changed` on the widget's rotate gestures. */
49
- rotate: typeof rotate;
93
+ rotate: (
94
+ widget: Gtk.Accessible,
95
+ ...args: Parameters<typeof rotate> extends [Gtk.Widget, ...infer Args] ? Args : never
96
+ ) => Promise<void>;
50
97
  /** Emits `scale-changed` on the widget's zoom gestures. */
51
- zoom: typeof zoom;
98
+ zoom: (
99
+ widget: Gtk.Accessible,
100
+ ...args: Parameters<typeof zoom> extends [Gtk.Widget, ...infer Args] ? Args : never
101
+ ) => Promise<void>;
52
102
  /** Emits `swipe` with the given velocity on the widget's swipe gestures. */
53
- swipe: typeof swipe;
103
+ swipe: (
104
+ widget: Gtk.Accessible,
105
+ ...args: Parameters<typeof swipe> extends [Gtk.Widget, ...infer Args] ? Args : never
106
+ ) => Promise<void>;
54
107
  /** Emits `pressed` at the given point on the widget's long-press gestures. */
55
- longPress: typeof longPress;
108
+ longPress: (
109
+ widget: Gtk.Accessible,
110
+ ...args: Parameters<typeof longPress> extends [Gtk.Widget, ...infer Args] ? Args : never
111
+ ) => Promise<void>;
56
112
  /** Runs a begin, update, and end sequence on the widget's drag gestures, ending at the given offset. */
57
- drag: typeof drag;
113
+ drag: (
114
+ widget: Gtk.Accessible,
115
+ ...args: Parameters<typeof drag> extends [Gtk.Widget, ...infer Args] ? Args : never
116
+ ) => Promise<void>;
58
117
  /** Emits `drop` with the given content on the widget's drop targets. */
59
- drop: typeof drop;
118
+ drop: (
119
+ widget: Gtk.Accessible,
120
+ ...args: Parameters<typeof drop> extends [Gtk.Widget, ...infer Args] ? Args : never
121
+ ) => Promise<void>;
60
122
  /** Requires a drag source on the source widget, then emits `drop` with the content on the target's drop targets. */
61
- dragAndDrop: typeof dragAndDrop;
123
+ dragAndDrop: (
124
+ source: Gtk.Accessible,
125
+ target: Gtk.Accessible,
126
+ ...args: Parameters<typeof dragAndDrop> extends [Gtk.Widget, Gtk.Widget, ...infer Args] ? Args : never
127
+ ) => Promise<void>;
62
128
  /** Emits a jump `change-value` on a Gtk.Range so it moves to the given value. */
63
- slide: typeof slide;
129
+ slide: (
130
+ widget: Gtk.Accessible,
131
+ ...args: Parameters<typeof slide> extends [Gtk.Widget, ...infer Args] ? Args : never
132
+ ) => Promise<void>;
64
133
  /** Adds the delta to the adjustments of the widget itself, or of its nearest scrollable ancestor. */
65
- scroll: typeof scroll;
134
+ scroll: (
135
+ widget: Gtk.Accessible,
136
+ ...args: Parameters<typeof scroll> extends [Gtk.Widget, ...infer Args] ? Args : never
137
+ ) => Promise<void>;
66
138
  /** Sends a key sequence through GTK's propagation chain. */
67
- keyboard: (widget: Gtk.Widget, input: string) => Promise<void>;
139
+ keyboard: (widget: Gtk.Accessible, input: string) => Promise<void>;
68
140
  /** Applies a pointer token while preserving held-button state. */
69
- pointer: (widget: Gtk.Widget, input: PointerInput) => Promise<void>;
141
+ pointer: (widget: Gtk.Accessible, input: PointerInput) => Promise<void>;
70
142
  };
71
143
 
144
+ const onWidget =
145
+ <Args extends unknown[]>(helper: (widget: Gtk.Widget, ...args: Args) => Promise<void>) =>
146
+ (target: Gtk.Accessible, ...args: Args): Promise<void> =>
147
+ helper(requireWidget(target), ...args);
148
+
149
+ const onWidgetPair =
150
+ <Args extends unknown[]>(helper: (source: Gtk.Widget, target: Gtk.Widget, ...args: Args) => Promise<void>) =>
151
+ (source: Gtk.Accessible, target: Gtk.Accessible, ...args: Args): Promise<void> =>
152
+ helper(requireWidget(source), requireWidget(target), ...args);
153
+
72
154
  /** User interactions that dispatch GTK events and gestures. */
73
155
  const userEvent: UserEvent = {
74
156
  ...createInstance(createInitialState(), {}),
@@ -93,30 +175,34 @@ function createInstance(state: UserEventState, options: UserEventOptions): UserE
93
175
 
94
176
  return {
95
177
  setup: (overrides?: UserEventOptions): UserEvent => createInstance(state, { ...options, ...overrides }),
96
- click: after(click),
97
- dblClick: after(dblClick),
98
- tripleClick: after(tripleClick),
99
- tab: after(tab),
100
- type: after(type),
101
- clear: after(clear),
102
- copy: after(copy),
103
- cut: after(cut),
104
- paste: after(paste),
105
- selectOptions: after(selectOptions),
106
- deselectOptions: after(deselectOptions),
107
- hover: after(hover),
108
- unhover: after(unhover),
109
- rotate: after(rotate),
110
- zoom: after(zoom),
111
- swipe: after(swipe),
112
- longPress: after(longPress),
113
- drag: after(drag),
114
- drop: after(drop),
115
- dragAndDrop: after(dragAndDrop),
116
- slide: after(slide),
117
- scroll: after(scroll),
118
- keyboard: after((widget: Gtk.Widget, input: string): Promise<void> => keyboard(state, widget, input)),
119
- pointer: after((widget: Gtk.Widget, input: PointerInput): Promise<void> => pointer(state, widget, input)),
178
+ click: after(onWidget(click)),
179
+ dblClick: after(onWidget(dblClick)),
180
+ tripleClick: after(onWidget(tripleClick)),
181
+ tab: after(onWidget(tab)),
182
+ type: after(onWidget(type)),
183
+ clear: after(onWidget(clear)),
184
+ copy: after(onWidget(copy)),
185
+ cut: after(onWidget(cut)),
186
+ paste: after(onWidget(paste)),
187
+ selectOptions: after(onWidget(selectOptions)),
188
+ deselectOptions: after(onWidget(deselectOptions)),
189
+ hover: after(onWidget(hover)),
190
+ unhover: after(onWidget(unhover)),
191
+ rotate: after(onWidget(rotate)),
192
+ zoom: after(onWidget(zoom)),
193
+ swipe: after(onWidget(swipe)),
194
+ longPress: after(onWidget(longPress)),
195
+ drag: after(onWidget(drag)),
196
+ drop: after(onWidget(drop)),
197
+ dragAndDrop: after(onWidgetPair(dragAndDrop)),
198
+ slide: after(onWidget(slide)),
199
+ scroll: after(onWidget(scroll)),
200
+ keyboard: after(
201
+ onWidget((widget: Gtk.Widget, input: string): Promise<void> => keyboard(state, widget, input)),
202
+ ),
203
+ pointer: after(
204
+ onWidget((widget: Gtk.Widget, input: PointerInput): Promise<void> => pointer(state, widget, input)),
205
+ ),
120
206
  };
121
207
  }
122
208
 
@@ -18,6 +18,7 @@ type KeyAction = { keyval: number; isPress: boolean };
18
18
  type ParseStep = { actions: KeyAction[]; next: number };
19
19
  type KeyStopController = Gtk.EventControllerKey | Gtk.ShortcutController;
20
20
  type KeyStop = { widget: Gtk.Widget; controller: KeyStopController };
21
+ type KeyTarget = { root: Gtk.Root | null; focus: Gtk.Widget | null };
21
22
 
22
23
  const KEY_MAP: Record<string, number> = {
23
24
  Enter: Gdk.KEY_Return,
@@ -366,9 +367,37 @@ const didStopHandlePress = (stop: KeyStop, keyval: number, modifiers: Gdk.Modifi
366
367
  return controller.emit("key-pressed", keyval, 0, modifiers);
367
368
  };
368
369
 
369
- const didHandlePress = (widget: Gtk.Widget, keyval: number, modifiers: Gdk.ModifierType): boolean =>
370
- didDispatchApplicationAccels(widget, keyval, modifiers) ||
371
- keyPropagationChain(widget).some((stop) => didStopHandlePress(stop, keyval, modifiers));
370
+ const keyTargetFor = (widget: Gtk.Widget): KeyTarget => {
371
+ const root = widget.getRoot();
372
+
373
+ return { root, focus: root instanceof Gtk.Window ? root.getFocus() : null };
374
+ };
375
+
376
+ const isKeyTargetInvalidated = (widget: Gtk.Widget, target: KeyTarget): boolean => {
377
+ const current = keyTargetFor(widget);
378
+
379
+ return current.root !== target.root || current.focus !== target.focus || !widget.getMapped();
380
+ };
381
+
382
+ const didHandlePress = (widget: Gtk.Widget, keyval: number, modifiers: Gdk.ModifierType): boolean => {
383
+ const target = keyTargetFor(widget);
384
+
385
+ if (didDispatchApplicationAccels(widget, keyval, modifiers)) {
386
+ return true;
387
+ }
388
+
389
+ if (isKeyTargetInvalidated(widget, target)) {
390
+ return true;
391
+ }
392
+
393
+ for (const stop of keyPropagationChain(widget)) {
394
+ if (didStopHandlePress(stop, keyval, modifiers) || isKeyTargetInvalidated(widget, target)) {
395
+ return true;
396
+ }
397
+ }
398
+
399
+ return false;
400
+ };
372
401
 
373
402
  const emitKeyReleased = (widget: Gtk.Widget, keyval: number, modifiers: Gdk.ModifierType): void => {
374
403
  for (const stop of keyPropagationChain(widget)) {
@@ -1,5 +1,5 @@
1
- import type * as Gtk from "@gtkx/gi/gtk";
2
1
  import * as GLib from "@gtkx/gi/glib";
2
+ import * as Gtk from "@gtkx/gi/gtk";
3
3
  import { ancestors } from "../traversal.js";
4
4
  import { callBooleanGetter, hasWidgetMethod } from "../widget-getters.js";
5
5
 
@@ -41,7 +41,7 @@ const applyRowPress = (row: Gtk.Widget, press: number): void => {
41
41
  selectRow(row);
42
42
 
43
43
  if (isRowActivatedByPress(row, press)) {
44
- row.activate();
44
+ Gtk.Widget.prototype.activate.call(row);
45
45
  }
46
46
  };
47
47
 
@@ -1,16 +1,15 @@
1
1
  import type * as Gtk from "@gtkx/gi/gtk";
2
- import type { NativeClick } from "./native-click.js";
3
2
  import type { UserEventState } from "./state.js";
4
- import { clickGestures, emitClickPhase, getAuthoredClickGestures } from "./click.js";
3
+ import { type DirectClick, directClickFor, emitSitePhase } from "./click.js";
5
4
  import { wrapEvent } from "./event-wrapper.js";
6
- import { isClickTransparent, nativeClickFor } from "./native-click.js";
5
+ import { isClickTransparent } from "./native-click.js";
7
6
 
8
7
  /**
9
8
  * A pointer action token: a full click (`click`, `[MouseLeft]`), a button press (`down`,
10
9
  * `[MouseLeft>]`), or a button release (`up`, `[/MouseLeft]`).
11
10
  */
12
11
  type PointerInput = "click" | "down" | "up" | "[MouseLeft]" | "[MouseLeft>]" | "[/MouseLeft]";
13
- type PointerTarget = { widget: Gtk.Widget; gestures: Gtk.GestureClick[]; native: NativeClick | null };
12
+ type PointerTarget = { widget: Gtk.Widget; click: DirectClick };
14
13
 
15
14
  const PRESS_INPUTS: Set<PointerInput> = new Set(["[MouseLeft>]", "down"]);
16
15
  const RELEASE_INPUTS: Set<PointerInput> = new Set(["[/MouseLeft]", "up"]);
@@ -32,26 +31,23 @@ const pointerWidgetFor = (widget: Gtk.Widget): Gtk.Widget => {
32
31
  return current;
33
32
  };
34
33
 
35
- const pointerGesturesFor = (widget: Gtk.Widget, native: NativeClick | null): Gtk.GestureClick[] =>
36
- native === null ? clickGestures(widget) : getAuthoredClickGestures(widget);
37
-
38
34
  const pointerTargetFor = (widget: Gtk.Widget): PointerTarget => {
39
35
  const target = pointerWidgetFor(widget);
40
- const native = nativeClickFor(target, true);
41
36
 
42
- return { widget: target, gestures: pointerGesturesFor(target, native), native };
37
+ return { widget: target, click: directClickFor(target) };
43
38
  };
44
39
 
45
- const pressPointer = ({ widget, gestures }: PointerTarget): void => {
46
- emitClickPhase(widget, gestures, 1, "pressed");
40
+ const pressPointer = ({ click }: PointerTarget): void => {
41
+ emitSitePhase(click.sites, 1, "pressed");
47
42
  };
48
43
 
49
- const releasePointer = ({ widget, gestures, native }: PointerTarget): void => {
50
- emitClickPhase(widget, gestures, 1, "released");
51
- native?.(widget, 1);
44
+ const releasePointer = ({ widget, click }: PointerTarget): void => {
45
+ emitSitePhase(click.sites, 1, "released");
46
+ click.outcome?.(widget, 1);
52
47
  };
53
48
 
54
- const isPointerPressable = (target: PointerTarget): boolean => target.native !== null || target.gestures.length > 0;
49
+ const isPointerPressable = (target: PointerTarget): boolean =>
50
+ target.click.outcome !== null || target.click.sites.some((site) => site.gestures.length > 0);
55
51
 
56
52
  const applyPointerInput = (widget: Gtk.Widget, state: UserEventState, input: PointerInput): void => {
57
53
  const target = pointerTargetFor(widget);
@@ -153,9 +153,17 @@ const runSelectionEvent = (
153
153
  byRole(widget, valueArray);
154
154
  });
155
155
 
156
+ const requireWidget = (target: Gtk.Accessible): Gtk.Widget => {
157
+ if (!(target instanceof Gtk.Widget)) {
158
+ throw new TypeError("Cannot select options on a non-widget accessible");
159
+ }
160
+
161
+ return target;
162
+ };
163
+
156
164
  /** Selects positions without activating them. */
157
- const selectOptions = (widget: Gtk.Widget, values: number | number[]): Promise<void> =>
158
- runSelectionEvent(widget, values, selectInListView, selectByRole);
165
+ const selectOptions = (widget: Gtk.Accessible, values: number | number[]): Promise<void> =>
166
+ runSelectionEvent(requireWidget(widget), values, selectInListView, selectByRole);
159
167
 
160
168
  const deselectInListView = (widget: Gtk.ListView | Gtk.GridView | Gtk.ColumnView, valueArray: number[]): void => {
161
169
  const selectionModel = requireSelectionModel(widget, "deselect");
@@ -176,7 +184,7 @@ const deselectByRole = (widget: Gtk.Widget, valueArray: number[]): void => {
176
184
  };
177
185
 
178
186
  /** Unselects positions without activating them. */
179
- const deselectOptions = (widget: Gtk.Widget, values: number | number[]): Promise<void> =>
180
- runSelectionEvent(widget, values, deselectInListView, deselectByRole);
187
+ const deselectOptions = (widget: Gtk.Accessible, values: number | number[]): Promise<void> =>
188
+ runSelectionEvent(requireWidget(widget), values, deselectInListView, deselectByRole);
181
189
 
182
190
  export { selectOptions, deselectOptions };
package/src/wait-for.ts CHANGED
@@ -4,12 +4,13 @@ import { runWithActEnvironment } from "./act.js";
4
4
  import { getConfig } from "./config.js";
5
5
  import { timeoutError } from "./errors.js";
6
6
  import { advanceFakeClock, delay, now } from "./timers.js";
7
+ import { requireWidget } from "./widget-target.js";
7
8
 
8
9
  type PollResult<T> = { status: "resolved"; value: T } | { status: "timedout"; lastError: Error | null };
9
10
  /** Widgets watched for removal; null counts as already removed. */
10
- type RemovalTarget = Gtk.Widget | Gtk.Widget[] | null;
11
+ type RemovalTarget = Gtk.Accessible | Gtk.Accessible[] | null;
11
12
  /** The widgets to watch for removal, or a function re-read on every poll to locate them. */
12
- type ElementOrCallback = Gtk.Widget | Gtk.Widget[] | (() => RemovalTarget);
13
+ type ElementOrCallback = Gtk.Accessible | Gtk.Accessible[] | (() => RemovalTarget);
13
14
 
14
15
  const DEFAULT_INTERVAL = 50;
15
16
  const ELEMENT_NOT_REMOVED = new Error("Element not yet removed");
@@ -102,10 +103,8 @@ const waitFor = <T>(callback: () => T | Promise<T>, options?: WaitForOptions): P
102
103
  const getTarget = (elementOrCallback: ElementOrCallback): RemovalTarget =>
103
104
  typeof elementOrCallback === "function" ? elementOrCallback() : elementOrCallback;
104
105
 
105
- const isWidgetRemoved = (widget: Gtk.Widget | null): boolean => {
106
- if (widget === null) {
107
- return true;
108
- }
106
+ const isWidgetRemoved = (target: Gtk.Accessible): boolean => {
107
+ const widget = requireWidget(target);
109
108
 
110
109
  try {
111
110
  return widget.getRoot() === null;
@@ -115,6 +114,10 @@ const isWidgetRemoved = (widget: Gtk.Widget | null): boolean => {
115
114
  };
116
115
 
117
116
  const isTargetRemoved = (target: RemovalTarget): boolean => {
117
+ if (target === null) {
118
+ return true;
119
+ }
120
+
118
121
  if (Array.isArray(target)) {
119
122
  return target.every((widget) => isWidgetRemoved(widget));
120
123
  }
@@ -13,6 +13,7 @@ import { EDITABLE_ROLES, isEditable, readEditableText } from "./editable.js";
13
13
  import { isNameFromAuthor, isNameProhibited } from "./role-naming.js";
14
14
  import { children, descendants, relationCandidates } from "./traversal.js";
15
15
  import { callBooleanGetter, callStringGetter, getCallableMethod } from "./widget-getters.js";
16
+ import { requireWidget } from "./widget-target.js";
16
17
 
17
18
  type WidgetValueField = "now" | "min" | "max";
18
19
  type CheckedState = "checked" | "unchecked" | "mixed";
@@ -64,12 +65,12 @@ const getLabelText = (widget: Gtk.Widget): string | null => {
64
65
  };
65
66
 
66
67
  const stripMnemonic = (text: string): string => text.replaceAll(/_(.)/g, "$1");
67
- const isUnderlineUsed = (widget: Gtk.Widget): boolean => callBooleanGetter(widget, "getUseUnderline") ?? false;
68
+ const isUnderlineUsed = (widget: object): boolean => callBooleanGetter(widget, "getUseUnderline") ?? false;
68
69
 
69
- const readNamingText = (widget: Gtk.Widget, getter: string, value: string): string =>
70
+ const readNamingText = (widget: object, getter: string, value: string): string =>
70
71
  getter !== EDITABLE_TEXT_GETTER && isUnderlineUsed(widget) ? stripMnemonic(value) : value;
71
72
 
72
- const readFirstText = (widget: Gtk.Widget, getters: string[]): string | null => {
73
+ const readFirstText = (widget: object, getters: string[]): string | null => {
73
74
  for (const getter of getters) {
74
75
  const value = callStringGetter(widget, getter);
75
76
 
@@ -87,7 +88,7 @@ const readFirstText = (widget: Gtk.Widget, getters: string[]): string | null =>
87
88
  *
88
89
  * @param widget The widget to read text from.
89
90
  */
90
- const getWidgetText = (widget: Gtk.Widget): string | null => readFirstText(widget, DEFAULT_TEXT_GETTERS);
91
+ const getWidgetText = (widget: Gtk.Accessible): string | null => readFirstText(widget, DEFAULT_TEXT_GETTERS);
91
92
 
92
93
  const namingLabelText = (widget: Gtk.Widget): string | null => {
93
94
  const text = getLabelText(widget);
@@ -400,22 +401,22 @@ const isSelfLabelling = (widget: Gtk.Widget, targets: Gtk.Widget[]): boolean =>
400
401
  return targets.every((target) => own.has(target));
401
402
  };
402
403
 
403
- const getWidgetLabelledByText = (widget: Gtk.Widget): string | null => {
404
- const targets = readAccessibleWidgets(widget, Gtk.AccessibleRelation.LABELLED_BY);
405
-
406
- if (targets === null) {
407
- return null;
408
- }
409
-
404
+ const labelledByText = (targets: Gtk.Widget[]): string | null => {
410
405
  const texts = collectAccessibleNames(targets);
411
406
 
412
407
  return texts.length > 0 ? texts.join(" ") : null;
413
408
  };
414
409
 
410
+ const getWidgetLabelledByText = (widget: Gtk.Widget): string | null => {
411
+ const targets = readAccessibleWidgets(widget, Gtk.AccessibleRelation.LABELLED_BY);
412
+
413
+ return targets === null ? null : labelledByText(targets);
414
+ };
415
+
415
416
  const getWidgetExternalLabelText = (widget: Gtk.Widget): string | null => {
416
417
  const targets = readAccessibleWidgets(widget, Gtk.AccessibleRelation.LABELLED_BY);
417
418
 
418
- return targets === null || isSelfLabelling(widget, targets) ? null : getWidgetLabelledByText(widget);
419
+ return targets === null || isSelfLabelling(widget, targets) ? null : labelledByText(targets);
419
420
  };
420
421
 
421
422
  const isTooltipUsedAsName = (widget: Gtk.Widget): boolean =>
@@ -440,27 +441,40 @@ const getWidgetDescribedByText = (widget: Gtk.Widget): string | null => {
440
441
  return isTooltipUsedAsName(widget) ? null : callStringGetter(widget, "getTooltipText");
441
442
  };
442
443
 
444
+ const isHiddenWithin = (widget: Gtk.Widget, hiddenWidgets: Map<Gtk.Widget, boolean>): boolean => {
445
+ const cached = hiddenWidgets.get(widget);
446
+
447
+ if (cached !== undefined) {
448
+ return cached;
449
+ }
450
+
451
+ const parent = widget.getParent();
452
+
453
+ const isHidden =
454
+ readAccessibleBoolean(widget, Gtk.AccessibleState.HIDDEN) === true ||
455
+ (parent !== null && isHiddenWithin(parent, hiddenWidgets));
456
+
457
+ hiddenWidgets.set(widget, isHidden);
458
+
459
+ return isHidden;
460
+ };
461
+
462
+ const buildInaccessibilityCheck = (): ((target: Gtk.Accessible) => boolean) => {
463
+ const hiddenWidgets: Map<Gtk.Widget, boolean> = new Map();
464
+
465
+ return (target) => isHiddenWithin(requireWidget(target), hiddenWidgets);
466
+ };
467
+
443
468
  /**
444
469
  * Returns whether a widget is excluded from the accessibility tree, because it or one of its
445
470
  * ancestors is marked hidden.
446
471
  *
447
472
  * @param widget The widget to test.
448
473
  */
449
- const isInaccessible = (widget: Gtk.Widget): boolean => {
450
- let current: Gtk.Widget | null = widget;
451
-
452
- while (current) {
453
- if (readAccessibleBoolean(current, Gtk.AccessibleState.HIDDEN) === true) {
454
- return true;
455
- }
456
-
457
- current = current.getParent();
458
- }
459
-
460
- return false;
461
- };
474
+ const isInaccessible = (target: Gtk.Accessible): boolean => buildInaccessibilityCheck()(target);
462
475
 
463
476
  export {
477
+ buildInaccessibilityCheck,
464
478
  namingLabelText,
465
479
  getWidgetText,
466
480
  getWidgetTextContent,
@@ -1,28 +1,26 @@
1
- import type * as GObject from "@gtkx/gi/gobject";
2
- import type * as Gtk from "@gtkx/gi/gtk";
3
1
  import { getClassType, getInstanceType, TYPE_INVALID, typeName } from "@gtkx/runtime";
4
2
  import { resolveWrapperClass } from "@gtkx/runtime/internal";
5
3
  import { type AnyClass, walkClassChain } from "@gtkx/utils";
6
4
 
7
5
  const UNNAMED_TYPE_TAG = "Object";
8
6
 
9
- const getWidgetMethod = (widget: Gtk.Widget, name: string): unknown => Reflect.get(widget, name);
10
- const getWidgetTypeName = (widget: Gtk.Widget): string | null => typeName(getInstanceType(widget));
7
+ const getWidgetMethod = (widget: object, name: string): unknown => Reflect.get(widget, name);
8
+ const getWidgetTypeName = (widget: object): string | null => typeName(getInstanceType(widget));
11
9
  const isClass = (value: unknown): value is AnyClass => typeof value === "function";
12
10
 
13
11
  const nonEmpty = (value: unknown): string | undefined =>
14
12
  typeof value === "string" && value.length > 0 ? value : undefined;
15
13
 
16
- const getObjectClass = (object: GObject.Object): AnyClass | null => {
14
+ const getObjectClass = (object: object): AnyClass | null => {
17
15
  const cls: unknown = object.constructor;
18
16
 
19
17
  return isClass(cls) ? cls : null;
20
18
  };
21
19
 
22
- const getNearestClassName = (object: GObject.Object): string | undefined =>
20
+ const getNearestClassName = (object: object): string | undefined =>
23
21
  walkClassChain(getObjectClass(object), (ancestor) => nonEmpty(ancestor.name));
24
22
 
25
- const getUntypedClassName = (object: GObject.Object): string | undefined => {
23
+ const getUntypedClassName = (object: object): string | undefined => {
26
24
  const cls = getObjectClass(object);
27
25
 
28
26
  return cls !== null && getClassType(cls) === TYPE_INVALID ? nonEmpty(cls.name) : undefined;
@@ -34,7 +32,7 @@ const getExactWrapperName = (type: bigint): string | undefined => {
34
32
  return wrapper !== null && getClassType(wrapper) === type ? nonEmpty(wrapper.name) : undefined;
35
33
  };
36
34
 
37
- const getTypeTag = (object: GObject.Object): string => {
35
+ const getTypeTag = (object: object): string => {
38
36
  const type = getInstanceType(object);
39
37
 
40
38
  return (
@@ -46,15 +44,15 @@ const getTypeTag = (object: GObject.Object): string => {
46
44
  );
47
45
  };
48
46
 
49
- const isDefaultWidgetName = (widget: Gtk.Widget, name: string): boolean =>
47
+ const isDefaultWidgetName = (widget: object, name: string): boolean =>
50
48
  name.length === 0 || name === getWidgetTypeName(widget);
51
49
 
52
- const hasWidgetMethod = (widget: Gtk.Widget, name: string): boolean =>
50
+ const hasWidgetMethod = (widget: object, name: string): boolean =>
53
51
  typeof getWidgetMethod(widget, name) === "function";
54
52
 
55
53
  /* eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- callers name the signature */
56
54
  const getCallableMethod = <Args extends unknown[], Result>(
57
- widget: Gtk.Widget,
55
+ widget: object,
58
56
  name: string,
59
57
  ): ((...args: Args) => Result) | null => {
60
58
  const fn = getWidgetMethod(widget, name);
@@ -62,13 +60,13 @@ const getCallableMethod = <Args extends unknown[], Result>(
62
60
  return typeof fn === "function" ? (fn as (...args: Args) => Result).bind(widget) : null;
63
61
  };
64
62
 
65
- const callBooleanGetter = (widget: Gtk.Widget, method: string): boolean | null => {
63
+ const callBooleanGetter = (widget: object, method: string): boolean | null => {
66
64
  const value = getCallableMethod<[], unknown>(widget, method)?.();
67
65
 
68
66
  return typeof value === "boolean" ? value : null;
69
67
  };
70
68
 
71
- const callStringGetter = (widget: Gtk.Widget, method: string): string | null => {
69
+ const callStringGetter = (widget: object, method: string): string | null => {
72
70
  const value = getCallableMethod<[], unknown>(widget, method)?.();
73
71
 
74
72
  return typeof value === "string" ? value : null;
@@ -0,0 +1,11 @@
1
+ import * as Gtk from "@gtkx/gi/gtk";
2
+
3
+ const requireWidget = (target: object): Gtk.Widget => {
4
+ if (!(target instanceof Gtk.Widget)) {
5
+ throw new TypeError("Expected a Gtk.Widget");
6
+ }
7
+
8
+ return target;
9
+ };
10
+
11
+ export { requireWidget };