@gtkx/testing 0.21.0 → 1.0.0-rc.2

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 (216) hide show
  1. package/README.md +137 -34
  2. package/dist/act.d.ts +19 -0
  3. package/dist/act.d.ts.map +1 -0
  4. package/dist/act.js +56 -0
  5. package/dist/act.js.map +1 -0
  6. package/dist/bound-queries.d.ts +33 -0
  7. package/dist/bound-queries.d.ts.map +1 -0
  8. package/dist/bound-queries.js +2 -0
  9. package/dist/bound-queries.js.map +1 -0
  10. package/dist/build-queries.d.ts +16 -0
  11. package/dist/build-queries.d.ts.map +1 -0
  12. package/dist/build-queries.js +115 -0
  13. package/dist/build-queries.js.map +1 -0
  14. package/dist/cleanup-registry.d.ts +5 -0
  15. package/dist/cleanup-registry.d.ts.map +1 -0
  16. package/dist/cleanup-registry.js +21 -0
  17. package/dist/cleanup-registry.js.map +1 -0
  18. package/dist/config.d.ts +20 -52
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +17 -46
  21. package/dist/config.js.map +1 -1
  22. package/dist/editable.d.ts +8 -0
  23. package/dist/editable.d.ts.map +1 -0
  24. package/dist/editable.js +26 -0
  25. package/dist/editable.js.map +1 -0
  26. package/dist/errors.d.ts +30 -0
  27. package/dist/errors.d.ts.map +1 -0
  28. package/dist/errors.js +135 -0
  29. package/dist/errors.js.map +1 -0
  30. package/dist/fire-event.d.ts +8 -25
  31. package/dist/fire-event.d.ts.map +1 -1
  32. package/dist/fire-event.js +11 -30
  33. package/dist/fire-event.js.map +1 -1
  34. package/dist/frame-sync.d.ts +4 -0
  35. package/dist/frame-sync.d.ts.map +1 -0
  36. package/dist/frame-sync.js +37 -0
  37. package/dist/frame-sync.js.map +1 -0
  38. package/dist/index.d.ts +17 -12
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +11 -7
  41. package/dist/index.js.map +1 -1
  42. package/dist/matchers.d.ts +59 -0
  43. package/dist/matchers.d.ts.map +1 -0
  44. package/dist/matchers.js +117 -0
  45. package/dist/matchers.js.map +1 -0
  46. package/dist/pretty-widget.d.ts +24 -26
  47. package/dist/pretty-widget.d.ts.map +1 -1
  48. package/dist/pretty-widget.js +111 -117
  49. package/dist/pretty-widget.js.map +1 -1
  50. package/dist/queries.d.ts +190 -180
  51. package/dist/queries.d.ts.map +1 -1
  52. package/dist/queries.js +291 -320
  53. package/dist/queries.js.map +1 -1
  54. package/dist/register-test-hooks.d.ts +2 -0
  55. package/dist/register-test-hooks.d.ts.map +1 -0
  56. package/dist/register-test-hooks.js +16 -0
  57. package/dist/register-test-hooks.js.map +1 -0
  58. package/dist/render-hook.d.ts +15 -35
  59. package/dist/render-hook.d.ts.map +1 -1
  60. package/dist/render-hook.js +9 -45
  61. package/dist/render-hook.js.map +1 -1
  62. package/dist/render.d.ts +13 -42
  63. package/dist/render.d.ts.map +1 -1
  64. package/dist/render.js +138 -121
  65. package/dist/render.js.map +1 -1
  66. package/dist/role-helpers.d.ts +19 -53
  67. package/dist/role-helpers.d.ts.map +1 -1
  68. package/dist/role-helpers.js +48 -63
  69. package/dist/role-helpers.js.map +1 -1
  70. package/dist/screen.d.ts +7 -63
  71. package/dist/screen.d.ts.map +1 -1
  72. package/dist/screen.js +24 -110
  73. package/dist/screen.js.map +1 -1
  74. package/dist/screenshot.d.ts +21 -23
  75. package/dist/screenshot.d.ts.map +1 -1
  76. package/dist/screenshot.js +98 -50
  77. package/dist/screenshot.js.map +1 -1
  78. package/dist/suggestions.d.ts +27 -0
  79. package/dist/suggestions.d.ts.map +1 -0
  80. package/dist/suggestions.js +61 -0
  81. package/dist/suggestions.js.map +1 -0
  82. package/dist/traversal.d.ts +12 -10
  83. package/dist/traversal.d.ts.map +1 -1
  84. package/dist/traversal.js +36 -15
  85. package/dist/traversal.js.map +1 -1
  86. package/dist/types.d.ts +115 -169
  87. package/dist/types.d.ts.map +1 -1
  88. package/dist/types.js.map +1 -1
  89. package/dist/user-event/adjustment.d.ts +10 -0
  90. package/dist/user-event/adjustment.d.ts.map +1 -0
  91. package/dist/user-event/adjustment.js +35 -0
  92. package/dist/user-event/adjustment.js.map +1 -0
  93. package/dist/user-event/click.d.ts +8 -0
  94. package/dist/user-event/click.d.ts.map +1 -0
  95. package/dist/user-event/click.js +73 -0
  96. package/dist/user-event/click.js.map +1 -0
  97. package/dist/user-event/controller.d.ts +8 -0
  98. package/dist/user-event/controller.d.ts.map +1 -0
  99. package/dist/user-event/controller.js +31 -0
  100. package/dist/user-event/controller.js.map +1 -0
  101. package/dist/user-event/dispatch.d.ts +7 -0
  102. package/dist/user-event/dispatch.d.ts.map +1 -0
  103. package/dist/user-event/dispatch.js +11 -0
  104. package/dist/user-event/dispatch.js.map +1 -0
  105. package/dist/user-event/event-wrapper.d.ts +4 -0
  106. package/dist/user-event/event-wrapper.d.ts.map +1 -0
  107. package/dist/user-event/event-wrapper.js +59 -0
  108. package/dist/user-event/event-wrapper.js.map +1 -0
  109. package/dist/user-event/gesture.d.ts +35 -0
  110. package/dist/user-event/gesture.d.ts.map +1 -0
  111. package/dist/user-event/gesture.js +138 -0
  112. package/dist/user-event/gesture.js.map +1 -0
  113. package/dist/user-event/index.d.ts +51 -0
  114. package/dist/user-event/index.d.ts.map +1 -0
  115. package/dist/user-event/index.js +42 -0
  116. package/dist/user-event/index.js.map +1 -0
  117. package/dist/user-event/keyboard.d.ts +10 -0
  118. package/dist/user-event/keyboard.d.ts.map +1 -0
  119. package/dist/user-event/keyboard.js +204 -0
  120. package/dist/user-event/keyboard.js.map +1 -0
  121. package/dist/user-event/pointer.d.ts +10 -0
  122. package/dist/user-event/pointer.d.ts.map +1 -0
  123. package/dist/user-event/pointer.js +30 -0
  124. package/dist/user-event/pointer.js.map +1 -0
  125. package/dist/user-event/selection.d.ts +5 -0
  126. package/dist/user-event/selection.d.ts.map +1 -0
  127. package/dist/user-event/selection.js +105 -0
  128. package/dist/user-event/selection.js.map +1 -0
  129. package/dist/user-event/state.d.ts +7 -0
  130. package/dist/user-event/state.d.ts.map +1 -0
  131. package/dist/user-event/state.js +3 -0
  132. package/dist/user-event/state.js.map +1 -0
  133. package/dist/user-event/text.d.ts +18 -0
  134. package/dist/user-event/text.d.ts.map +1 -0
  135. package/dist/user-event/text.js +113 -0
  136. package/dist/user-event/text.js.map +1 -0
  137. package/dist/wait-for.d.ts +16 -36
  138. package/dist/wait-for.d.ts.map +1 -1
  139. package/dist/wait-for.js +74 -70
  140. package/dist/wait-for.js.map +1 -1
  141. package/dist/widget-accessible-properties.d.ts +34 -0
  142. package/dist/widget-accessible-properties.d.ts.map +1 -0
  143. package/dist/widget-accessible-properties.js +260 -0
  144. package/dist/widget-accessible-properties.js.map +1 -0
  145. package/dist/within.d.ts +10 -26
  146. package/dist/within.d.ts.map +1 -1
  147. package/dist/within.js +16 -25
  148. package/dist/within.js.map +1 -1
  149. package/package.json +23 -12
  150. package/src/act.ts +77 -0
  151. package/src/bound-queries.ts +37 -0
  152. package/src/build-queries.ts +226 -0
  153. package/src/cleanup-registry.ts +27 -0
  154. package/src/config.ts +35 -62
  155. package/src/editable.ts +37 -0
  156. package/src/errors.ts +191 -0
  157. package/src/fire-event.ts +13 -38
  158. package/src/frame-sync.ts +51 -0
  159. package/src/index.ts +58 -15
  160. package/src/matchers.ts +251 -0
  161. package/src/pretty-widget.ts +163 -130
  162. package/src/queries.ts +485 -396
  163. package/src/register-test-hooks.ts +19 -0
  164. package/src/render-hook.tsx +38 -45
  165. package/src/render.tsx +192 -155
  166. package/src/role-helpers.ts +65 -72
  167. package/src/screen.ts +26 -114
  168. package/src/screenshot.ts +128 -56
  169. package/src/suggestions.ts +106 -0
  170. package/src/traversal.ts +56 -22
  171. package/src/types.ts +144 -172
  172. package/src/user-event/adjustment.ts +62 -0
  173. package/src/user-event/click.ts +99 -0
  174. package/src/user-event/controller.ts +58 -0
  175. package/src/user-event/dispatch.ts +25 -0
  176. package/src/user-event/event-wrapper.ts +80 -0
  177. package/src/user-event/gesture.ts +239 -0
  178. package/src/user-event/index.ts +81 -0
  179. package/src/user-event/keyboard.ts +290 -0
  180. package/src/user-event/pointer.ts +46 -0
  181. package/src/user-event/selection.ts +159 -0
  182. package/src/user-event/state.ts +8 -0
  183. package/src/user-event/text.ts +172 -0
  184. package/src/wait-for.ts +117 -78
  185. package/src/widget-accessible-properties.ts +388 -0
  186. package/src/within.ts +29 -27
  187. package/dist/bind-queries.d.ts +0 -14
  188. package/dist/bind-queries.d.ts.map +0 -1
  189. package/dist/bind-queries.js +0 -29
  190. package/dist/bind-queries.js.map +0 -1
  191. package/dist/error-builder.d.ts +0 -28
  192. package/dist/error-builder.d.ts.map +0 -1
  193. package/dist/error-builder.js +0 -81
  194. package/dist/error-builder.js.map +0 -1
  195. package/dist/timing.d.ts +0 -18
  196. package/dist/timing.d.ts.map +0 -1
  197. package/dist/timing.js +0 -18
  198. package/dist/timing.js.map +0 -1
  199. package/dist/user-event.d.ts +0 -138
  200. package/dist/user-event.d.ts.map +0 -1
  201. package/dist/user-event.js +0 -411
  202. package/dist/user-event.js.map +0 -1
  203. package/dist/widget-text.d.ts +0 -69
  204. package/dist/widget-text.d.ts.map +0 -1
  205. package/dist/widget-text.js +0 -177
  206. package/dist/widget-text.js.map +0 -1
  207. package/dist/widget.d.ts +0 -3
  208. package/dist/widget.d.ts.map +0 -1
  209. package/dist/widget.js +0 -13
  210. package/dist/widget.js.map +0 -1
  211. package/src/bind-queries.ts +0 -52
  212. package/src/error-builder.ts +0 -102
  213. package/src/timing.ts +0 -17
  214. package/src/user-event.ts +0 -492
  215. package/src/widget-text.ts +0 -202
  216. package/src/widget.ts +0 -15
package/src/queries.ts CHANGED
@@ -1,52 +1,312 @@
1
- import * as Gtk from "@gtkx/ffi/gtk";
2
- import { buildMultipleFoundError, buildNotFoundError } from "./error-builder.js";
1
+ import * as Gtk from "@gtkx/gi/gtk";
2
+ import type { ByRoleOptions, ByRoleValue, Matcher, MatcherOptions, NormalizerFn, NormalizerOptions } from "./types.js";
3
+ import { buildQueries, type BuiltQueries, type QueryAllBy } from "./build-queries.js";
4
+ import { multipleFoundError, notFoundError } from "./errors.js";
3
5
  import { type Container, findAll, traverse } from "./traversal.js";
4
- import type { ByRoleOptions, TextMatch, TextMatchOptions } from "./types.js";
5
- import { waitFor } from "./wait-for.js";
6
6
  import {
7
7
  getWidgetAccessibleName,
8
+ getWidgetBusyState,
8
9
  getWidgetCheckedState,
10
+ getWidgetDescription,
11
+ getWidgetDisplayValue,
9
12
  getWidgetExpandedState,
13
+ getWidgetLabelledByText,
14
+ getWidgetLabelText,
15
+ getWidgetLevel,
10
16
  getWidgetName,
17
+ getWidgetOwnLabel,
18
+ getWidgetPlaceholderText,
11
19
  getWidgetPressedState,
12
20
  getWidgetSelectedState,
13
- getWidgetText,
14
- } from "./widget-text.js";
21
+ getWidgetValue,
22
+ isInaccessible,
23
+ } from "./widget-accessible-properties.js";
24
+
25
+ type QueryFamilyReturns = {
26
+ queryBy: Gtk.Widget | null;
27
+ queryAllBy: Gtk.Widget[];
28
+ getBy: Gtk.Widget;
29
+ getAllBy: Gtk.Widget[];
30
+ findBy: Promise<Gtk.Widget>;
31
+ findAllBy: Promise<Gtk.Widget[]>;
32
+ };
15
33
 
16
- const buildNormalizer = (options?: TextMatchOptions): ((text: string) => string) => {
17
- if (options?.normalizer) {
18
- return options.normalizer;
19
- }
34
+ type NamedFamily<Suffix extends string, Args extends unknown[]> = {
35
+ [K in keyof QueryFamilyReturns as `${K & string}${Suffix}`]: (
36
+ container: Container,
37
+ ...args: Args
38
+ ) => QueryFamilyReturns[K];
39
+ };
40
+
41
+ type BuiltinQueries = NamedFamily<"Role", [role: Gtk.AccessibleRole, options?: ByRoleOptions]> &
42
+ NamedFamily<"LabelText", [text: Matcher, options?: MatcherOptions]> &
43
+ NamedFamily<"Text", [text: Matcher, options?: MatcherOptions]> &
44
+ NamedFamily<"Name", [name: Matcher, options?: MatcherOptions]> &
45
+ NamedFamily<"PlaceholderText", [text: Matcher, options?: MatcherOptions]> &
46
+ NamedFamily<"DisplayValue", [value: Matcher, options?: MatcherOptions]>;
47
+
48
+ const roleQueries = nameQueryFamily(
49
+ "Role",
50
+ queryAllByRole,
51
+ buildQueries<[role: Gtk.AccessibleRole, options?: ByRoleOptions]>(
52
+ "Role",
53
+ queryAllByRole,
54
+ (container, matches, role, options) =>
55
+ multipleFoundError(container, { queryType: "role", role, options }, matches),
56
+ (container, role, options) => notFoundError(container, { queryType: "role", role, options }),
57
+ ),
58
+ );
59
+
60
+ const labelTextQueries = nameQueryFamily(
61
+ "LabelText",
62
+ queryAllByLabelText,
63
+ buildQueries<[text: Matcher, options?: MatcherOptions]>(
64
+ "LabelText",
65
+ queryAllByLabelText,
66
+ (container, matches, text) => multipleFoundError(container, { queryType: "labelText", text }, matches),
67
+ (container, text) => notFoundError(container, { queryType: "labelText", text }),
68
+ ),
69
+ );
70
+
71
+ const textQueries = nameQueryFamily(
72
+ "Text",
73
+ queryAllByText,
74
+ buildQueries<[text: Matcher, options?: MatcherOptions]>(
75
+ "Text",
76
+ queryAllByText,
77
+ (container, matches, text) => multipleFoundError(container, { queryType: "text", text }, matches),
78
+ (container, text) => notFoundError(container, { queryType: "text", text }),
79
+ ),
80
+ );
81
+
82
+ const nameQueries = nameQueryFamily(
83
+ "Name",
84
+ queryAllByName,
85
+ buildQueries<[name: Matcher, options?: MatcherOptions]>(
86
+ "Name",
87
+ queryAllByName,
88
+ (container, matches, name) => multipleFoundError(container, { queryType: "name", name }, matches),
89
+ (container, name) => notFoundError(container, { queryType: "name", name }),
90
+ ),
91
+ );
92
+
93
+ const placeholderTextQueries = nameQueryFamily(
94
+ "PlaceholderText",
95
+ queryAllByPlaceholderText,
96
+ buildQueries<[text: Matcher, options?: MatcherOptions]>(
97
+ "PlaceholderText",
98
+ queryAllByPlaceholderText,
99
+ (container, matches, text) => multipleFoundError(container, { queryType: "placeholderText", text }, matches),
100
+ (container, text) => notFoundError(container, { queryType: "placeholderText", text }),
101
+ ),
102
+ );
103
+
104
+ const displayValueQueries = nameQueryFamily(
105
+ "DisplayValue",
106
+ queryAllByDisplayValue,
107
+ buildQueries<[value: Matcher, options?: MatcherOptions]>(
108
+ "DisplayValue",
109
+ queryAllByDisplayValue,
110
+ (container, matches, value) => multipleFoundError(container, { queryType: "displayValue", value }, matches),
111
+ (container, value) => notFoundError(container, { queryType: "displayValue", value }),
112
+ ),
113
+ );
114
+
115
+ const builtinQueries: BuiltinQueries = {
116
+ ...roleQueries,
117
+ ...labelTextQueries,
118
+ ...textQueries,
119
+ ...nameQueries,
120
+ ...placeholderTextQueries,
121
+ ...displayValueQueries,
122
+ };
123
+
124
+ /**
125
+ * Returns the single widget with a matching accessible role and options, or null when none match.
126
+ * Throws when more than one matches.
127
+ */
128
+ const queryByRole: BuiltinQueries["queryByRole"] = roleQueries.queryByRole;
129
+ /** Returns every widget with a matching accessible role and options. Throws when none match. */
130
+ const getAllByRole: BuiltinQueries["getAllByRole"] = roleQueries.getAllByRole;
131
+ /**
132
+ * Returns the single widget with a matching accessible role and options.
133
+ * Throws when none or more than one matches.
134
+ */
135
+ const getByRole: BuiltinQueries["getByRole"] = roleQueries.getByRole;
136
+ /**
137
+ * Waits for and returns every widget with a matching accessible role and options, retrying until at
138
+ * least one appears or the timeout elapses.
139
+ */
140
+ const findAllByRole: BuiltinQueries["findAllByRole"] = roleQueries.findAllByRole;
141
+ /**
142
+ * Waits for and returns the single widget with a matching accessible role and options, retrying
143
+ * until it appears or the timeout elapses. Rejects when none or more than one matches.
144
+ */
145
+ const findByRole: BuiltinQueries["findByRole"] = roleQueries.findByRole;
146
+ /**
147
+ * Returns the single widget with matching associated label text, or null when none match.
148
+ * Throws when more than one matches.
149
+ */
150
+ const queryByLabelText: BuiltinQueries["queryByLabelText"] = labelTextQueries.queryByLabelText;
151
+ /** Returns every widget with matching associated label text. Throws when none match. */
152
+ const getAllByLabelText: BuiltinQueries["getAllByLabelText"] = labelTextQueries.getAllByLabelText;
153
+ /** Returns the single widget with matching associated label text. Throws when none or more than one matches. */
154
+ const getByLabelText: BuiltinQueries["getByLabelText"] = labelTextQueries.getByLabelText;
155
+ /**
156
+ * Waits for and returns every widget with matching associated label text, retrying until at least
157
+ * one appears or the timeout elapses.
158
+ */
159
+ const findAllByLabelText: BuiltinQueries["findAllByLabelText"] = labelTextQueries.findAllByLabelText;
160
+ /**
161
+ * Waits for and returns the single widget with matching associated label text, retrying until it
162
+ * appears or the timeout elapses. Rejects when none or more than one matches.
163
+ */
164
+ const findByLabelText: BuiltinQueries["findByLabelText"] = labelTextQueries.findByLabelText;
165
+ /**
166
+ * Returns the single widget with matching rendered text content, or null when none match.
167
+ * Throws when more than one matches.
168
+ */
169
+ const queryByText: BuiltinQueries["queryByText"] = textQueries.queryByText;
170
+ /** Returns every widget with matching rendered text content. Throws when none match. */
171
+ const getAllByText: BuiltinQueries["getAllByText"] = textQueries.getAllByText;
172
+ /** Returns the single widget with matching rendered text content. Throws when none or more than one matches. */
173
+ const getByText: BuiltinQueries["getByText"] = textQueries.getByText;
174
+ /**
175
+ * Waits for and returns every widget with matching rendered text content, retrying until at least
176
+ * one appears or the timeout elapses.
177
+ */
178
+ const findAllByText: BuiltinQueries["findAllByText"] = textQueries.findAllByText;
179
+ /**
180
+ * Waits for and returns the single widget with matching rendered text content, retrying until it
181
+ * appears or the timeout elapses. Rejects when none or more than one matches.
182
+ */
183
+ const findByText: BuiltinQueries["findByText"] = textQueries.findByText;
184
+ /**
185
+ * Returns the single widget with a matching widget name, or null when none match.
186
+ * Throws when more than one matches.
187
+ */
188
+ const queryByName: BuiltinQueries["queryByName"] = nameQueries.queryByName;
189
+ /** Returns every widget with a matching widget name. Throws when none match. */
190
+ const getAllByName: BuiltinQueries["getAllByName"] = nameQueries.getAllByName;
191
+ /** Returns the single widget with a matching widget name. Throws when none or more than one matches. */
192
+ const getByName: BuiltinQueries["getByName"] = nameQueries.getByName;
193
+ /**
194
+ * Waits for and returns every widget with a matching widget name, retrying until at least one
195
+ * appears or the timeout elapses.
196
+ */
197
+ const findAllByName: BuiltinQueries["findAllByName"] = nameQueries.findAllByName;
198
+ /**
199
+ * Waits for and returns the single widget with a matching widget name, retrying until it appears or
200
+ * the timeout elapses. Rejects when none or more than one matches.
201
+ */
202
+ const findByName: BuiltinQueries["findByName"] = nameQueries.findByName;
203
+
204
+ /**
205
+ * Returns the single widget with matching placeholder text, or null when none match.
206
+ * Throws when more than one matches.
207
+ */
208
+ const queryByPlaceholderText: BuiltinQueries["queryByPlaceholderText"] =
209
+ placeholderTextQueries.queryByPlaceholderText;
210
+
211
+ /** Returns every widget with matching placeholder text. Throws when none match. */
212
+ const getAllByPlaceholderText: BuiltinQueries["getAllByPlaceholderText"] =
213
+ placeholderTextQueries.getAllByPlaceholderText;
20
214
 
21
- const trim = options?.trim ?? true;
22
- const collapseWhitespace = options?.collapseWhitespace ?? true;
215
+ /** Returns the single widget with matching placeholder text. Throws when none or more than one matches. */
216
+ const getByPlaceholderText: BuiltinQueries["getByPlaceholderText"] = placeholderTextQueries.getByPlaceholderText;
217
+
218
+ /**
219
+ * Waits for and returns every widget with matching placeholder text, retrying until at least one
220
+ * appears or the timeout elapses.
221
+ */
222
+ const findAllByPlaceholderText: BuiltinQueries["findAllByPlaceholderText"] =
223
+ placeholderTextQueries.findAllByPlaceholderText;
224
+
225
+ /**
226
+ * Waits for and returns the single widget with matching placeholder text, retrying until it appears
227
+ * or the timeout elapses. Rejects when none or more than one matches.
228
+ */
229
+ const findByPlaceholderText: BuiltinQueries["findByPlaceholderText"] =
230
+ placeholderTextQueries.findByPlaceholderText;
231
+
232
+ /**
233
+ * Returns the single widget with a matching display value, or null when none match.
234
+ * Throws when more than one matches.
235
+ */
236
+ const queryByDisplayValue: BuiltinQueries["queryByDisplayValue"] = displayValueQueries.queryByDisplayValue;
237
+ /** Returns every widget with a matching display value. Throws when none match. */
238
+ const getAllByDisplayValue: BuiltinQueries["getAllByDisplayValue"] = displayValueQueries.getAllByDisplayValue;
239
+ /** Returns the single widget with a matching display value. Throws when none or more than one matches. */
240
+ const getByDisplayValue: BuiltinQueries["getByDisplayValue"] = displayValueQueries.getByDisplayValue;
241
+ /**
242
+ * Waits for and returns every widget with a matching display value, retrying until at least one
243
+ * appears or the timeout elapses.
244
+ */
245
+ const findAllByDisplayValue: BuiltinQueries["findAllByDisplayValue"] = displayValueQueries.findAllByDisplayValue;
246
+ /**
247
+ * Waits for and returns the single widget with a matching display value, retrying until it appears
248
+ * or the timeout elapses. Rejects when none or more than one matches.
249
+ */
250
+ const findByDisplayValue: BuiltinQueries["findByDisplayValue"] = displayValueQueries.findByDisplayValue;
23
251
 
252
+ /**
253
+ * Builds the default text normalizer, which optionally trims surrounding whitespace and collapses
254
+ * runs of whitespace into single spaces.
255
+ * @param options Toggles for trimming and whitespace collapsing.
256
+ * @returns A function that normalizes a string for comparison against a matcher.
257
+ */
258
+ const getDefaultNormalizer = ({
259
+ trim = true,
260
+ collapseWhitespace = true,
261
+ }: NormalizerOptions = {}): NormalizerFn => {
24
262
  return (text: string): string => {
25
263
  let result = text;
264
+
26
265
  if (trim) {
27
266
  result = result.trim();
28
267
  }
29
268
 
30
269
  if (collapseWhitespace) {
31
- result = result.replace(/\s+/g, " ");
270
+ result = result.replaceAll(/\s+/g, " ");
32
271
  }
33
272
 
34
273
  return result;
35
274
  };
36
275
  };
37
276
 
38
- const normalizeText = (text: string, options?: TextMatchOptions): string => {
277
+ const buildNormalizer = (options?: MatcherOptions): NormalizerFn => {
278
+ const { normalizer, trim, collapseWhitespace } = options ?? {};
279
+
280
+ if (!normalizer) {
281
+ return getDefaultNormalizer({ trim, collapseWhitespace });
282
+ }
283
+
284
+ if (trim !== undefined || collapseWhitespace !== undefined) {
285
+ throw new Error(
286
+ "trim and collapseWhitespace are not supported with a normalizer. " +
287
+ "If you want to use the default trim and collapseWhitespace logic in your normalizer, " +
288
+ "use \"getDefaultNormalizer({ trim, collapseWhitespace })\" and compose that into your normalizer",
289
+ );
290
+ }
291
+
292
+ return normalizer;
293
+ };
294
+
295
+ const normalizeText = (text: string, options?: MatcherOptions): string => {
39
296
  const normalizer = buildNormalizer(options);
297
+
40
298
  return normalizer(text);
41
299
  };
42
300
 
43
- const matchText = (
301
+ const isTextMatch = (
44
302
  actual: string | null,
45
- expected: TextMatch,
303
+ expected: Matcher,
46
304
  widget: Gtk.Widget,
47
- options?: TextMatchOptions,
305
+ options?: MatcherOptions,
48
306
  ): boolean => {
49
- if (actual === null) return false;
307
+ if (actual === null) {
308
+ return false;
309
+ }
50
310
 
51
311
  const normalizedActual = normalizeText(actual, options);
52
312
 
@@ -56,452 +316,281 @@ const matchText = (
56
316
 
57
317
  if (expected instanceof RegExp) {
58
318
  expected.lastIndex = 0;
319
+
59
320
  return expected.test(normalizedActual);
60
321
  }
61
322
 
62
- const normalizedExpected = normalizeText(expected, options);
63
- const exact = options?.exact ?? true;
64
- return exact
323
+ const normalizedExpected = normalizeText(String(expected), options);
324
+ const isExact = options?.exact ?? true;
325
+
326
+ return isExact
65
327
  ? normalizedActual === normalizedExpected
66
328
  : normalizedActual.toLowerCase().includes(normalizedExpected.toLowerCase());
67
329
  };
68
330
 
69
- const matchByRoleOptions = (widget: Gtk.Widget, options?: ByRoleOptions): boolean => {
70
- if (!options) return true;
71
-
72
- if (options.name !== undefined) {
73
- const text = getWidgetAccessibleName(widget);
74
- if (!matchText(text, options.name, widget, options)) return false;
75
- }
76
-
77
- if (options.checked !== undefined) {
78
- const checked = getWidgetCheckedState(widget);
79
- if (checked !== options.checked) return false;
331
+ const hasMatchingAccessibleName = (widget: Gtk.Widget, options: ByRoleOptions): boolean => {
332
+ if (options.name === undefined) {
333
+ return true;
80
334
  }
81
335
 
82
- if (options.pressed !== undefined) {
83
- const pressed = getWidgetPressedState(widget);
84
- if (pressed !== options.pressed) return false;
85
- }
86
-
87
- if (options.expanded !== undefined) {
88
- const expanded = getWidgetExpandedState(widget);
89
- if (expanded !== options.expanded) return false;
90
- }
336
+ const text = getWidgetAccessibleName(widget);
91
337
 
92
- if (options.selected !== undefined) {
93
- const selected = getWidgetSelectedState(widget);
94
- if (selected !== options.selected) return false;
95
- }
96
-
97
- return true;
98
- };
99
-
100
- /**
101
- * Finds all elements matching a role without throwing.
102
- *
103
- * @param container - The container to search within
104
- * @param role - The GTK accessible role to match
105
- * @param options - Query options including name and state filters
106
- * @returns Array of matching widgets (empty if none found)
107
- */
108
- export const queryAllByRole = (
109
- container: Container,
110
- role: Gtk.AccessibleRole,
111
- options?: ByRoleOptions,
112
- ): Gtk.Widget[] => {
113
- return findAll(container, (node) => {
114
- if (node.getAccessibleRole() !== role) return false;
115
- return matchByRoleOptions(node, options);
116
- });
338
+ return isTextMatch(text, options.name, widget, options);
117
339
  };
118
340
 
119
- /**
120
- * Finds a single element matching a role without throwing.
121
- *
122
- * @param container - The container to search within
123
- * @param role - The GTK accessible role to match
124
- * @param options - Query options including name and state filters
125
- * @returns The matching widget or null if not found
126
- * @throws Error if multiple elements match
127
- */
128
- export const queryByRole = (
129
- container: Container,
130
- role: Gtk.AccessibleRole,
131
- options?: ByRoleOptions,
132
- ): Gtk.Widget | null => {
133
- const matches = queryAllByRole(container, role, options);
341
+ const isNumericValueMatch = (expected: number | undefined, actual: number | null): boolean =>
342
+ expected === undefined || actual === expected;
134
343
 
135
- if (matches.length > 1) {
136
- throw buildMultipleFoundError(container, "role", { role, options }, matches.length);
137
- }
344
+ const hasMatchingAccessibleValue = (widget: Gtk.Widget, value: ByRoleValue, options: ByRoleOptions): boolean => {
345
+ const actual = getWidgetValue(widget);
138
346
 
139
- return matches[0] ?? null;
140
- };
347
+ const numericChecks: [number | undefined, number | null][] = [
348
+ [value.now, actual.now],
349
+ [value.min, actual.min],
350
+ [value.max, actual.max],
351
+ ];
141
352
 
142
- /**
143
- * Finds all elements that are labelled by a GtkLabel whose text matches.
144
- *
145
- * Uses GtkLabel's mnemonic widget association to find form elements
146
- * by their label text. Only returns widgets that are properly labelled
147
- * via GtkLabel's mnemonic-widget property.
148
- *
149
- * @param container - The container to search within
150
- * @param text - Label text to match (string, RegExp, or custom matcher)
151
- * @param options - Query options including normalization
152
- * @returns Array of labelled widgets (empty if none found)
153
- */
154
- export const queryAllByLabelText = (
155
- container: Container,
156
- text: TextMatch,
157
- options?: TextMatchOptions,
158
- ): Gtk.Widget[] => {
159
- const results: Gtk.Widget[] = [];
160
-
161
- for (const node of traverse(container)) {
162
- if (!(node instanceof Gtk.Label)) continue;
163
-
164
- const labelText = node.getLabel();
165
- if (!labelText) continue;
166
- if (!matchText(labelText, text, node, options)) continue;
167
-
168
- const target = node.getMnemonicWidget();
169
- if (target) {
170
- results.push(target);
353
+ for (const [expected, current] of numericChecks) {
354
+ if (!isNumericValueMatch(expected, current)) {
355
+ return false;
171
356
  }
172
357
  }
173
358
 
174
- return results;
175
- };
176
-
177
- /**
178
- * Finds a single element matching label text without throwing.
179
- *
180
- * @param container - The container to search within
181
- * @param text - Text to match (string, RegExp, or custom matcher)
182
- * @param options - Query options including normalization
183
- * @returns The matching widget or null if not found
184
- * @throws Error if multiple elements match
185
- */
186
- export const queryByLabelText = (
187
- container: Container,
188
- text: TextMatch,
189
- options?: TextMatchOptions,
190
- ): Gtk.Widget | null => {
191
- const matches = queryAllByLabelText(container, text, options);
192
-
193
- if (matches.length > 1) {
194
- throw buildMultipleFoundError(container, "labelText", { text, options }, matches.length);
195
- }
196
-
197
- return matches[0] ?? null;
359
+ return value.text === undefined || isTextMatch(actual.text, value.text, widget, options);
198
360
  };
199
361
 
200
- /**
201
- * Finds all elements matching text content without throwing.
202
- *
203
- * @param container - The container to search within
204
- * @param text - Text to match (string, RegExp, or custom matcher)
205
- * @param options - Query options including normalization
206
- * @returns Array of matching widgets (empty if none found)
207
- */
208
- export const queryAllByText = (container: Container, text: TextMatch, options?: TextMatchOptions): Gtk.Widget[] => {
209
- return findAll(container, (node) => {
210
- return matchText(getWidgetText(node), text, node, options);
211
- });
212
- };
213
-
214
- /**
215
- * Finds a single element matching text content without throwing.
216
- *
217
- * @param container - The container to search within
218
- * @param text - Text to match (string, RegExp, or custom matcher)
219
- * @param options - Query options including normalization
220
- * @returns The matching widget or null if not found
221
- * @throws Error if multiple elements match
222
- */
223
- export const queryByText = (container: Container, text: TextMatch, options?: TextMatchOptions): Gtk.Widget | null => {
224
- const matches = queryAllByText(container, text, options);
225
-
226
- if (matches.length > 1) {
227
- throw buildMultipleFoundError(container, "text", { text, options }, matches.length);
362
+ const hasMatchingBooleanStates = (widget: Gtk.Widget, options: ByRoleOptions): boolean => {
363
+ const stateChecks: [boolean | undefined, () => boolean | null][] = [
364
+ [options.checked, () => getWidgetCheckedState(widget)],
365
+ [options.pressed, () => getWidgetPressedState(widget)],
366
+ [options.expanded, () => getWidgetExpandedState(widget)],
367
+ [options.selected, () => getWidgetSelectedState(widget)],
368
+ [options.busy, () => getWidgetBusyState(widget) ?? false],
369
+ ];
370
+
371
+ for (const [expected, getActual] of stateChecks) {
372
+ if (expected !== undefined && getActual() !== expected) {
373
+ return false;
374
+ }
228
375
  }
229
376
 
230
- return matches[0] ?? null;
231
- };
232
-
233
- /**
234
- * Finds all elements matching a widget name without throwing.
235
- *
236
- * @param container - The container to search within
237
- * @param name - Widget name to match (string, RegExp, or custom matcher)
238
- * @param options - Query options including normalization
239
- * @returns Array of matching widgets (empty if none found)
240
- */
241
- export const queryAllByName = (container: Container, name: TextMatch, options?: TextMatchOptions): Gtk.Widget[] => {
242
- return findAll(container, (node) => {
243
- const widgetName = getWidgetName(node);
244
- return matchText(widgetName, name, node, options);
245
- });
377
+ return true;
246
378
  };
247
379
 
248
- /**
249
- * Finds a single element matching a widget name without throwing.
250
- *
251
- * @param container - The container to search within
252
- * @param name - Widget name to match (string, RegExp, or custom matcher)
253
- * @param options - Query options including normalization
254
- * @returns The matching widget or null if not found
255
- * @throws Error if multiple elements match
256
- */
257
- export const queryByName = (container: Container, name: TextMatch, options?: TextMatchOptions): Gtk.Widget | null => {
258
- const matches = queryAllByName(container, name, options);
380
+ const hasMatchingLevelState = (widget: Gtk.Widget, options: ByRoleOptions): boolean =>
381
+ options.level === undefined || getWidgetLevel(widget) === options.level;
259
382
 
260
- if (matches.length > 1) {
261
- throw buildMultipleFoundError(container, "name", { name, options }, matches.length);
262
- }
383
+ const hasMatchingDescriptionState = (widget: Gtk.Widget, options: ByRoleOptions): boolean =>
384
+ options.description === undefined ||
385
+ isTextMatch(getWidgetDescription(widget), options.description, widget, options);
263
386
 
264
- return matches[0] ?? null;
265
- };
387
+ const hasMatchingValueState = (widget: Gtk.Widget, options: ByRoleOptions): boolean =>
388
+ options.value === undefined || hasMatchingAccessibleValue(widget, options.value, options);
266
389
 
267
- const getAllByRole = (container: Container, role: Gtk.AccessibleRole, options?: ByRoleOptions): Gtk.Widget[] => {
268
- const matches = queryAllByRole(container, role, options);
390
+ const hasMatchingAccessibleStates = (widget: Gtk.Widget, options: ByRoleOptions): boolean =>
391
+ hasMatchingBooleanStates(widget, options) &&
392
+ hasMatchingLevelState(widget, options) &&
393
+ hasMatchingDescriptionState(widget, options) &&
394
+ hasMatchingValueState(widget, options);
269
395
 
270
- if (matches.length === 0) {
271
- throw buildNotFoundError(container, "role", { role, options });
396
+ const hasMatchingByRoleOptions = (widget: Gtk.Widget, options?: ByRoleOptions): boolean => {
397
+ if (!options) {
398
+ return true;
272
399
  }
273
400
 
274
- return matches;
401
+ return hasMatchingAccessibleName(widget, options) && hasMatchingAccessibleStates(widget, options);
275
402
  };
276
403
 
277
- const getByRole = (container: Container, role: Gtk.AccessibleRole, options?: ByRoleOptions): Gtk.Widget => {
278
- const matches = getAllByRole(container, role, options);
279
-
280
- if (matches.length > 1) {
281
- throw buildMultipleFoundError(container, "role", { role, options }, matches.length);
282
- }
283
-
284
- return matches[0] as Gtk.Widget;
285
- };
404
+ function nameQueryFamily<Suffix extends string, Args extends unknown[]>(
405
+ suffix: Suffix,
406
+ queryAllBy: QueryAllBy<Args>,
407
+ built: BuiltQueries<Args>,
408
+ ): NamedFamily<Suffix, Args> {
409
+ const family = {
410
+ [`queryBy${suffix}`]: built.queryBy,
411
+ [`queryAllBy${suffix}`]: queryAllBy,
412
+ [`getBy${suffix}`]: built.getBy,
413
+ [`getAllBy${suffix}`]: built.getAllBy,
414
+ [`findBy${suffix}`]: built.findBy,
415
+ [`findAllBy${suffix}`]: built.findAllBy,
416
+ };
286
417
 
287
- const getAllByLabelText = (container: Container, text: TextMatch, options?: TextMatchOptions): Gtk.Widget[] => {
288
- const matches = queryAllByLabelText(container, text, options);
418
+ return family as NamedFamily<Suffix, Args>;
419
+ }
289
420
 
290
- if (matches.length === 0) {
291
- throw buildNotFoundError(container, "labelText", { text, options });
292
- }
421
+ /**
422
+ * Finds every widget under the container whose accessible role matches `role` and that satisfies the given options.
423
+ * Widgets excluded from the accessibility tree are skipped unless `options.hidden` is set.
424
+ * @param container Widget subtree to search.
425
+ * @param role Accessible role to match.
426
+ * @param options Additional accessible name, state, and value constraints.
427
+ * @returns Every matching widget, or an empty array when none match.
428
+ */
429
+ function queryAllByRole(
430
+ container: Container,
431
+ role: Gtk.AccessibleRole,
432
+ options?: ByRoleOptions,
433
+ ): Gtk.Widget[] {
434
+ return findAll(container, (widget) => {
435
+ if (widget.getAccessibleRole() !== role) {
436
+ return false;
437
+ }
293
438
 
294
- return matches;
295
- };
439
+ if (!options?.hidden && isInaccessible(widget)) {
440
+ return false;
441
+ }
296
442
 
297
- const getByLabelText = (container: Container, text: TextMatch, options?: TextMatchOptions): Gtk.Widget => {
298
- const matches = getAllByLabelText(container, text, options);
443
+ return hasMatchingByRoleOptions(widget, options);
444
+ });
445
+ }
299
446
 
300
- if (matches.length > 1) {
301
- throw buildMultipleFoundError(container, "labelText", { text, options }, matches.length);
447
+ const collectMnemonicMatch = (
448
+ widget: Gtk.Widget,
449
+ text: Matcher,
450
+ options: MatcherOptions | undefined,
451
+ ): Gtk.Widget | null => {
452
+ if (!(widget instanceof Gtk.Label)) {
453
+ return null;
302
454
  }
303
455
 
304
- return matches[0] as Gtk.Widget;
305
- };
306
-
307
- const getAllByText = (container: Container, text: TextMatch, options?: TextMatchOptions): Gtk.Widget[] => {
308
- const matches = queryAllByText(container, text, options);
456
+ const labelText = widget.getLabel();
309
457
 
310
- if (matches.length === 0) {
311
- throw buildNotFoundError(container, "text", { text, options });
458
+ if (!labelText || !isTextMatch(labelText, text, widget, options)) {
459
+ return null;
312
460
  }
313
461
 
314
- return matches;
462
+ return widget.getMnemonicWidget();
315
463
  };
316
464
 
317
- const getByText = (container: Container, text: TextMatch, options?: TextMatchOptions): Gtk.Widget => {
318
- const matches = getAllByText(container, text, options);
465
+ const collectLabelMatches = (
466
+ results: Set<Gtk.Widget>,
467
+ widget: Gtk.Widget,
468
+ text: Matcher,
469
+ options: MatcherOptions | undefined,
470
+ ): void => {
471
+ const mnemonicTarget = collectMnemonicMatch(widget, text, options);
319
472
 
320
- if (matches.length > 1) {
321
- throw buildMultipleFoundError(container, "text", { text, options }, matches.length);
473
+ if (mnemonicTarget) {
474
+ results.add(mnemonicTarget);
322
475
  }
323
476
 
324
- return matches[0] as Gtk.Widget;
325
- };
326
-
327
- const getAllByName = (container: Container, name: TextMatch, options?: TextMatchOptions): Gtk.Widget[] => {
328
- const matches = queryAllByName(container, name, options);
477
+ const ownLabel = getWidgetOwnLabel(widget);
329
478
 
330
- if (matches.length === 0) {
331
- throw buildNotFoundError(container, "name", { name, options });
479
+ if (ownLabel !== null && isTextMatch(ownLabel, text, widget, options)) {
480
+ results.add(widget);
332
481
  }
333
482
 
334
- return matches;
335
- };
336
-
337
- const getByName = (container: Container, name: TextMatch, options?: TextMatchOptions): Gtk.Widget => {
338
- const matches = getAllByName(container, name, options);
483
+ const labelledByText = getWidgetLabelledByText(widget);
339
484
 
340
- if (matches.length > 1) {
341
- throw buildMultipleFoundError(container, "name", { name, options }, matches.length);
485
+ if (labelledByText !== null && isTextMatch(labelledByText, text, widget, options)) {
486
+ results.add(widget);
342
487
  }
343
-
344
- return matches[0] as Gtk.Widget;
345
488
  };
346
489
 
347
490
  /**
348
- * Finds a single element by accessible role.
349
- *
350
- * Waits for the element to appear, throwing if not found within timeout.
351
- *
352
- * @param container - The container to search within
353
- * @param role - The GTK accessible role to match
354
- * @param options - Query options including name, state filters, and timeout
355
- * @returns Promise resolving to the matching widget
356
- *
357
- * @example
358
- * ```tsx
359
- * const button = await findByRole(container, Gtk.AccessibleRole.BUTTON, { name: "Submit" });
360
- * ```
491
+ * Finds every widget associated with a label whose text matches: a Gtk.Label mnemonic target, the
492
+ * widget's own accessible label, or its labelled-by relation.
493
+ * @param container Widget subtree to search.
494
+ * @param text Matcher for the label text.
495
+ * @param options Text matching options.
496
+ * @returns Every matching widget, or an empty array when none match.
361
497
  */
362
- export const findByRole = async (
363
- container: Container,
364
- role: Gtk.AccessibleRole,
365
- options?: ByRoleOptions,
366
- ): Promise<Gtk.Widget> =>
367
- waitFor(() => getByRole(container, role, options), {
368
- timeout: options?.timeout,
369
- });
498
+ function queryAllByLabelText(container: Container, text: Matcher, options?: MatcherOptions): Gtk.Widget[] {
499
+ const results: Set<Gtk.Widget> = new Set();
370
500
 
371
- /**
372
- * Finds all elements matching an accessible role.
373
- *
374
- * @param container - The container to search within
375
- * @param role - The GTK accessible role to match
376
- * @param options - Query options including name, state filters, and timeout
377
- * @returns Promise resolving to array of matching widgets
378
- */
379
- export const findAllByRole = async (
380
- container: Container,
381
- role: Gtk.AccessibleRole,
382
- options?: ByRoleOptions,
383
- ): Promise<Gtk.Widget[]> =>
384
- waitFor(() => getAllByRole(container, role, options), {
385
- timeout: options?.timeout,
386
- });
501
+ for (const widget of traverse(container)) {
502
+ collectLabelMatches(results, widget, text, options);
503
+ }
387
504
 
388
- /**
389
- * Finds a single element that is labelled by a GtkLabel whose text matches.
390
- *
391
- * Waits for the element to appear, throwing if not found within timeout.
392
- * Uses GtkLabel's mnemonic widget association to find form elements.
393
- *
394
- * @param container - The container to search within
395
- * @param text - Label text to match (string, RegExp, or custom matcher)
396
- * @param options - Query options including normalization and timeout
397
- * @returns Promise resolving to the labelled widget
398
- *
399
- * @example
400
- * ```tsx
401
- * const input = await findByLabelText(container, "Username");
402
- * ```
403
- */
404
- export const findByLabelText = async (
405
- container: Container,
406
- text: TextMatch,
407
- options?: TextMatchOptions,
408
- ): Promise<Gtk.Widget> =>
409
- waitFor(() => getByLabelText(container, text, options), {
410
- timeout: options?.timeout,
411
- });
505
+ return [...results];
506
+ }
412
507
 
413
508
  /**
414
- * Finds all elements matching label or text content.
415
- *
416
- * @param container - The container to search within
417
- * @param text - Text to match (string, RegExp, or custom matcher)
418
- * @param options - Query options including normalization and timeout
419
- * @returns Promise resolving to array of matching widgets
509
+ * Finds every widget whose rendered text content matches.
510
+ * @param container Widget subtree to search.
511
+ * @param text Matcher for the widget text.
512
+ * @param options Text matching options.
513
+ * @returns Every matching widget, or an empty array when none match.
420
514
  */
421
- export const findAllByLabelText = async (
422
- container: Container,
423
- text: TextMatch,
424
- options?: TextMatchOptions,
425
- ): Promise<Gtk.Widget[]> =>
426
- waitFor(() => getAllByLabelText(container, text, options), {
427
- timeout: options?.timeout,
428
- });
515
+ function queryAllByText(container: Container, text: Matcher, options?: MatcherOptions): Gtk.Widget[] {
516
+ return findAll(container, (widget) => isTextMatch(getWidgetLabelText(widget), text, widget, options));
517
+ }
429
518
 
430
519
  /**
431
- * Finds a single element by visible text content.
432
- *
433
- * Similar to {@link findByLabelText} but focuses on directly visible text.
434
- *
435
- * @param container - The container to search within
436
- * @param text - Text to match (string, RegExp, or custom matcher)
437
- * @param options - Query options including normalization and timeout
438
- * @returns Promise resolving to the matching widget
520
+ * Finds every widget whose widget name matches.
521
+ * @param container Widget subtree to search.
522
+ * @param name Matcher for the widget name.
523
+ * @param options Text matching options.
524
+ * @returns Every matching widget, or an empty array when none match.
439
525
  */
440
- export const findByText = async (
441
- container: Container,
442
- text: TextMatch,
443
- options?: TextMatchOptions,
444
- ): Promise<Gtk.Widget> =>
445
- waitFor(() => getByText(container, text, options), {
446
- timeout: options?.timeout,
447
- });
448
-
449
- /**
450
- * Finds all elements matching visible text content.
451
- *
452
- * @param container - The container to search within
453
- * @param text - Text to match (string, RegExp, or custom matcher)
454
- * @param options - Query options including normalization and timeout
455
- * @returns Promise resolving to array of matching widgets
456
- */
457
- export const findAllByText = async (
458
- container: Container,
459
- text: TextMatch,
460
- options?: TextMatchOptions,
461
- ): Promise<Gtk.Widget[]> =>
462
- waitFor(() => getAllByText(container, text, options), {
463
- timeout: options?.timeout,
464
- });
526
+ function queryAllByName(container: Container, name: Matcher, options?: MatcherOptions): Gtk.Widget[] {
527
+ return findAll(container, (widget) => isTextMatch(getWidgetName(widget), name, widget, options));
528
+ }
465
529
 
466
530
  /**
467
- * Finds a single element by widget name.
468
- *
469
- * Uses the widget's `name` property (gtk_widget_get_name).
470
- * Set via the `name` prop on GTKX components.
471
- *
472
- * @param container - The container to search within
473
- * @param name - Widget name to match (string, RegExp, or custom matcher)
474
- * @param options - Query options including normalization and timeout
475
- * @returns Promise resolving to the matching widget
476
- *
477
- * @example
478
- * ```tsx
479
- * // In component: <GtkButton name="submit-btn" />
480
- * const button = await findByName(container, "submit-btn");
481
- * ```
531
+ * Finds every widget whose placeholder text matches.
532
+ * @param container Widget subtree to search.
533
+ * @param text Matcher for the placeholder text.
534
+ * @param options Text matching options.
535
+ * @returns Every matching widget, or an empty array when none match.
482
536
  */
483
- export const findByName = async (
537
+ function queryAllByPlaceholderText(
484
538
  container: Container,
485
- name: TextMatch,
486
- options?: TextMatchOptions,
487
- ): Promise<Gtk.Widget> =>
488
- waitFor(() => getByName(container, name, options), {
489
- timeout: options?.timeout,
490
- });
539
+ text: Matcher,
540
+ options?: MatcherOptions,
541
+ ): Gtk.Widget[] {
542
+ return findAll(container, (widget) => isTextMatch(getWidgetPlaceholderText(widget), text, widget, options));
543
+ }
491
544
 
492
545
  /**
493
- * Finds all elements matching a widget name pattern.
494
- *
495
- * @param container - The container to search within
496
- * @param name - Widget name to match (string, RegExp, or custom matcher)
497
- * @param options - Query options including normalization and timeout
498
- * @returns Promise resolving to array of matching widgets
546
+ * Finds every widget whose current display value matches.
547
+ * @param container Widget subtree to search.
548
+ * @param value Matcher for the display value.
549
+ * @param options Text matching options.
550
+ * @returns Every matching widget, or an empty array when none match.
499
551
  */
500
- export const findAllByName = async (
501
- container: Container,
502
- name: TextMatch,
503
- options?: TextMatchOptions,
504
- ): Promise<Gtk.Widget[]> =>
505
- waitFor(() => getAllByName(container, name, options), {
506
- timeout: options?.timeout,
507
- });
552
+ function queryAllByDisplayValue(container: Container, value: Matcher, options?: MatcherOptions): Gtk.Widget[] {
553
+ return findAll(container, (widget) => isTextMatch(getWidgetDisplayValue(widget), value, widget, options));
554
+ }
555
+
556
+ export {
557
+ builtinQueries,
558
+ queryByRole,
559
+ getAllByRole,
560
+ getByRole,
561
+ findAllByRole,
562
+ findByRole,
563
+ queryByLabelText,
564
+ getAllByLabelText,
565
+ getByLabelText,
566
+ findAllByLabelText,
567
+ findByLabelText,
568
+ queryByText,
569
+ getAllByText,
570
+ getByText,
571
+ findAllByText,
572
+ findByText,
573
+ queryByName,
574
+ getAllByName,
575
+ getByName,
576
+ findAllByName,
577
+ findByName,
578
+ queryByPlaceholderText,
579
+ getAllByPlaceholderText,
580
+ getByPlaceholderText,
581
+ findAllByPlaceholderText,
582
+ findByPlaceholderText,
583
+ queryByDisplayValue,
584
+ getAllByDisplayValue,
585
+ getByDisplayValue,
586
+ findAllByDisplayValue,
587
+ findByDisplayValue,
588
+ getDefaultNormalizer,
589
+ queryAllByRole,
590
+ queryAllByLabelText,
591
+ queryAllByText,
592
+ queryAllByName,
593
+ queryAllByPlaceholderText,
594
+ queryAllByDisplayValue,
595
+ type BuiltinQueries,
596
+ };