@gtkx/testing 1.6.0 → 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 (132) hide show
  1. package/README.md +47 -44
  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/config.d.ts.map +1 -1
  11. package/dist/config.js +1 -3
  12. package/dist/config.js.map +1 -1
  13. package/dist/fire-event.d.ts +3 -3
  14. package/dist/fire-event.d.ts.map +1 -1
  15. package/dist/fire-event.js +14 -4
  16. package/dist/fire-event.js.map +1 -1
  17. package/dist/matchers.d.ts +52 -56
  18. package/dist/matchers.d.ts.map +1 -1
  19. package/dist/matchers.js +4 -16
  20. package/dist/matchers.js.map +1 -1
  21. package/dist/queries.d.ts +37 -133
  22. package/dist/queries.d.ts.map +1 -1
  23. package/dist/queries.js +48 -136
  24. package/dist/queries.js.map +1 -1
  25. package/dist/query-helpers.d.ts +2 -2
  26. package/dist/query-helpers.d.ts.map +1 -1
  27. package/dist/query-helpers.js +10 -3
  28. package/dist/query-helpers.js.map +1 -1
  29. package/dist/render.d.ts.map +1 -1
  30. package/dist/render.js +4 -0
  31. package/dist/render.js.map +1 -1
  32. package/dist/role-helpers.d.ts +1 -1
  33. package/dist/role-helpers.d.ts.map +1 -1
  34. package/dist/role-helpers.js +2 -1
  35. package/dist/role-helpers.js.map +1 -1
  36. package/dist/screenshot.d.ts +1 -1
  37. package/dist/screenshot.d.ts.map +1 -1
  38. package/dist/screenshot.js +26 -8
  39. package/dist/screenshot.js.map +1 -1
  40. package/dist/suggestions.d.ts +1 -1
  41. package/dist/suggestions.d.ts.map +1 -1
  42. package/dist/suggestions.js +3 -1
  43. package/dist/suggestions.js.map +1 -1
  44. package/dist/traversal.d.ts +4 -4
  45. package/dist/traversal.d.ts.map +1 -1
  46. package/dist/traversal.js +25 -7
  47. package/dist/traversal.js.map +1 -1
  48. package/dist/types.d.ts +10 -10
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/types.js.map +1 -1
  51. package/dist/user-event/adjustment.d.ts +2 -14
  52. package/dist/user-event/adjustment.d.ts.map +1 -1
  53. package/dist/user-event/adjustment.js +2 -14
  54. package/dist/user-event/adjustment.js.map +1 -1
  55. package/dist/user-event/click.d.ts +20 -45
  56. package/dist/user-event/click.d.ts.map +1 -1
  57. package/dist/user-event/click.js +31 -50
  58. package/dist/user-event/click.js.map +1 -1
  59. package/dist/user-event/controller.d.ts +5 -5
  60. package/dist/user-event/controller.d.ts.map +1 -1
  61. package/dist/user-event/controller.js +5 -2
  62. package/dist/user-event/controller.js.map +1 -1
  63. package/dist/user-event/gesture.d.ts +9 -43
  64. package/dist/user-event/gesture.d.ts.map +1 -1
  65. package/dist/user-event/gesture.js +9 -43
  66. package/dist/user-event/gesture.js.map +1 -1
  67. package/dist/user-event/index.d.ts +38 -107
  68. package/dist/user-event/index.d.ts.map +1 -1
  69. package/dist/user-event/index.js +29 -30
  70. package/dist/user-event/index.js.map +1 -1
  71. package/dist/user-event/keyboard.d.ts +1 -1
  72. package/dist/user-event/keyboard.d.ts.map +1 -1
  73. package/dist/user-event/keyboard.js +24 -3
  74. package/dist/user-event/keyboard.js.map +1 -1
  75. package/dist/user-event/list-row-click.d.ts +1 -1
  76. package/dist/user-event/list-row-click.d.ts.map +1 -1
  77. package/dist/user-event/list-row-click.js +2 -1
  78. package/dist/user-event/list-row-click.js.map +1 -1
  79. package/dist/user-event/pointer.d.ts.map +1 -1
  80. package/dist/user-event/pointer.js +9 -11
  81. package/dist/user-event/pointer.js.map +1 -1
  82. package/dist/user-event/selection.d.ts +4 -20
  83. package/dist/user-event/selection.d.ts.map +1 -1
  84. package/dist/user-event/selection.js +11 -23
  85. package/dist/user-event/selection.js.map +1 -1
  86. package/dist/user-event/text.d.ts +5 -34
  87. package/dist/user-event/text.d.ts.map +1 -1
  88. package/dist/user-event/text.js +5 -34
  89. package/dist/user-event/text.js.map +1 -1
  90. package/dist/wait-for.d.ts +2 -2
  91. package/dist/wait-for.d.ts.map +1 -1
  92. package/dist/wait-for.js +6 -4
  93. package/dist/wait-for.js.map +1 -1
  94. package/dist/widget-accessible-properties.d.ts +4 -3
  95. package/dist/widget-accessible-properties.d.ts.map +1 -1
  96. package/dist/widget-accessible-properties.js +38 -51
  97. package/dist/widget-accessible-properties.js.map +1 -1
  98. package/dist/widget-getters.d.ts +7 -9
  99. package/dist/widget-getters.d.ts.map +1 -1
  100. package/dist/widget-getters.js.map +1 -1
  101. package/dist/widget-target.d.ts +4 -0
  102. package/dist/widget-target.d.ts.map +1 -0
  103. package/dist/widget-target.js +9 -0
  104. package/dist/widget-target.js.map +1 -0
  105. package/package.json +17 -10
  106. package/src/accessible-native.ts +45 -9
  107. package/src/build-queries.ts +60 -30
  108. package/src/config.ts +1 -3
  109. package/src/fire-event.ts +32 -8
  110. package/src/matchers.ts +63 -85
  111. package/src/queries.ts +94 -156
  112. package/src/query-helpers.ts +17 -9
  113. package/src/render.tsx +7 -2
  114. package/src/role-helpers.ts +3 -1
  115. package/src/screenshot.ts +49 -8
  116. package/src/suggestions.ts +3 -1
  117. package/src/traversal.ts +35 -11
  118. package/src/types.ts +14 -10
  119. package/src/user-event/adjustment.ts +2 -14
  120. package/src/user-event/click.ts +40 -58
  121. package/src/user-event/controller.ts +8 -5
  122. package/src/user-event/gesture.ts +9 -43
  123. package/src/user-event/index.ts +149 -132
  124. package/src/user-event/keyboard.ts +33 -4
  125. package/src/user-event/list-row-click.ts +2 -2
  126. package/src/user-event/pointer.ts +11 -15
  127. package/src/user-event/selection.ts +15 -25
  128. package/src/user-event/text.ts +5 -34
  129. package/src/wait-for.ts +9 -6
  130. package/src/widget-accessible-properties.ts +61 -69
  131. package/src/widget-getters.ts +11 -13
  132. 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";
@@ -15,130 +26,132 @@ type UserEventOptions = {
15
26
  delay?: number | null | undefined;
16
27
  };
17
28
 
18
- /**
19
- * The set of user interaction helpers exposed by {@link userEvent}, covering clicks, typing,
20
- * keyboard, pointer, gestures, selection, and scrolling.
21
- */
29
+ /** User interactions exposed by {@link userEvent}. */
22
30
  type UserEvent = {
23
- /**
24
- * Returns a fresh instance whose helpers share one keyboard and pointer state, so held modifiers
25
- * and buttons carry across its calls but not across instances.
26
- */
31
+ /** Creates an instance with shared keyboard and pointer state. */
27
32
  setup: (options?: UserEventOptions) => UserEvent;
28
- /**
29
- * Activates a widget that neither claims the click nor carries a click gesture of its own, and
30
- * otherwise presses and releases outwards from the clicked widget through every widget carrying
31
- * a click gesture, stopping at the first button or indexed child. A list box row or flow box
32
- * child receives its click gesture at the clicked position, passes one on to the gestures its
33
- * container carries, and is then activated, or exclusively selected when its container does not
34
- * activate on a single click. The press also stops at a widget whose click GTK4 implements
35
- * itself, where the gestures that widget carries of its own still take the press and the same
36
- * outcome is then applied through GTK's own public action: a list, grid, or column-view row is
37
- * focused and selected, and activated as well when its view activates on a single click; an
38
- * expandable tree expander that is itself the clicked widget toggles its expansion; a notebook
39
- * tab, clicked directly or through its label, focuses its notebook and switches to its page; a
40
- * column header sorts by its column. A column-view cell and the row carrying the column headers
41
- * stand in the way of the click rather than taking it, so a click on either reaches the row or
42
- * the view behind it.
43
- */
44
- click: typeof click;
45
- /**
46
- * Delivers a two-press click gesture along the same path a single click takes, without trying
47
- * activation first, activating a list box row or flow box child on that path and replacing its
48
- * container's selection whether or not that container activates on a single click. A widget
49
- * whose click GTK4 implements itself receives that outcome once per press, so a row is selected
50
- * and activated by the second press, a tree expander ends back where it started, a notebook tab
51
- * stays on the page the first press opened, and a column header inverts its order after sorting.
52
- */
53
- dblClick: typeof dblClick;
54
- /**
55
- * Delivers a three-press click gesture the same way a double click is delivered, applying the
56
- * same outcome to a list box row or flow box child, and applying the outcome GTK4 implements
57
- * itself once per press to a row, tree expander, notebook tab, or column header.
58
- */
59
- tripleClick: typeof tripleClick;
60
- /** Moves focus within the widget's root, forward by default and backward with `isShiftHeld`. */
61
- tab: typeof tab;
62
- /**
63
- * Focuses an editable widget the way clicking into it does, leaving its text unselected, applies
64
- * any initial selection, and inserts the text at the cursor, deleting the text the widget has
65
- * selected first, the way GTK4 does.
66
- */
67
- type: typeof type;
68
- /** Focuses an editable widget and deletes its whole text, leaving nothing selected. */
69
- clear: typeof clear;
70
- /** Writes an editable widget's current selection to the clipboard. */
71
- copy: typeof copy;
72
- /** Writes an editable widget's current selection to the clipboard and deletes it. */
73
- cut: typeof cut;
74
- /**
75
- * Inserts the given text, or the clipboard's text, at an editable widget's cursor, deleting the
76
- * text the widget has selected first, the way GTK4 does.
77
- */
78
- paste: typeof paste;
79
- /**
80
- * Selects the items at those positions in a view or drop-down, or selects the indexed children of
81
- * a list box or flow box through their container, without activating them.
82
- */
33
+ /** Activates or presses and releases a widget. */
34
+ click: (
35
+ widget: Gtk.Accessible,
36
+ ...args: Parameters<typeof click> extends [Gtk.Widget, ...infer Args] ? Args : never
37
+ ) => Promise<void>;
38
+ /** Delivers a double-click gesture. */
39
+ dblClick: (
40
+ widget: Gtk.Accessible,
41
+ ...args: Parameters<typeof dblClick> extends [Gtk.Widget, ...infer Args] ? Args : never
42
+ ) => Promise<void>;
43
+ /** Delivers a triple-click gesture. */
44
+ tripleClick: (
45
+ widget: Gtk.Accessible,
46
+ ...args: Parameters<typeof tripleClick> extends [Gtk.Widget, ...infer Args] ? Args : never
47
+ ) => Promise<void>;
48
+ /** Moves focus forward or backward within the root. */
49
+ tab: (
50
+ widget: Gtk.Accessible,
51
+ ...args: Parameters<typeof tab> extends [Gtk.Widget, ...infer Args] ? Args : never
52
+ ) => Promise<void>;
53
+ /** Types into an editable widget. */
54
+ type: (
55
+ widget: Gtk.Accessible,
56
+ ...args: Parameters<typeof type> extends [Gtk.Widget, ...infer Args] ? Args : never
57
+ ) => Promise<void>;
58
+ /** Deletes all text from an editable widget. */
59
+ clear: (
60
+ widget: Gtk.Accessible,
61
+ ...args: Parameters<typeof clear> extends [Gtk.Widget, ...infer Args] ? Args : never
62
+ ) => Promise<void>;
63
+ /** Copies the current selection. */
64
+ copy: (
65
+ widget: Gtk.Accessible,
66
+ ...args: Parameters<typeof copy> extends [Gtk.Widget, ...infer Args] ? Args : never
67
+ ) => Promise<void>;
68
+ /** Cuts the current selection. */
69
+ cut: (
70
+ widget: Gtk.Accessible,
71
+ ...args: Parameters<typeof cut> extends [Gtk.Widget, ...infer Args] ? Args : never
72
+ ) => Promise<void>;
73
+ /** Pastes text or clipboard contents. */
74
+ paste: (
75
+ widget: Gtk.Accessible,
76
+ ...args: Parameters<typeof paste> extends [Gtk.Widget, ...infer Args] ? Args : never
77
+ ) => Promise<void>;
78
+ /** Selects positions without activating them. */
83
79
  selectOptions: typeof selectOptions;
84
- /** Unselects the items at those positions, leaving list box and flow box children unactivated. */
80
+ /** Unselects positions without activating them. */
85
81
  deselectOptions: typeof deselectOptions;
86
82
  /** Emits a motion `enter` on the widget, adding a motion controller when it has none. */
87
- hover: typeof hover;
83
+ hover: (
84
+ widget: Gtk.Accessible,
85
+ ...args: Parameters<typeof hover> extends [Gtk.Widget, ...infer Args] ? Args : never
86
+ ) => Promise<void>;
88
87
  /** Emits a motion `leave` on the widget, adding a motion controller when it has none. */
89
- unhover: typeof unhover;
88
+ unhover: (
89
+ widget: Gtk.Accessible,
90
+ ...args: Parameters<typeof unhover> extends [Gtk.Widget, ...infer Args] ? Args : never
91
+ ) => Promise<void>;
90
92
  /** Emits `angle-changed` on the widget's rotate gestures. */
91
- rotate: typeof rotate;
93
+ rotate: (
94
+ widget: Gtk.Accessible,
95
+ ...args: Parameters<typeof rotate> extends [Gtk.Widget, ...infer Args] ? Args : never
96
+ ) => Promise<void>;
92
97
  /** Emits `scale-changed` on the widget's zoom gestures. */
93
- zoom: typeof zoom;
98
+ zoom: (
99
+ widget: Gtk.Accessible,
100
+ ...args: Parameters<typeof zoom> extends [Gtk.Widget, ...infer Args] ? Args : never
101
+ ) => Promise<void>;
94
102
  /** Emits `swipe` with the given velocity on the widget's swipe gestures. */
95
- swipe: typeof swipe;
103
+ swipe: (
104
+ widget: Gtk.Accessible,
105
+ ...args: Parameters<typeof swipe> extends [Gtk.Widget, ...infer Args] ? Args : never
106
+ ) => Promise<void>;
96
107
  /** Emits `pressed` at the given point on the widget's long-press gestures. */
97
- longPress: typeof longPress;
108
+ longPress: (
109
+ widget: Gtk.Accessible,
110
+ ...args: Parameters<typeof longPress> extends [Gtk.Widget, ...infer Args] ? Args : never
111
+ ) => Promise<void>;
98
112
  /** Runs a begin, update, and end sequence on the widget's drag gestures, ending at the given offset. */
99
- drag: typeof drag;
113
+ drag: (
114
+ widget: Gtk.Accessible,
115
+ ...args: Parameters<typeof drag> extends [Gtk.Widget, ...infer Args] ? Args : never
116
+ ) => Promise<void>;
100
117
  /** Emits `drop` with the given content on the widget's drop targets. */
101
- drop: typeof drop;
118
+ drop: (
119
+ widget: Gtk.Accessible,
120
+ ...args: Parameters<typeof drop> extends [Gtk.Widget, ...infer Args] ? Args : never
121
+ ) => Promise<void>;
102
122
  /** Requires a drag source on the source widget, then emits `drop` with the content on the target's drop targets. */
103
- 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>;
104
128
  /** Emits a jump `change-value` on a Gtk.Range so it moves to the given value. */
105
- slide: typeof slide;
129
+ slide: (
130
+ widget: Gtk.Accessible,
131
+ ...args: Parameters<typeof slide> extends [Gtk.Widget, ...infer Args] ? Args : never
132
+ ) => Promise<void>;
106
133
  /** Adds the delta to the adjustments of the widget itself, or of its nearest scrollable ancestor. */
107
- scroll: typeof scroll;
108
- /**
109
- * Sends a key sequence along the chain GTK4 propagates a key event through: the application's
110
- * accelerators first, then the capture-phase controllers from the root down to the event widget,
111
- * the event widget's target-phase controllers, and the bubble-phase controllers from it back up
112
- * to the root, so a key controller or shortcut controller a container or window above the widget
113
- * carries receives the key too, each in the phase it declares. The event widget is the editable
114
- * delegate GTK4 focuses when the widget has one, the `Gtk.Text` inside a `Gtk.Entry` for
115
- * instance, so the delegate stands ahead of the widget in the chain. A shortcut controller whose
116
- * scope is not local runs where it registered instead, the root for a global one and the nearest
117
- * `Gtk.ShortcutManager` for a managed one, so it fires wherever focus sits inside that subtree
118
- * and never at its own widget, while a shortcut whose widget is insensitive or unmapped stays
119
- * inert wherever it sits. The first controller that handles a press ends the chain, and a
120
- * `Gdk.KEY_Return` press none of them handled reaches an editable widget's activate handler.
121
- * Built-in key bindings take part, since GTK4 carries them on a shortcut controller: a
122
- * `Gtk.Text`'s arrow keys or a `Gtk.TextView`'s undo can consume a press before an ancestor
123
- * sees it. Key controllers GTK4 attaches itself are the exception, since they read a GDK event
124
- * that off-screen synthesis cannot produce, so only the key controllers the tree connected a
125
- * `key-pressed` or `key-released` handler to take part. Held modifiers carry across calls.
126
- */
127
- keyboard: (widget: Gtk.Widget, input: string) => Promise<void>;
128
- /**
129
- * Applies a pointer token to the click gestures the widget already carries, tracking whether the
130
- * left button is held across calls. On a widget whose click GTK4 implements itself, the release
131
- * also applies GTK's own outcome, the way {@link UserEvent.click} does, and a column-view cell
132
- * or the row carrying the column headers passes the token to the row or view behind it.
133
- */
134
- pointer: (widget: Gtk.Widget, input: PointerInput) => Promise<void>;
134
+ scroll: (
135
+ widget: Gtk.Accessible,
136
+ ...args: Parameters<typeof scroll> extends [Gtk.Widget, ...infer Args] ? Args : never
137
+ ) => Promise<void>;
138
+ /** Sends a key sequence through GTK's propagation chain. */
139
+ keyboard: (widget: Gtk.Accessible, input: string) => Promise<void>;
140
+ /** Applies a pointer token while preserving held-button state. */
141
+ pointer: (widget: Gtk.Accessible, input: PointerInput) => Promise<void>;
135
142
  };
136
143
 
137
- /**
138
- * High-level helpers that drive widgets by dispatching the same events and gestures GTK4 delivers
139
- * when someone clicks, types, or drags. Call `setup` for an instance with its own keyboard and
140
- * pointer state.
141
- */
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
+
154
+ /** User interactions that dispatch GTK events and gestures. */
142
155
  const userEvent: UserEvent = {
143
156
  ...createInstance(createInitialState(), {}),
144
157
  setup: (options?: UserEventOptions): UserEvent => createInstance(createInitialState(), options ?? {}),
@@ -162,30 +175,34 @@ function createInstance(state: UserEventState, options: UserEventOptions): UserE
162
175
 
163
176
  return {
164
177
  setup: (overrides?: UserEventOptions): UserEvent => createInstance(state, { ...options, ...overrides }),
165
- click: after(click),
166
- dblClick: after(dblClick),
167
- tripleClick: after(tripleClick),
168
- tab: after(tab),
169
- type: after(type),
170
- clear: after(clear),
171
- copy: after(copy),
172
- cut: after(cut),
173
- paste: after(paste),
174
- selectOptions: after(selectOptions),
175
- deselectOptions: after(deselectOptions),
176
- hover: after(hover),
177
- unhover: after(unhover),
178
- rotate: after(rotate),
179
- zoom: after(zoom),
180
- swipe: after(swipe),
181
- longPress: after(longPress),
182
- drag: after(drag),
183
- drop: after(drop),
184
- dragAndDrop: after(dragAndDrop),
185
- slide: after(slide),
186
- scroll: after(scroll),
187
- keyboard: after((widget: Gtk.Widget, input: string): Promise<void> => keyboard(state, widget, input)),
188
- 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
+ ),
189
206
  };
190
207
  }
191
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,
@@ -65,7 +66,7 @@ const MODIFIER_KEYVAL_TO_MASK: Record<number, number> = {
65
66
  [Gdk.KEY_Meta_R]: Gdk.ModifierType.META_MASK,
66
67
  };
67
68
 
68
- /** Moves focus within the widget's root, forward by default and backward when `isShiftHeld` is set. */
69
+ /** Moves focus within the widget's root. */
69
70
  const tab = (widget: Gtk.Widget, options?: TabOptions): Promise<void> =>
70
71
  wrapEvent(widget, () => {
71
72
  const direction = options?.isShiftHeld ? Gtk.DirectionType.TAB_BACKWARD : Gtk.DirectionType.TAB_FORWARD;
@@ -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);
@@ -19,9 +19,7 @@ const SELECTABLE_ROLES: Set<Gtk.AccessibleRole> = new Set([
19
19
  Gtk.AccessibleRole.LIST,
20
20
  ]);
21
21
 
22
- const isSelectable = (widget: Gtk.Widget): boolean => {
23
- return SELECTABLE_ROLES.has(widget.getAccessibleRole());
24
- };
22
+ const isSelectable = (widget: Gtk.Widget): boolean => SELECTABLE_ROLES.has(widget.getAccessibleRole());
25
23
 
26
24
  const selectListViewItems = (
27
25
  selectionModel: Gtk.SelectionModel,
@@ -155,18 +153,17 @@ const runSelectionEvent = (
155
153
  byRole(widget, valueArray);
156
154
  });
157
155
 
158
- /**
159
- * Selects the items at the given positions through a list, grid, or column view's selection model,
160
- * by setting the selected position of a widget with the COMBO_BOX role, or by selecting the indexed
161
- * children of a list box or flow box through their container. An empty array clears a view's
162
- * selection. The children are never activated, so no row-activated handler runs.
163
- *
164
- * @throws When a view has no selection model, when a COMBO_BOX-role widget is given more than one
165
- * position or exposes no `setSelected`, or when the widget's role is none of COMBO_BOX, GRID, or
166
- * LIST.
167
- */
168
- const selectOptions = (widget: Gtk.Widget, values: number | number[]): Promise<void> =>
169
- runSelectionEvent(widget, values, selectInListView, selectByRole);
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
+
164
+ /** Selects positions without activating them. */
165
+ const selectOptions = (widget: Gtk.Accessible, values: number | number[]): Promise<void> =>
166
+ runSelectionEvent(requireWidget(widget), values, selectInListView, selectByRole);
170
167
 
171
168
  const deselectInListView = (widget: Gtk.ListView | Gtk.GridView | Gtk.ColumnView, valueArray: number[]): void => {
172
169
  const selectionModel = requireSelectionModel(widget, "deselect");
@@ -186,15 +183,8 @@ const deselectByRole = (widget: Gtk.Widget, valueArray: number[]): void => {
186
183
  applyIndexedChildren(widget, valueArray, unselectChildRow);
187
184
  };
188
185
 
189
- /**
190
- * Unselects the items at the given positions in a list, grid, or column view, or unselects the
191
- * indexed children of a list box or flow box through their container. A child that is not selected
192
- * is left alone, and no child is activated.
193
- *
194
- * @throws When a view has no selection model, when the widget's role is none of COMBO_BOX, GRID, or
195
- * LIST, or when it exposes no indexed children.
196
- */
197
- const deselectOptions = (widget: Gtk.Widget, values: number | number[]): Promise<void> =>
198
- runSelectionEvent(widget, values, deselectInListView, deselectByRole);
186
+ /** Unselects positions without activating them. */
187
+ const deselectOptions = (widget: Gtk.Accessible, values: number | number[]): Promise<void> =>
188
+ runSelectionEvent(requireWidget(widget), values, deselectInListView, deselectByRole);
199
189
 
200
190
  export { selectOptions, deselectOptions };
@@ -198,15 +198,7 @@ const runEditableEvent = (
198
198
  action(widget);
199
199
  });
200
200
 
201
- /**
202
- * Focuses the widget unless `shouldFocus` is false, applies any initial selection, and inserts the
203
- * text at the cursor, deleting the text the widget has selected first, the way typing over a
204
- * selection does in GTK4. Focusing works the way clicking into the widget does, leaving its text
205
- * unselected: a `Gtk.Editable` takes its caret to the end of its text, a `Gtk.TextView` keeps the
206
- * caret where it stands, and the replaced text stays on the widget's undo stack either way.
207
- *
208
- * @throws When the widget is neither a Gtk.Editable nor a Gtk.TextView.
209
- */
201
+ /** Types into an editable widget. */
210
202
  const type = (widget: Gtk.Widget, text: string, options?: TypeOptions): Promise<void> =>
211
203
  runEditableEvent(widget, "Cannot type into element", (editable) => {
212
204
  if (options?.shouldFocus ?? true) {
@@ -286,45 +278,24 @@ const clearEditable = (editable: EditableTarget): void => {
286
278
  }
287
279
  };
288
280
 
289
- /**
290
- * Focuses an editable widget and deletes its whole text through the widget's own editing API, so the
291
- * deletion emits the signals an edit made by hand emits. Text a tag protects is detected before the
292
- * deletion runs, leaving such a widget untouched. A widget that writes a new value back in response
293
- * to the deletion, as an input mask does, is cleared successfully and keeps whatever it wrote.
294
- *
295
- * @throws When the widget is neither a Gtk.Editable nor a Gtk.TextView, when it refuses edits, when
296
- * a tag protects part of its text, or when a handler blocks the deletion.
297
- */
281
+ /** Deletes all text from an editable widget. */
298
282
  const clear = (widget: Gtk.Widget): Promise<void> =>
299
283
  runEditableEvent(widget, "Cannot clear element", clearEditable);
300
284
 
301
- /**
302
- * Writes an editable widget's selected text to its clipboard, or the empty string when nothing is selected.
303
- *
304
- * @throws When the widget is neither a Gtk.Editable nor a Gtk.TextView.
305
- */
285
+ /** Copies the current selection. */
306
286
  const copy = (widget: Gtk.Widget): Promise<void> =>
307
287
  runEditableEvent(widget, "Cannot copy", (editable) => {
308
288
  writeClipboardText(editable, readSelection(editable));
309
289
  });
310
290
 
311
- /**
312
- * Writes an editable widget's selected text to its clipboard, then deletes that selection.
313
- *
314
- * @throws When the widget is neither a Gtk.Editable nor a Gtk.TextView.
315
- */
291
+ /** Cuts the current selection. */
316
292
  const cut = (widget: Gtk.Widget): Promise<void> =>
317
293
  runEditableEvent(widget, "Cannot cut", (editable) => {
318
294
  writeClipboardText(editable, readSelection(editable));
319
295
  deleteSelection(editable);
320
296
  });
321
297
 
322
- /**
323
- * Inserts the given text at an editable widget's cursor, reading the clipboard instead when no text
324
- * is given, and deleting the text the widget has selected first, the way a paste does in GTK4.
325
- *
326
- * @throws When the widget is neither a Gtk.Editable nor a Gtk.TextView.
327
- */
298
+ /** Pastes text or clipboard contents. */
328
299
  const paste = async (widget: Gtk.Widget, text?: string): Promise<void> => {
329
300
  if (!isEditable(widget)) {
330
301
  throw new Error(`Cannot paste: ${EDITABLE_REQUIRED}`);
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
  }