@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
package/src/queries.ts CHANGED
@@ -12,6 +12,7 @@ import { multipleFoundError, notFoundError } from "./errors.js";
12
12
  import { getDefaultNormalizer } from "./normalize.js";
13
13
  import { type Container, findAll, traverse } from "./traversal.js";
14
14
  import {
15
+ buildInaccessibilityCheck,
15
16
  getWidgetAccessibleName,
16
17
  getWidgetBusyState,
17
18
  getWidgetDescription,
@@ -26,7 +27,6 @@ import {
26
27
  getWidgetPressedState,
27
28
  getWidgetSelectedState,
28
29
  getWidgetValueText,
29
- isInaccessible,
30
30
  isWidgetChecked,
31
31
  isWidgetValueMatch,
32
32
  namingLabelText,
@@ -39,7 +39,7 @@ type BuiltinQueries = QueryFamilies<[container: Container]>;
39
39
  const roleQueries = nameQueryFamily(
40
40
  "Role",
41
41
  queryAllByRole,
42
- buildQueries<[role: Gtk.AccessibleRole, options?: ByRoleOptions]>(
42
+ buildQueries<[role: Gtk.AccessibleRole, options?: ByRoleOptions<Gtk.Widget>], Gtk.Widget>(
43
43
  queryAllByRole,
44
44
  (container, matches, role, options) =>
45
45
  multipleFoundError(container, { queryType: "role", role, options }, matches),
@@ -50,7 +50,7 @@ const roleQueries = nameQueryFamily(
50
50
  const labelTextQueries = nameQueryFamily(
51
51
  "LabelText",
52
52
  queryAllByLabelText,
53
- buildQueries<[text: Matcher, options?: MatcherOptions]>(
53
+ buildQueries<[text: Matcher, options?: MatcherOptions<Gtk.Widget>], Gtk.Widget>(
54
54
  queryAllByLabelText,
55
55
  (container, matches, text) => multipleFoundError(container, { queryType: "labelText", text }, matches),
56
56
  (container, text) => notFoundError(container, { queryType: "labelText", text }),
@@ -60,7 +60,7 @@ const labelTextQueries = nameQueryFamily(
60
60
  const textQueries = nameQueryFamily(
61
61
  "Text",
62
62
  queryAllByText,
63
- buildQueries<[text: Matcher, options?: MatcherOptions]>(
63
+ buildQueries<[text: Matcher, options?: MatcherOptions<Gtk.Widget>], Gtk.Widget>(
64
64
  queryAllByText,
65
65
  (container, matches, text) => multipleFoundError(container, { queryType: "text", text }, matches),
66
66
  (container, text) => notFoundError(container, { queryType: "text", text }),
@@ -70,7 +70,7 @@ const textQueries = nameQueryFamily(
70
70
  const nameQueries = nameQueryFamily(
71
71
  "Name",
72
72
  queryAllByName,
73
- buildQueries<[name: Matcher, options?: MatcherOptions]>(
73
+ buildQueries<[name: Matcher, options?: MatcherOptions<Gtk.Widget>], Gtk.Widget>(
74
74
  queryAllByName,
75
75
  (container, matches, name) => multipleFoundError(container, { queryType: "name", name }, matches),
76
76
  (container, name) => notFoundError(container, { queryType: "name", name }),
@@ -80,7 +80,7 @@ const nameQueries = nameQueryFamily(
80
80
  const placeholderTextQueries = nameQueryFamily(
81
81
  "PlaceholderText",
82
82
  queryAllByPlaceholderText,
83
- buildQueries<[text: Matcher, options?: MatcherOptions]>(
83
+ buildQueries<[text: Matcher, options?: MatcherOptions<Gtk.Widget>], Gtk.Widget>(
84
84
  queryAllByPlaceholderText,
85
85
  (container, matches, text) => multipleFoundError(container, { queryType: "placeholderText", text }, matches),
86
86
  (container, text) => notFoundError(container, { queryType: "placeholderText", text }),
@@ -90,7 +90,7 @@ const placeholderTextQueries = nameQueryFamily(
90
90
  const displayValueQueries = nameQueryFamily(
91
91
  "DisplayValue",
92
92
  queryAllByDisplayValue,
93
- buildQueries<[value: Matcher, options?: MatcherOptions]>(
93
+ buildQueries<[value: Matcher, options?: MatcherOptions<Gtk.Widget>], Gtk.Widget>(
94
94
  queryAllByDisplayValue,
95
95
  (container, matches, value) => multipleFoundError(container, { queryType: "displayValue", value }, matches),
96
96
  (container, value) => notFoundError(container, { queryType: "displayValue", value }),
@@ -106,161 +106,65 @@ const builtinQueries = {
106
106
  ...displayValueQueries,
107
107
  } as BuiltinQueries;
108
108
 
109
- /**
110
- * Returns the single mapped widget with a matching accessible role and options, or null when none
111
- * match. Widgets that are not mapped are never matched. Throws when more than one matches.
112
- */
109
+ /** Queries one mapped widget by accessible role. */
113
110
  const queryByRole: BuiltinQueries["queryByRole"] = builtinQueries.queryByRole;
114
- /**
115
- * Returns every mapped widget with a matching accessible role and options. Widgets that are not
116
- * mapped are never matched. Throws when none match.
117
- */
111
+ /** Gets all mapped widgets with an accessible role. */
118
112
  const getAllByRole: BuiltinQueries["getAllByRole"] = builtinQueries.getAllByRole;
119
- /**
120
- * Returns the single mapped widget with a matching accessible role and options. Widgets that are not
121
- * mapped are never matched. Throws when none or more than one matches.
122
- */
113
+ /** Gets one mapped widget by accessible role. */
123
114
  const getByRole: BuiltinQueries["getByRole"] = builtinQueries.getByRole;
124
- /**
125
- * Waits for and returns every mapped widget with a matching accessible role and options, retrying
126
- * until at least one appears or the timeout elapses. Widgets that are not mapped are never matched.
127
- */
115
+ /** Finds all mapped widgets with an accessible role. */
128
116
  const findAllByRole: BuiltinQueries["findAllByRole"] = builtinQueries.findAllByRole;
129
- /**
130
- * Waits for and returns the single mapped widget with a matching accessible role and options,
131
- * retrying until it appears or the timeout elapses. Widgets that are not mapped are never matched.
132
- * Rejects when none or more than one matches.
133
- */
117
+ /** Finds one mapped widget by accessible role. */
134
118
  const findByRole: BuiltinQueries["findByRole"] = builtinQueries.findByRole;
135
- /**
136
- * Returns the single mapped widget with matching associated label text, or null when none match.
137
- * Widgets that are not mapped are never matched. Throws when more than one matches.
138
- */
119
+ /** Queries one mapped widget by label text. */
139
120
  const queryByLabelText: BuiltinQueries["queryByLabelText"] = builtinQueries.queryByLabelText;
140
- /**
141
- * Returns every mapped widget with matching associated label text. Widgets that are not mapped are
142
- * never matched. Throws when none match.
143
- */
121
+ /** Gets all mapped widgets with label text. */
144
122
  const getAllByLabelText: BuiltinQueries["getAllByLabelText"] = builtinQueries.getAllByLabelText;
145
- /**
146
- * Returns the single mapped widget with matching associated label text. Widgets that are not mapped
147
- * are never matched. Throws when none or more than one matches.
148
- */
123
+ /** Gets one mapped widget by label text. */
149
124
  const getByLabelText: BuiltinQueries["getByLabelText"] = builtinQueries.getByLabelText;
150
- /**
151
- * Waits for and returns every mapped widget with matching associated label text, retrying until at
152
- * least one appears or the timeout elapses. Widgets that are not mapped are never matched.
153
- */
125
+ /** Finds all mapped widgets with label text. */
154
126
  const findAllByLabelText: BuiltinQueries["findAllByLabelText"] = builtinQueries.findAllByLabelText;
155
- /**
156
- * Waits for and returns the single mapped widget with matching associated label text, retrying until
157
- * it appears or the timeout elapses. Widgets that are not mapped are never matched. Rejects when
158
- * none or more than one matches.
159
- */
127
+ /** Finds one mapped widget by label text. */
160
128
  const findByLabelText: BuiltinQueries["findByLabelText"] = builtinQueries.findByLabelText;
161
- /**
162
- * Returns the single mapped widget with matching rendered text content, or null when none match.
163
- * Widgets that are not mapped are never matched. Throws when more than one matches.
164
- */
129
+ /** Queries one mapped widget by rendered text. */
165
130
  const queryByText: BuiltinQueries["queryByText"] = builtinQueries.queryByText;
166
- /**
167
- * Returns every mapped widget with matching rendered text content. Widgets that are not mapped are
168
- * never matched. Throws when none match.
169
- */
131
+ /** Gets all mapped widgets with rendered text. */
170
132
  const getAllByText: BuiltinQueries["getAllByText"] = builtinQueries.getAllByText;
171
- /**
172
- * Returns the single mapped widget with matching rendered text content. Widgets that are not mapped
173
- * are never matched. Throws when none or more than one matches.
174
- */
133
+ /** Gets one mapped widget by rendered text. */
175
134
  const getByText: BuiltinQueries["getByText"] = builtinQueries.getByText;
176
- /**
177
- * Waits for and returns every mapped widget with matching rendered text content, retrying until at
178
- * least one appears or the timeout elapses. Widgets that are not mapped are never matched.
179
- */
135
+ /** Finds all mapped widgets with rendered text. */
180
136
  const findAllByText: BuiltinQueries["findAllByText"] = builtinQueries.findAllByText;
181
- /**
182
- * Waits for and returns the single mapped widget with matching rendered text content, retrying until
183
- * it appears or the timeout elapses. Widgets that are not mapped are never matched. Rejects when
184
- * none or more than one matches.
185
- */
137
+ /** Finds one mapped widget by rendered text. */
186
138
  const findByText: BuiltinQueries["findByText"] = builtinQueries.findByText;
187
- /**
188
- * Returns the single mapped widget with a matching widget name, or null when none match. Widgets
189
- * that are not mapped are never matched. Throws when more than one matches.
190
- */
139
+ /** Queries one mapped widget by GTK name. */
191
140
  const queryByName: BuiltinQueries["queryByName"] = builtinQueries.queryByName;
192
- /**
193
- * Returns every mapped widget with a matching widget name. Widgets that are not mapped are never
194
- * matched. Throws when none match.
195
- */
141
+ /** Gets all mapped widgets with a GTK name. */
196
142
  const getAllByName: BuiltinQueries["getAllByName"] = builtinQueries.getAllByName;
197
- /**
198
- * Returns the single mapped widget with a matching widget name. Widgets that are not mapped are
199
- * never matched. Throws when none or more than one matches.
200
- */
143
+ /** Gets one mapped widget by GTK name. */
201
144
  const getByName: BuiltinQueries["getByName"] = builtinQueries.getByName;
202
- /**
203
- * Waits for and returns every mapped widget with a matching widget name, retrying until at least one
204
- * appears or the timeout elapses. Widgets that are not mapped are never matched.
205
- */
145
+ /** Finds all mapped widgets with a GTK name. */
206
146
  const findAllByName: BuiltinQueries["findAllByName"] = builtinQueries.findAllByName;
207
- /**
208
- * Waits for and returns the single mapped widget with a matching widget name, retrying until it
209
- * appears or the timeout elapses. Widgets that are not mapped are never matched. Rejects when none
210
- * or more than one matches.
211
- */
147
+ /** Finds one mapped widget by GTK name. */
212
148
  const findByName: BuiltinQueries["findByName"] = builtinQueries.findByName;
213
- /**
214
- * Returns the single mapped widget with matching placeholder text, or null when none match. Widgets
215
- * that are not mapped are never matched. Throws when more than one matches.
216
- */
149
+ /** Queries one mapped widget by placeholder text. */
217
150
  const queryByPlaceholderText: BuiltinQueries["queryByPlaceholderText"] = builtinQueries.queryByPlaceholderText;
218
- /**
219
- * Returns every mapped widget with matching placeholder text. Widgets that are not mapped are never
220
- * matched. Throws when none match.
221
- */
151
+ /** Gets all mapped widgets with placeholder text. */
222
152
  const getAllByPlaceholderText: BuiltinQueries["getAllByPlaceholderText"] = builtinQueries.getAllByPlaceholderText;
223
- /**
224
- * Returns the single mapped widget with matching placeholder text. Widgets that are not mapped are
225
- * never matched. Throws when none or more than one matches.
226
- */
153
+ /** Gets one mapped widget by placeholder text. */
227
154
  const getByPlaceholderText: BuiltinQueries["getByPlaceholderText"] = builtinQueries.getByPlaceholderText;
228
- /**
229
- * Waits for and returns every mapped widget with matching placeholder text, retrying until at least
230
- * one appears or the timeout elapses. Widgets that are not mapped are never matched.
231
- */
155
+ /** Finds all mapped widgets with placeholder text. */
232
156
  const findAllByPlaceholderText: BuiltinQueries["findAllByPlaceholderText"] = builtinQueries.findAllByPlaceholderText;
233
- /**
234
- * Waits for and returns the single mapped widget with matching placeholder text, retrying until it
235
- * appears or the timeout elapses. Widgets that are not mapped are never matched. Rejects when none
236
- * or more than one matches.
237
- */
157
+ /** Finds one mapped widget by placeholder text. */
238
158
  const findByPlaceholderText: BuiltinQueries["findByPlaceholderText"] = builtinQueries.findByPlaceholderText;
239
- /**
240
- * Returns the single mapped widget with a matching display value, or null when none match. Widgets
241
- * that are not mapped are never matched. Throws when more than one matches.
242
- */
159
+ /** Queries one mapped widget by display value. */
243
160
  const queryByDisplayValue: BuiltinQueries["queryByDisplayValue"] = builtinQueries.queryByDisplayValue;
244
- /**
245
- * Returns every mapped widget with a matching display value. Widgets that are not mapped are never
246
- * matched. Throws when none match.
247
- */
161
+ /** Gets all mapped widgets with a display value. */
248
162
  const getAllByDisplayValue: BuiltinQueries["getAllByDisplayValue"] = builtinQueries.getAllByDisplayValue;
249
- /**
250
- * Returns the single mapped widget with a matching display value. Widgets that are not mapped are
251
- * never matched. Throws when none or more than one matches.
252
- */
163
+ /** Gets one mapped widget by display value. */
253
164
  const getByDisplayValue: BuiltinQueries["getByDisplayValue"] = builtinQueries.getByDisplayValue;
254
- /**
255
- * Waits for and returns every mapped widget with a matching display value, retrying until at least
256
- * one appears or the timeout elapses. Widgets that are not mapped are never matched.
257
- */
165
+ /** Finds all mapped widgets with a display value. */
258
166
  const findAllByDisplayValue: BuiltinQueries["findAllByDisplayValue"] = builtinQueries.findAllByDisplayValue;
259
- /**
260
- * Waits for and returns the single mapped widget with a matching display value, retrying until it
261
- * appears or the timeout elapses. Widgets that are not mapped are never matched. Rejects when none
262
- * or more than one matches.
263
- */
167
+ /** Finds one mapped widget by display value. */
264
168
  const findByDisplayValue: BuiltinQueries["findByDisplayValue"] = builtinQueries.findByDisplayValue;
265
169
 
266
170
  const buildNormalizer = (options?: MatcherOptions): NormalizerFn => {
@@ -380,25 +284,27 @@ const hasMatchingAccessibleStates = (widget: Gtk.Widget, options: ByRoleOptions)
380
284
  hasMatchingDescriptionState(widget, options) &&
381
285
  hasMatchingValueState(widget, options);
382
286
 
383
- const isMatchingWidgetType = (widget: Gtk.Widget, options?: MatcherOptions): boolean =>
287
+ const isMatchingWidgetType = <T extends Gtk.Accessible>(
288
+ widget: Gtk.Widget,
289
+ options?: MatcherOptions<T>,
290
+ ): widget is Gtk.Widget & T =>
384
291
  options?.as === undefined || widget instanceof options.as;
385
292
 
386
- const hasMatchingByRoleOptions = (widget: Gtk.Widget, options?: ByRoleOptions): boolean => {
293
+ const hasMatchingAccessibleOptions = (widget: Gtk.Widget, options?: ByRoleOptions): boolean => {
387
294
  if (!options) {
388
295
  return true;
389
296
  }
390
297
 
391
298
  return (
392
- isMatchingWidgetType(widget, options) &&
393
299
  hasMatchingAccessibleName(widget, options) &&
394
300
  hasMatchingAccessibleStates(widget, options)
395
301
  );
396
302
  };
397
303
 
398
- function nameQueryFamily<Args extends unknown[]>(
304
+ function nameQueryFamily<Args extends unknown[], Element extends Gtk.Accessible>(
399
305
  suffix: string,
400
- queryAllBy: QueryAllBy<Args>,
401
- built: BuiltQueries<Args>,
306
+ queryAllBy: QueryAllBy<Args, Element>,
307
+ built: BuiltQueries<Args, Element>,
402
308
  ): Record<string, unknown> {
403
309
  const [queryBy, getAllBy, getBy, findAllBy, findBy] = built;
404
310
 
@@ -421,22 +327,30 @@ function nameQueryFamily<Args extends unknown[]>(
421
327
  * @param options Additional accessible name, state, and value constraints.
422
328
  * @returns Every matching widget, or an empty array when none match.
423
329
  */
424
- function queryAllByRole(
330
+ function queryAllByRole<T extends Gtk.Accessible = Gtk.Widget>(
425
331
  container: Container,
426
332
  role: Gtk.AccessibleRole,
427
- options?: ByRoleOptions,
428
- ): Gtk.Widget[] {
429
- return findAll(container, (widget) => {
333
+ options?: ByRoleOptions<T>,
334
+ ): T[] {
335
+ const isInaccessible = buildInaccessibilityCheck();
336
+
337
+ const matches = findAll(container, (widget) => {
430
338
  if (widget.getAccessibleRole() !== role) {
431
339
  return false;
432
340
  }
433
341
 
342
+ if (!isMatchingWidgetType(widget, options)) {
343
+ return false;
344
+ }
345
+
434
346
  if (!options?.hidden && isInaccessible(widget)) {
435
347
  return false;
436
348
  }
437
349
 
438
- return hasMatchingByRoleOptions(widget, options);
350
+ return hasMatchingAccessibleOptions(widget, options);
439
351
  });
352
+
353
+ return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
440
354
  }
441
355
 
442
356
  const collectMnemonicMatch = (
@@ -490,14 +404,18 @@ const collectLabelMatches = (
490
404
  * @param options Text matching options.
491
405
  * @returns Every matching widget, or an empty array when none match.
492
406
  */
493
- function queryAllByLabelText(container: Container, text: Matcher, options?: MatcherOptions): Gtk.Widget[] {
407
+ function queryAllByLabelText<T extends Gtk.Accessible = Gtk.Widget>(
408
+ container: Container,
409
+ text: Matcher,
410
+ options?: MatcherOptions<T>,
411
+ ): T[] {
494
412
  const results: Set<Gtk.Widget> = new Set();
495
413
 
496
414
  for (const widget of traverse(container)) {
497
415
  collectLabelMatches(results, widget, text, options);
498
416
  }
499
417
 
500
- return [...results].filter((widget) => isMatchingWidgetType(widget, options));
418
+ return [...results].filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
501
419
  }
502
420
 
503
421
  /**
@@ -507,12 +425,18 @@ function queryAllByLabelText(container: Container, text: Matcher, options?: Matc
507
425
  * @param options Text matching options.
508
426
  * @returns Every matching widget, or an empty array when none match.
509
427
  */
510
- function queryAllByText(container: Container, text: Matcher, options?: MatcherOptions): Gtk.Widget[] {
511
- return findAll(
428
+ function queryAllByText<T extends Gtk.Accessible = Gtk.Widget>(
429
+ container: Container,
430
+ text: Matcher,
431
+ options?: MatcherOptions<T>,
432
+ ): T[] {
433
+ const matches = findAll(
512
434
  container,
513
435
  (widget) =>
514
436
  isMatchingWidgetType(widget, options) && isTextMatch(getWidgetLabelText(widget), text, widget, options),
515
437
  );
438
+
439
+ return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
516
440
  }
517
441
 
518
442
  /**
@@ -522,11 +446,17 @@ function queryAllByText(container: Container, text: Matcher, options?: MatcherOp
522
446
  * @param options Text matching options.
523
447
  * @returns Every matching widget, or an empty array when none match.
524
448
  */
525
- function queryAllByName(container: Container, name: Matcher, options?: MatcherOptions): Gtk.Widget[] {
526
- return findAll(
449
+ function queryAllByName<T extends Gtk.Accessible = Gtk.Widget>(
450
+ container: Container,
451
+ name: Matcher,
452
+ options?: MatcherOptions<T>,
453
+ ): T[] {
454
+ const matches = findAll(
527
455
  container,
528
456
  (widget) => isMatchingWidgetType(widget, options) && isTextMatch(getWidgetName(widget), name, widget, options),
529
457
  );
458
+
459
+ return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
530
460
  }
531
461
 
532
462
  /**
@@ -536,17 +466,19 @@ function queryAllByName(container: Container, name: Matcher, options?: MatcherOp
536
466
  * @param options Text matching options.
537
467
  * @returns Every matching widget, or an empty array when none match.
538
468
  */
539
- function queryAllByPlaceholderText(
469
+ function queryAllByPlaceholderText<T extends Gtk.Accessible = Gtk.Widget>(
540
470
  container: Container,
541
471
  text: Matcher,
542
- options?: MatcherOptions,
543
- ): Gtk.Widget[] {
544
- return findAll(
472
+ options?: MatcherOptions<T>,
473
+ ): T[] {
474
+ const matches = findAll(
545
475
  container,
546
476
  (widget) =>
547
477
  isMatchingWidgetType(widget, options) &&
548
478
  isTextMatch(getWidgetPlaceholderText(widget), text, widget, options),
549
479
  );
480
+
481
+ return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
550
482
  }
551
483
 
552
484
  /**
@@ -556,13 +488,19 @@ function queryAllByPlaceholderText(
556
488
  * @param options Text matching options.
557
489
  * @returns Every matching widget, or an empty array when none match.
558
490
  */
559
- function queryAllByDisplayValue(container: Container, value: Matcher, options?: MatcherOptions): Gtk.Widget[] {
560
- return findAll(
491
+ function queryAllByDisplayValue<T extends Gtk.Accessible = Gtk.Widget>(
492
+ container: Container,
493
+ value: Matcher,
494
+ options?: MatcherOptions<T>,
495
+ ): T[] {
496
+ const matches = findAll(
561
497
  container,
562
498
  (widget) =>
563
499
  isMatchingWidgetType(widget, options) &&
564
500
  isTextMatch(getWidgetDisplayValue(widget), value, widget, options),
565
501
  );
502
+
503
+ return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
566
504
  }
567
505
 
568
506
  export {
@@ -1,5 +1,5 @@
1
1
  import type * as Gtk from "@gtkx/gi/gtk";
2
- import { camelCase } from "@gtkx/utils";
2
+ import { getProperty } from "@gtkx/gi/gobject";
3
3
  import type { Matcher, MatcherOptions } from "./types.js";
4
4
  import { buildQueries } from "./build-queries.js";
5
5
  import { getElementError } from "./errors.js";
@@ -27,7 +27,13 @@ const queryHelpers: QueryHelpers = {
27
27
  };
28
28
 
29
29
  const readObjectProperty = (widget: Gtk.Widget, property: string): string | null => {
30
- const value: unknown = Reflect.get(widget, camelCase(property));
30
+ let value: unknown;
31
+
32
+ try {
33
+ value = Reflect.apply(getProperty, undefined, [widget, property]);
34
+ } catch {
35
+ return null;
36
+ }
31
37
 
32
38
  if (typeof value === "string") {
33
39
  return value;
@@ -51,18 +57,20 @@ const readObjectProperty = (widget: Gtk.Widget, property: string): string | null
51
57
  * @param options Text matching options.
52
58
  * @returns Every matching widget, or an empty array when none match.
53
59
  */
54
- function queryAllByObjectProperty(
60
+ function queryAllByObjectProperty<T extends Gtk.Accessible = Gtk.Widget>(
55
61
  property: string,
56
62
  container: Container,
57
63
  text: Matcher,
58
- options?: MatcherOptions,
59
- ): Gtk.Widget[] {
60
- return findAll(
64
+ options?: MatcherOptions<T>,
65
+ ): T[] {
66
+ const matches = findAll(
61
67
  container,
62
68
  (widget) =>
63
69
  isMatchingWidgetType(widget, options) &&
64
70
  isTextMatch(readObjectProperty(widget, property), text, widget, options),
65
71
  );
72
+
73
+ return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
66
74
  }
67
75
 
68
76
  /**
@@ -76,12 +84,12 @@ function queryAllByObjectProperty(
76
84
  * @returns The matching widget, or null when none match.
77
85
  * @throws When more than one widget matches.
78
86
  */
79
- function queryByObjectProperty(
87
+ function queryByObjectProperty<T extends Gtk.Accessible = Gtk.Widget>(
80
88
  property: string,
81
89
  container: Container,
82
90
  text: Matcher,
83
- options?: MatcherOptions,
84
- ): Gtk.Widget | null {
91
+ options?: MatcherOptions<T>,
92
+ ): T | null {
85
93
  const matches = queryAllByObjectProperty(property, container, text, options);
86
94
 
87
95
  if (matches.length > 1) {
package/src/render.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import type { RootElement } from "@gtkx/react";
1
+ import type { CaughtErrorInfo, RootElement } from "@gtkx/react";
2
2
  import * as Gtk from "@gtkx/gi/gtk";
3
3
  import {
4
4
  createReconcilerRoot,
@@ -21,6 +21,7 @@ import { clearScreen, setScreen } from "./screen.js";
21
21
  import { captureScreen } from "./screenshot.js";
22
22
  import { type Container, roots, TOPLEVELS } from "./traversal.js";
23
23
  import { resetClipboard } from "./user-event/index.js";
24
+ import { requireWidget } from "./widget-target.js";
24
25
  import { findPresentedWindowFailure, findRenderedWindowFailure, mappedToplevels } from "./window-state.js";
25
26
  import { within } from "./within.js";
26
27
 
@@ -125,6 +126,10 @@ const resolveContainer = (container: RenderOptions["container"]): ResolvedContai
125
126
  return { containerInfo: container, window: null };
126
127
  }
127
128
 
129
+ if (container !== undefined) {
130
+ requireWidget(container);
131
+ }
132
+
128
133
  const window = createHarnessWindow();
129
134
  window.setDecorated(false);
130
135
 
@@ -188,7 +193,7 @@ const settleRender = async (
188
193
 
189
194
  const renderErrorHandlers = <Q extends QueryMap>(options: RenderOptions<Q> | undefined) => ({
190
195
  onUncaughtError: handleError,
191
- onCaughtError: (error: unknown, errorInfo: ErrorInfo): void => {
196
+ onCaughtError: (error: unknown, errorInfo: CaughtErrorInfo): void => {
192
197
  handleError(error);
193
198
  options?.onCaughtError?.(error, errorInfo);
194
199
  },
@@ -3,6 +3,7 @@ import { sortStringsBy } from "@gtkx/utils";
3
3
  import { type Container, traverse } from "./traversal.js";
4
4
  import { getWidgetAccessibleName, getWidgetLevel } from "./widget-accessible-properties.js";
5
5
  import { getTypeTag } from "./widget-getters.js";
6
+ import { requireWidget } from "./widget-target.js";
6
7
 
7
8
  const ROLE_NAMES_BY_VALUE = enumNamesByValue(Gtk.AccessibleRole);
8
9
 
@@ -125,6 +126,7 @@ const logRoles = (container: Container): void => {
125
126
  *
126
127
  * @param widget The widget to read the level from.
127
128
  */
128
- const computeHeadingLevel = (widget: Gtk.Widget): number | undefined => getWidgetLevel(widget) ?? undefined;
129
+ const computeHeadingLevel = (widget: Gtk.Accessible): number | undefined =>
130
+ getWidgetLevel(requireWidget(widget)) ?? undefined;
129
131
 
130
132
  export { computeHeadingLevel, formatRole, formatRoleList, getRoles, prettyRoles, logRoles };
package/src/screenshot.ts CHANGED
@@ -9,6 +9,7 @@ import { getConfig } from "./config.js";
9
9
  import { now } from "./timers.js";
10
10
  import { descendants } from "./traversal.js";
11
11
  import { waitFor } from "./wait-for.js";
12
+ import { requireWidget } from "./widget-target.js";
12
13
  import { activeToplevel, mappedToplevels } from "./window-state.js";
13
14
 
14
15
  type FrameProbe = {
@@ -31,6 +32,20 @@ type CaptureOutcome =
31
32
  { status: "captured"; result: ScreenshotResult } |
32
33
  { status: "stalled"; failure: CaptureFailure };
33
34
 
35
+ type BackgroundSnapshot = {
36
+ renderBackground(...args: [context: object, x: number, y: number, width: number, height: number]): void;
37
+ };
38
+
39
+ type StyledWidget = {
40
+ getStyleContext(): object;
41
+ };
42
+
43
+ type RenderSize = {
44
+ scale: number;
45
+ width: number;
46
+ height: number;
47
+ };
48
+
34
49
  const DEFAULT_SCREENSHOT_INTERVAL = 10;
35
50
  const PRESENTATION_PROBE_MS = 250;
36
51
 
@@ -47,9 +62,7 @@ const OTHER_FAILURE_HINT =
47
62
 
48
63
  const NOTHING_ON_SCREEN_MESSAGE = "Nothing is on screen to capture: no toplevel window is mapped";
49
64
 
50
- const bytesToBase64 = (bytes: Uint8Array | number[]): string => {
51
- return Buffer.from(bytes).toString("base64");
52
- };
65
+ const bytesToBase64 = (bytes: Uint8Array | number[]): string => Buffer.from(bytes).toString("base64");
53
66
 
54
67
  const describeWidgetState = (widget: Gtk.Widget): string =>
55
68
  `realized=${String(widget.getRealized())} mapped=${String(widget.getMapped())} ` +
@@ -181,16 +194,43 @@ const getChildrenNode = (widget: Gtk.Widget, scale: number): Gsk.RenderNode | nu
181
194
  return snapshot.toNode();
182
195
  };
183
196
 
197
+ const addWindowBackground = (
198
+ widget: Gtk.Widget,
199
+ node: Gsk.RenderNode,
200
+ { scale, width, height }: RenderSize,
201
+ ): Gsk.RenderNode => {
202
+ if (!(widget instanceof Gtk.Window)) {
203
+ return node;
204
+ }
205
+
206
+ const snapshot = new Gtk.Snapshot();
207
+ const backgroundSnapshot: BackgroundSnapshot = snapshot;
208
+ const styledWidget: StyledWidget = widget;
209
+ snapshot.save();
210
+ snapshot.scale(scale, scale);
211
+ backgroundSnapshot.renderBackground(styledWidget.getStyleContext(), 0, 0, width, height);
212
+ snapshot.restore();
213
+ snapshot.appendNode(node);
214
+
215
+ return snapshot.toNode() ?? node;
216
+ };
217
+
218
+ const getFallbackNode = (widget: Gtk.Widget, size: RenderSize): Gsk.RenderNode | null => {
219
+ const content = getChildrenNode(widget, size.scale);
220
+
221
+ return content === null ? null : addWindowBackground(widget, content, size);
222
+ };
223
+
184
224
  const getRenderNode = (widget: Gtk.Widget, scale: number, width: number, height: number): Gsk.RenderNode | null => {
185
- const presented = getPaintableNode(widget, scale, width, height);
225
+ const content = getPaintableNode(widget, scale, width, height);
186
226
 
187
- if (presented) {
188
- return presented;
227
+ if (content) {
228
+ return content;
189
229
  }
190
230
 
191
231
  allocateRoot(widget);
192
232
 
193
- return getPaintableNode(widget, scale, width, height) ?? getChildrenNode(widget, scale);
233
+ return getPaintableNode(widget, scale, width, height) ?? getFallbackNode(widget, { scale, width, height });
194
234
  };
195
235
 
196
236
  const renderToPng = (widget: Gtk.Widget, node: Gsk.RenderNode, width: number, height: number): ScreenshotResult => {
@@ -303,7 +343,8 @@ const captureUntilPaintable = (
303
343
  * @param options Optional scale, timeout, retry interval, and output path.
304
344
  * @returns The base64-encoded PNG data along with its mime type and dimensions.
305
345
  */
306
- const screenshot = async (widget: Gtk.Widget, options?: ScreenshotOptions): Promise<ScreenshotResult> => {
346
+ const screenshot = async (target: Gtk.Accessible, options?: ScreenshotOptions): Promise<ScreenshotResult> => {
347
+ const widget = requireWidget(target);
307
348
  const scale = options?.scale ?? 1;
308
349
 
309
350
  if (!Number.isFinite(scale) || scale <= 0) {
@@ -9,6 +9,7 @@ import {
9
9
  getWidgetOwnLabel,
10
10
  getWidgetPlaceholderText,
11
11
  } from "./widget-accessible-properties.js";
12
+ import { requireWidget } from "./widget-target.js";
12
13
 
13
14
  /** The query variant a suggestion targets. */
14
15
  type Variant = "get" | "getAll" | "query" | "queryAll" | "find" | "findAll";
@@ -75,10 +76,11 @@ const textSuggestion = (queryName: Method, variant: Variant, value: string | nul
75
76
  * @returns The best available suggestion, or undefined when no query applies.
76
77
  */
77
78
  const getSuggestedQuery = (
78
- widget: Gtk.Widget,
79
+ target: Gtk.Accessible,
79
80
  variant: Variant = "get",
80
81
  method?: Method,
81
82
  ): Suggestion | undefined => {
83
+ const widget = requireWidget(target);
82
84
  const builders: Record<Method, () => Suggestion | undefined> = {
83
85
  Role: () => roleSuggestion(widget, variant),
84
86
  LabelText: () =>