@gtkx/testing 1.0.0-rc.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +5 -5
  2. package/dist/accessible-native.d.ts +16 -0
  3. package/dist/accessible-native.d.ts.map +1 -0
  4. package/dist/accessible-native.js +206 -0
  5. package/dist/accessible-native.js.map +1 -0
  6. package/dist/build-queries.d.ts +27 -10
  7. package/dist/build-queries.d.ts.map +1 -1
  8. package/dist/build-queries.js +22 -24
  9. package/dist/build-queries.js.map +1 -1
  10. package/dist/cleanup-registry.d.ts +1 -1
  11. package/dist/cleanup-registry.d.ts.map +1 -1
  12. package/dist/cleanup-registry.js.map +1 -1
  13. package/dist/errors.d.ts +10 -1
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/errors.js +10 -1
  16. package/dist/errors.js.map +1 -1
  17. package/dist/fire-event.d.ts +35 -6
  18. package/dist/fire-event.d.ts.map +1 -1
  19. package/dist/fire-event.js +23 -7
  20. package/dist/fire-event.js.map +1 -1
  21. package/dist/frame-sync.d.ts +2 -1
  22. package/dist/frame-sync.d.ts.map +1 -1
  23. package/dist/frame-sync.js +15 -5
  24. package/dist/frame-sync.js.map +1 -1
  25. package/dist/harness-window.d.ts +4 -0
  26. package/dist/harness-window.d.ts.map +1 -0
  27. package/dist/harness-window.js +6 -0
  28. package/dist/harness-window.js.map +1 -0
  29. package/dist/index.d.ts +14 -7
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +9 -5
  32. package/dist/index.js.map +1 -1
  33. package/dist/internal.d.ts +4 -0
  34. package/dist/internal.d.ts.map +1 -0
  35. package/dist/internal.js +4 -0
  36. package/dist/internal.js.map +1 -0
  37. package/dist/matchers.d.ts +88 -46
  38. package/dist/matchers.d.ts.map +1 -1
  39. package/dist/matchers.js +156 -18
  40. package/dist/matchers.js.map +1 -1
  41. package/dist/pretty-format.d.ts +2 -0
  42. package/dist/pretty-format.d.ts.map +1 -0
  43. package/dist/pretty-format.js +2 -0
  44. package/dist/pretty-format.js.map +1 -0
  45. package/dist/pretty-widget.d.ts +5 -2
  46. package/dist/pretty-widget.d.ts.map +1 -1
  47. package/dist/pretty-widget.js +73 -56
  48. package/dist/pretty-widget.js.map +1 -1
  49. package/dist/production.d.ts +8 -0
  50. package/dist/production.d.ts.map +1 -0
  51. package/dist/production.js +34 -0
  52. package/dist/production.js.map +1 -0
  53. package/dist/queries.d.ts +98 -56
  54. package/dist/queries.d.ts.map +1 -1
  55. package/dist/queries.js +123 -83
  56. package/dist/queries.js.map +1 -1
  57. package/dist/query-helpers.d.ts +44 -0
  58. package/dist/query-helpers.d.ts.map +1 -0
  59. package/dist/query-helpers.js +57 -0
  60. package/dist/query-helpers.js.map +1 -0
  61. package/dist/render-children.d.ts +9 -0
  62. package/dist/render-children.d.ts.map +1 -0
  63. package/dist/render-children.js +11 -0
  64. package/dist/render-children.js.map +1 -0
  65. package/dist/render.d.ts.map +1 -1
  66. package/dist/render.js +11 -9
  67. package/dist/render.js.map +1 -1
  68. package/dist/role-helpers.d.ts +12 -3
  69. package/dist/role-helpers.d.ts.map +1 -1
  70. package/dist/role-helpers.js +13 -4
  71. package/dist/role-helpers.js.map +1 -1
  72. package/dist/role-naming.d.ts +5 -0
  73. package/dist/role-naming.d.ts.map +1 -0
  74. package/dist/role-naming.js +96 -0
  75. package/dist/role-naming.js.map +1 -0
  76. package/dist/screenshot.d.ts +12 -20
  77. package/dist/screenshot.d.ts.map +1 -1
  78. package/dist/screenshot.js +183 -91
  79. package/dist/screenshot.js.map +1 -1
  80. package/dist/traversal.d.ts +4 -1
  81. package/dist/traversal.d.ts.map +1 -1
  82. package/dist/traversal.js +25 -6
  83. package/dist/traversal.js.map +1 -1
  84. package/dist/types.d.ts +26 -14
  85. package/dist/types.d.ts.map +1 -1
  86. package/dist/types.js.map +1 -1
  87. package/dist/user-event/adjustment.d.ts +3 -0
  88. package/dist/user-event/adjustment.d.ts.map +1 -1
  89. package/dist/user-event/adjustment.js +23 -2
  90. package/dist/user-event/adjustment.js.map +1 -1
  91. package/dist/user-event/click.d.ts +43 -8
  92. package/dist/user-event/click.d.ts.map +1 -1
  93. package/dist/user-event/click.js +175 -44
  94. package/dist/user-event/click.js.map +1 -1
  95. package/dist/user-event/column-header-click.d.ts +4 -0
  96. package/dist/user-event/column-header-click.d.ts.map +1 -0
  97. package/dist/user-event/column-header-click.js +65 -0
  98. package/dist/user-event/column-header-click.js.map +1 -0
  99. package/dist/user-event/event-wrapper.d.ts.map +1 -1
  100. package/dist/user-event/event-wrapper.js +2 -1
  101. package/dist/user-event/event-wrapper.js.map +1 -1
  102. package/dist/user-event/index.d.ts +51 -10
  103. package/dist/user-event/index.d.ts.map +1 -1
  104. package/dist/user-event/index.js +43 -26
  105. package/dist/user-event/index.js.map +1 -1
  106. package/dist/user-event/indexed-children.d.ts +10 -0
  107. package/dist/user-event/indexed-children.d.ts.map +1 -0
  108. package/dist/user-event/indexed-children.js +50 -0
  109. package/dist/user-event/indexed-children.js.map +1 -0
  110. package/dist/user-event/keyboard.d.ts.map +1 -1
  111. package/dist/user-event/keyboard.js +33 -4
  112. package/dist/user-event/keyboard.js.map +1 -1
  113. package/dist/user-event/list-row-click.d.ts +4 -0
  114. package/dist/user-event/list-row-click.d.ts.map +1 -0
  115. package/dist/user-event/list-row-click.js +39 -0
  116. package/dist/user-event/list-row-click.js.map +1 -0
  117. package/dist/user-event/native-click.d.ts +6 -0
  118. package/dist/user-event/native-click.d.ts.map +1 -0
  119. package/dist/user-event/native-click.js +32 -0
  120. package/dist/user-event/native-click.js.map +1 -0
  121. package/dist/user-event/pointer.d.ts +1 -1
  122. package/dist/user-event/pointer.d.ts.map +1 -1
  123. package/dist/user-event/pointer.js +33 -9
  124. package/dist/user-event/pointer.js.map +1 -1
  125. package/dist/user-event/selection.d.ts +11 -8
  126. package/dist/user-event/selection.d.ts.map +1 -1
  127. package/dist/user-event/selection.js +49 -38
  128. package/dist/user-event/selection.js.map +1 -1
  129. package/dist/user-event/text.d.ts +6 -2
  130. package/dist/user-event/text.d.ts.map +1 -1
  131. package/dist/user-event/text.js +68 -18
  132. package/dist/user-event/text.js.map +1 -1
  133. package/dist/widget-accessible-properties.d.ts +14 -26
  134. package/dist/widget-accessible-properties.d.ts.map +1 -1
  135. package/dist/widget-accessible-properties.js +135 -190
  136. package/dist/widget-accessible-properties.js.map +1 -1
  137. package/dist/widget-getters.d.ts +8 -0
  138. package/dist/widget-getters.d.ts.map +1 -0
  139. package/dist/widget-getters.js +19 -0
  140. package/dist/widget-getters.js.map +1 -0
  141. package/dist/within.d.ts +3 -2
  142. package/dist/within.d.ts.map +1 -1
  143. package/dist/within.js +3 -2
  144. package/dist/within.js.map +1 -1
  145. package/package.json +12 -7
  146. package/src/accessible-native.ts +333 -0
  147. package/src/build-queries.ts +47 -41
  148. package/src/cleanup-registry.ts +1 -1
  149. package/src/errors.ts +12 -1
  150. package/src/fire-event.ts +61 -8
  151. package/src/frame-sync.ts +21 -5
  152. package/src/harness-window.ts +9 -0
  153. package/src/index.ts +18 -11
  154. package/src/internal.ts +11 -0
  155. package/src/matchers.ts +428 -70
  156. package/src/pretty-format.ts +1 -0
  157. package/src/pretty-widget.ts +97 -70
  158. package/src/production.ts +52 -0
  159. package/src/queries.ts +127 -89
  160. package/src/query-helpers.ts +97 -0
  161. package/src/render-children.ts +25 -0
  162. package/src/render.tsx +16 -11
  163. package/src/role-helpers.ts +14 -4
  164. package/src/role-naming.ts +99 -0
  165. package/src/screenshot.ts +272 -103
  166. package/src/traversal.ts +44 -6
  167. package/src/types.ts +28 -16
  168. package/src/user-event/adjustment.ts +29 -2
  169. package/src/user-event/click.ts +247 -52
  170. package/src/user-event/column-header-click.ts +90 -0
  171. package/src/user-event/event-wrapper.ts +3 -1
  172. package/src/user-event/index.ts +100 -37
  173. package/src/user-event/indexed-children.ts +75 -0
  174. package/src/user-event/keyboard.ts +52 -4
  175. package/src/user-event/list-row-click.ts +57 -0
  176. package/src/user-event/native-click.ts +46 -0
  177. package/src/user-event/pointer.ts +48 -9
  178. package/src/user-event/selection.ts +67 -41
  179. package/src/user-event/text.ts +92 -21
  180. package/src/widget-accessible-properties.ts +183 -230
  181. package/src/widget-getters.ts +32 -0
  182. package/src/within.ts +3 -2
package/src/queries.ts CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  getWidgetDescription,
18
18
  getWidgetDisplayValue,
19
19
  getWidgetExpandedState,
20
- getWidgetLabelledByText,
20
+ getWidgetExternalLabelText,
21
21
  getWidgetLabelText,
22
22
  getWidgetLevel,
23
23
  getWidgetName,
@@ -25,9 +25,12 @@ import {
25
25
  getWidgetPlaceholderText,
26
26
  getWidgetPressedState,
27
27
  getWidgetSelectedState,
28
- getWidgetValue,
28
+ getWidgetValueText,
29
29
  isInaccessible,
30
30
  isWidgetChecked,
31
+ isWidgetValueMatch,
32
+ namingLabelText,
33
+ type WidgetValueField,
31
34
  } from "./widget-accessible-properties.js";
32
35
 
33
36
  /** The built-in queries in their unbound form, each taking the container to search as its first argument. */
@@ -37,7 +40,6 @@ const roleQueries = nameQueryFamily(
37
40
  "Role",
38
41
  queryAllByRole,
39
42
  buildQueries<[role: Gtk.AccessibleRole, options?: ByRoleOptions]>(
40
- "Role",
41
43
  queryAllByRole,
42
44
  (container, matches, role, options) =>
43
45
  multipleFoundError(container, { queryType: "role", role, options }, matches),
@@ -49,7 +51,6 @@ const labelTextQueries = nameQueryFamily(
49
51
  "LabelText",
50
52
  queryAllByLabelText,
51
53
  buildQueries<[text: Matcher, options?: MatcherOptions]>(
52
- "LabelText",
53
54
  queryAllByLabelText,
54
55
  (container, matches, text) => multipleFoundError(container, { queryType: "labelText", text }, matches),
55
56
  (container, text) => notFoundError(container, { queryType: "labelText", text }),
@@ -60,7 +61,6 @@ const textQueries = nameQueryFamily(
60
61
  "Text",
61
62
  queryAllByText,
62
63
  buildQueries<[text: Matcher, options?: MatcherOptions]>(
63
- "Text",
64
64
  queryAllByText,
65
65
  (container, matches, text) => multipleFoundError(container, { queryType: "text", text }, matches),
66
66
  (container, text) => notFoundError(container, { queryType: "text", text }),
@@ -71,7 +71,6 @@ const nameQueries = nameQueryFamily(
71
71
  "Name",
72
72
  queryAllByName,
73
73
  buildQueries<[name: Matcher, options?: MatcherOptions]>(
74
- "Name",
75
74
  queryAllByName,
76
75
  (container, matches, name) => multipleFoundError(container, { queryType: "name", name }, matches),
77
76
  (container, name) => notFoundError(container, { queryType: "name", name }),
@@ -82,7 +81,6 @@ const placeholderTextQueries = nameQueryFamily(
82
81
  "PlaceholderText",
83
82
  queryAllByPlaceholderText,
84
83
  buildQueries<[text: Matcher, options?: MatcherOptions]>(
85
- "PlaceholderText",
86
84
  queryAllByPlaceholderText,
87
85
  (container, matches, text) => multipleFoundError(container, { queryType: "placeholderText", text }, matches),
88
86
  (container, text) => notFoundError(container, { queryType: "placeholderText", text }),
@@ -93,7 +91,6 @@ const displayValueQueries = nameQueryFamily(
93
91
  "DisplayValue",
94
92
  queryAllByDisplayValue,
95
93
  buildQueries<[value: Matcher, options?: MatcherOptions]>(
96
- "DisplayValue",
97
94
  queryAllByDisplayValue,
98
95
  (container, matches, value) => multipleFoundError(container, { queryType: "displayValue", value }, matches),
99
96
  (container, value) => notFoundError(container, { queryType: "displayValue", value }),
@@ -110,120 +107,159 @@ const builtinQueries = {
110
107
  } as BuiltinQueries;
111
108
 
112
109
  /**
113
- * Returns the single widget with a matching accessible role and options, or null when none match.
114
- * Throws when more than one matches.
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.
115
112
  */
116
113
  const queryByRole: BuiltinQueries["queryByRole"] = builtinQueries.queryByRole;
117
- /** Returns every widget with a matching accessible role and options. Throws when none match. */
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
+ */
118
118
  const getAllByRole: BuiltinQueries["getAllByRole"] = builtinQueries.getAllByRole;
119
119
  /**
120
- * Returns the single widget with a matching accessible role and options.
121
- * Throws when none or more than one matches.
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
122
  */
123
123
  const getByRole: BuiltinQueries["getByRole"] = builtinQueries.getByRole;
124
124
  /**
125
- * Waits for and returns every widget with a matching accessible role and options, retrying until at
126
- * least one appears or the timeout elapses.
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
127
  */
128
128
  const findAllByRole: BuiltinQueries["findAllByRole"] = builtinQueries.findAllByRole;
129
129
  /**
130
- * Waits for and returns the single widget with a matching accessible role and options, retrying
131
- * until it appears or the timeout elapses. Rejects when none or more than one matches.
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.
132
133
  */
133
134
  const findByRole: BuiltinQueries["findByRole"] = builtinQueries.findByRole;
134
135
  /**
135
- * Returns the single widget with matching associated label text, or null when none match.
136
- * Throws when more than one matches.
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.
137
138
  */
138
139
  const queryByLabelText: BuiltinQueries["queryByLabelText"] = builtinQueries.queryByLabelText;
139
- /** Returns every widget with matching associated label text. Throws when none match. */
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
+ */
140
144
  const getAllByLabelText: BuiltinQueries["getAllByLabelText"] = builtinQueries.getAllByLabelText;
141
- /** Returns the single widget with matching associated label text. Throws when none or more than one matches. */
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
+ */
142
149
  const getByLabelText: BuiltinQueries["getByLabelText"] = builtinQueries.getByLabelText;
143
150
  /**
144
- * Waits for and returns every widget with matching associated label text, retrying until at least
145
- * one appears or the timeout elapses.
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.
146
153
  */
147
154
  const findAllByLabelText: BuiltinQueries["findAllByLabelText"] = builtinQueries.findAllByLabelText;
148
155
  /**
149
- * Waits for and returns the single widget with matching associated label text, retrying until it
150
- * appears or the timeout elapses. Rejects when none or more than one matches.
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.
151
159
  */
152
160
  const findByLabelText: BuiltinQueries["findByLabelText"] = builtinQueries.findByLabelText;
153
161
  /**
154
- * Returns the single widget with matching rendered text content, or null when none match.
155
- * Throws when more than one matches.
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.
156
164
  */
157
165
  const queryByText: BuiltinQueries["queryByText"] = builtinQueries.queryByText;
158
- /** Returns every widget with matching rendered text content. Throws when none match. */
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
+ */
159
170
  const getAllByText: BuiltinQueries["getAllByText"] = builtinQueries.getAllByText;
160
- /** Returns the single widget with matching rendered text content. Throws when none or more than one matches. */
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
+ */
161
175
  const getByText: BuiltinQueries["getByText"] = builtinQueries.getByText;
162
176
  /**
163
- * Waits for and returns every widget with matching rendered text content, retrying until at least
164
- * one appears or the timeout elapses.
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.
165
179
  */
166
180
  const findAllByText: BuiltinQueries["findAllByText"] = builtinQueries.findAllByText;
167
181
  /**
168
- * Waits for and returns the single widget with matching rendered text content, retrying until it
169
- * appears or the timeout elapses. Rejects when none or more than one matches.
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.
170
185
  */
171
186
  const findByText: BuiltinQueries["findByText"] = builtinQueries.findByText;
172
187
  /**
173
- * Returns the single widget with a matching widget name, or null when none match.
174
- * Throws when more than one matches.
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.
175
190
  */
176
191
  const queryByName: BuiltinQueries["queryByName"] = builtinQueries.queryByName;
177
- /** Returns every widget with a matching widget name. Throws when none match. */
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
+ */
178
196
  const getAllByName: BuiltinQueries["getAllByName"] = builtinQueries.getAllByName;
179
- /** Returns the single widget with a matching widget name. Throws when none or more than one matches. */
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
+ */
180
201
  const getByName: BuiltinQueries["getByName"] = builtinQueries.getByName;
181
202
  /**
182
- * Waits for and returns every widget with a matching widget name, retrying until at least one
183
- * appears or the timeout elapses.
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.
184
205
  */
185
206
  const findAllByName: BuiltinQueries["findAllByName"] = builtinQueries.findAllByName;
186
207
  /**
187
- * Waits for and returns the single widget with a matching widget name, retrying until it appears or
188
- * the timeout elapses. Rejects when none or more than one matches.
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.
189
211
  */
190
212
  const findByName: BuiltinQueries["findByName"] = builtinQueries.findByName;
191
213
  /**
192
- * Returns the single widget with matching placeholder text, or null when none match.
193
- * Throws when more than one matches.
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.
194
216
  */
195
217
  const queryByPlaceholderText: BuiltinQueries["queryByPlaceholderText"] = builtinQueries.queryByPlaceholderText;
196
- /** Returns every widget with matching placeholder text. Throws when none match. */
218
+ /**
219
+ * Returns every mapped widget with matching placeholder text. Widgets that are not mapped are never
220
+ * matched. Throws when none match.
221
+ */
197
222
  const getAllByPlaceholderText: BuiltinQueries["getAllByPlaceholderText"] = builtinQueries.getAllByPlaceholderText;
198
- /** Returns the single widget with matching placeholder text. Throws when none or more than one matches. */
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
+ */
199
227
  const getByPlaceholderText: BuiltinQueries["getByPlaceholderText"] = builtinQueries.getByPlaceholderText;
200
228
  /**
201
- * Waits for and returns every widget with matching placeholder text, retrying until at least one
202
- * appears or the timeout elapses.
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.
203
231
  */
204
232
  const findAllByPlaceholderText: BuiltinQueries["findAllByPlaceholderText"] = builtinQueries.findAllByPlaceholderText;
205
233
  /**
206
- * Waits for and returns the single widget with matching placeholder text, retrying until it appears
207
- * or the timeout elapses. Rejects when none or more than one matches.
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.
208
237
  */
209
238
  const findByPlaceholderText: BuiltinQueries["findByPlaceholderText"] = builtinQueries.findByPlaceholderText;
210
239
  /**
211
- * Returns the single widget with a matching display value, or null when none match.
212
- * Throws when more than one matches.
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.
213
242
  */
214
243
  const queryByDisplayValue: BuiltinQueries["queryByDisplayValue"] = builtinQueries.queryByDisplayValue;
215
- /** Returns every widget with a matching display value. Throws when none match. */
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
+ */
216
248
  const getAllByDisplayValue: BuiltinQueries["getAllByDisplayValue"] = builtinQueries.getAllByDisplayValue;
217
- /** Returns the single widget with a matching display value. Throws when none or more than one matches. */
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
+ */
218
253
  const getByDisplayValue: BuiltinQueries["getByDisplayValue"] = builtinQueries.getByDisplayValue;
219
254
  /**
220
- * Waits for and returns every widget with a matching display value, retrying until at least one
221
- * appears or the timeout elapses.
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.
222
257
  */
223
258
  const findAllByDisplayValue: BuiltinQueries["findAllByDisplayValue"] = builtinQueries.findAllByDisplayValue;
224
259
  /**
225
- * Waits for and returns the single widget with a matching display value, retrying until it appears
226
- * or the timeout elapses. Rejects when none or more than one matches.
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.
227
263
  */
228
264
  const findByDisplayValue: BuiltinQueries["findByDisplayValue"] = builtinQueries.findByDisplayValue;
229
265
 
@@ -291,25 +327,23 @@ const hasMatchingAccessibleName = (widget: Gtk.Widget, options: ByRoleOptions):
291
327
  return isTextMatch(text, options.name, widget, options);
292
328
  };
293
329
 
294
- const isNumericValueMatch = (expected: number | undefined, actual: number | null): boolean =>
295
- expected === undefined || actual === expected;
330
+ const isNumericValueMatch = (widget: Gtk.Widget, field: WidgetValueField, expected: number | undefined): boolean =>
331
+ expected === undefined || isWidgetValueMatch(widget, field, expected);
296
332
 
297
333
  const hasMatchingAccessibleValue = (widget: Gtk.Widget, value: ByRoleValue, options: ByRoleOptions): boolean => {
298
- const actual = getWidgetValue(widget);
299
-
300
- const numericChecks: [number | undefined, number | null][] = [
301
- [value.now, actual.now],
302
- [value.min, actual.min],
303
- [value.max, actual.max],
334
+ const numericChecks: [WidgetValueField, number | undefined][] = [
335
+ ["now", value.now],
336
+ ["min", value.min],
337
+ ["max", value.max],
304
338
  ];
305
339
 
306
- for (const [expected, current] of numericChecks) {
307
- if (!isNumericValueMatch(expected, current)) {
340
+ for (const [field, expected] of numericChecks) {
341
+ if (!isNumericValueMatch(widget, field, expected)) {
308
342
  return false;
309
343
  }
310
344
  }
311
345
 
312
- return value.text === undefined || isTextMatch(actual.text, value.text, widget, options);
346
+ return value.text === undefined || isTextMatch(getWidgetValueText(widget), value.text, widget, options);
313
347
  };
314
348
 
315
349
  const hasMatchingBooleanStates = (widget: Gtk.Widget, options: ByRoleOptions): boolean => {
@@ -366,19 +400,22 @@ function nameQueryFamily<Args extends unknown[]>(
366
400
  queryAllBy: QueryAllBy<Args>,
367
401
  built: BuiltQueries<Args>,
368
402
  ): Record<string, unknown> {
403
+ const [queryBy, getAllBy, getBy, findAllBy, findBy] = built;
404
+
369
405
  return {
370
- [`queryBy${suffix}`]: built.queryBy,
406
+ [`queryBy${suffix}`]: queryBy,
371
407
  [`queryAllBy${suffix}`]: queryAllBy,
372
- [`getBy${suffix}`]: built.getBy,
373
- [`getAllBy${suffix}`]: built.getAllBy,
374
- [`findBy${suffix}`]: built.findBy,
375
- [`findAllBy${suffix}`]: built.findAllBy,
408
+ [`getBy${suffix}`]: getBy,
409
+ [`getAllBy${suffix}`]: getAllBy,
410
+ [`findBy${suffix}`]: findBy,
411
+ [`findAllBy${suffix}`]: findAllBy,
376
412
  };
377
413
  }
378
414
 
379
415
  /**
380
416
  * Finds every widget under the container whose accessible role matches `role` and that satisfies the given options.
381
- * Widgets excluded from the accessibility tree are skipped unless `options.hidden` is set.
417
+ * Widgets that are not mapped are never matched, and widgets excluded from the accessibility tree are
418
+ * skipped unless `options.hidden` is set.
382
419
  * @param container Widget subtree to search.
383
420
  * @param role Accessible role to match.
384
421
  * @param options Additional accessible name, state, and value constraints.
@@ -411,7 +448,7 @@ const collectMnemonicMatch = (
411
448
  return null;
412
449
  }
413
450
 
414
- const labelText = widget.getLabel();
451
+ const labelText = namingLabelText(widget);
415
452
 
416
453
  if (!labelText || !isTextMatch(labelText, text, widget, options)) {
417
454
  return null;
@@ -420,6 +457,12 @@ const collectMnemonicMatch = (
420
457
  return widget.getMnemonicWidget();
421
458
  };
422
459
 
460
+ const labellingText = (widget: Gtk.Widget): string | null => {
461
+ const ownLabel = widget.getAccessibleRole() === Gtk.AccessibleRole.LABEL ? null : getWidgetOwnLabel(widget);
462
+
463
+ return ownLabel ?? getWidgetExternalLabelText(widget);
464
+ };
465
+
423
466
  const collectLabelMatches = (
424
467
  results: Set<Gtk.Widget>,
425
468
  widget: Gtk.Widget,
@@ -432,22 +475,16 @@ const collectLabelMatches = (
432
475
  results.add(mnemonicTarget);
433
476
  }
434
477
 
435
- const ownLabel = getWidgetOwnLabel(widget);
436
-
437
- if (ownLabel !== null && isTextMatch(ownLabel, text, widget, options)) {
438
- results.add(widget);
439
- }
440
-
441
- const labelledByText = getWidgetLabelledByText(widget);
478
+ const labelText = labellingText(widget);
442
479
 
443
- if (labelledByText !== null && isTextMatch(labelledByText, text, widget, options)) {
480
+ if (labelText !== null && isTextMatch(labelText, text, widget, options)) {
444
481
  results.add(widget);
445
482
  }
446
483
  };
447
484
 
448
485
  /**
449
486
  * Finds every widget associated with a label whose text matches: a Gtk.Label mnemonic target, the
450
- * widget's own accessible label, or its labelled-by relation.
487
+ * widget's own accessible label, or its labelled-by relation. Widgets that are not mapped are never matched.
451
488
  * @param container Widget subtree to search.
452
489
  * @param text Matcher for the label text.
453
490
  * @param options Text matching options.
@@ -464,7 +501,7 @@ function queryAllByLabelText(container: Container, text: Matcher, options?: Matc
464
501
  }
465
502
 
466
503
  /**
467
- * Finds every widget whose rendered text content matches.
504
+ * Finds every widget whose rendered text content matches. Widgets that are not mapped are never matched.
468
505
  * @param container Widget subtree to search.
469
506
  * @param text Matcher for the widget text.
470
507
  * @param options Text matching options.
@@ -479,7 +516,7 @@ function queryAllByText(container: Container, text: Matcher, options?: MatcherOp
479
516
  }
480
517
 
481
518
  /**
482
- * Finds every widget whose widget name matches.
519
+ * Finds every widget whose widget name matches. Widgets that are not mapped are never matched.
483
520
  * @param container Widget subtree to search.
484
521
  * @param name Matcher for the widget name.
485
522
  * @param options Text matching options.
@@ -493,7 +530,7 @@ function queryAllByName(container: Container, name: Matcher, options?: MatcherOp
493
530
  }
494
531
 
495
532
  /**
496
- * Finds every widget whose placeholder text matches.
533
+ * Finds every widget whose placeholder text matches. Widgets that are not mapped are never matched.
497
534
  * @param container Widget subtree to search.
498
535
  * @param text Matcher for the placeholder text.
499
536
  * @param options Text matching options.
@@ -513,7 +550,7 @@ function queryAllByPlaceholderText(
513
550
  }
514
551
 
515
552
  /**
516
- * Finds every widget whose current display value matches.
553
+ * Finds every widget whose current display value matches. Widgets that are not mapped are never matched.
517
554
  * @param container Widget subtree to search.
518
555
  * @param value Matcher for the display value.
519
556
  * @param options Text matching options.
@@ -530,6 +567,8 @@ function queryAllByDisplayValue(container: Container, value: Matcher, options?:
530
567
 
531
568
  export {
532
569
  builtinQueries,
570
+ isMatchingWidgetType,
571
+ isTextMatch,
533
572
  queryByRole,
534
573
  getAllByRole,
535
574
  getByRole,
@@ -566,5 +605,4 @@ export {
566
605
  queryAllByName,
567
606
  queryAllByPlaceholderText,
568
607
  queryAllByDisplayValue,
569
- type BuiltinQueries,
570
608
  };
@@ -0,0 +1,97 @@
1
+ import type * as Gtk from "@gtkx/gi/gtk";
2
+ import { camelCase } from "@gtkx/utils";
3
+ import type { Matcher, MatcherOptions } from "./types.js";
4
+ import { buildQueries } from "./build-queries.js";
5
+ import { getElementError } from "./errors.js";
6
+ import { isMatchingWidgetType, isTextMatch } from "./queries.js";
7
+ import { type Container, findAll } from "./traversal.js";
8
+
9
+ /** The helpers used to build custom queries, bundled the way DOM Testing Library exposes them. */
10
+ type QueryHelpers = {
11
+ /** Derives a query family's variants from its `queryAllBy` function. */
12
+ buildQueries: typeof buildQueries;
13
+ /** Builds the error a failing query throws. */
14
+ getElementError: typeof getElementError;
15
+ /** Finds every widget whose GObject property matches. */
16
+ queryAllByObjectProperty: typeof queryAllByObjectProperty;
17
+ /** Finds the single widget whose GObject property matches. */
18
+ queryByObjectProperty: typeof queryByObjectProperty;
19
+ };
20
+
21
+ /** The query-building helpers, bundled so they can be reached under a single import. */
22
+ const queryHelpers: QueryHelpers = {
23
+ buildQueries,
24
+ getElementError,
25
+ queryAllByObjectProperty,
26
+ queryByObjectProperty,
27
+ };
28
+
29
+ const readObjectProperty = (widget: Gtk.Widget, property: string): string | null => {
30
+ const value: unknown = Reflect.get(widget, camelCase(property));
31
+
32
+ if (typeof value === "string") {
33
+ return value;
34
+ }
35
+
36
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
37
+ return String(value);
38
+ }
39
+
40
+ return null;
41
+ };
42
+
43
+ /**
44
+ * Finds every widget carrying a GObject property whose value matches, the counterpart of DOM Testing
45
+ * Library's `queryAllByAttribute`. The property name may be given in kebab-case or camelCase, and
46
+ * widgets that are not mapped are never matched.
47
+ *
48
+ * @param property Name of the GObject property to read.
49
+ * @param container Widget subtree to search.
50
+ * @param text Matcher for the property's value.
51
+ * @param options Text matching options.
52
+ * @returns Every matching widget, or an empty array when none match.
53
+ */
54
+ function queryAllByObjectProperty(
55
+ property: string,
56
+ container: Container,
57
+ text: Matcher,
58
+ options?: MatcherOptions,
59
+ ): Gtk.Widget[] {
60
+ return findAll(
61
+ container,
62
+ (widget) =>
63
+ isMatchingWidgetType(widget, options) &&
64
+ isTextMatch(readObjectProperty(widget, property), text, widget, options),
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Returns the single widget carrying a GObject property whose value matches, or null when none do,
70
+ * the counterpart of DOM Testing Library's `queryByAttribute`.
71
+ *
72
+ * @param property Name of the GObject property to read.
73
+ * @param container Widget subtree to search.
74
+ * @param text Matcher for the property's value.
75
+ * @param options Text matching options.
76
+ * @returns The matching widget, or null when none match.
77
+ * @throws When more than one widget matches.
78
+ */
79
+ function queryByObjectProperty(
80
+ property: string,
81
+ container: Container,
82
+ text: Matcher,
83
+ options?: MatcherOptions,
84
+ ): Gtk.Widget | null {
85
+ const matches = queryAllByObjectProperty(property, container, text, options);
86
+
87
+ if (matches.length > 1) {
88
+ throw getElementError(
89
+ `Found ${String(matches.length)} elements with the '${property}' property, but expected only one`,
90
+ container,
91
+ );
92
+ }
93
+
94
+ return matches[0] ?? null;
95
+ }
96
+
97
+ export { queryAllByObjectProperty, queryByObjectProperty, queryHelpers, type QueryHelpers };
@@ -0,0 +1,25 @@
1
+ import type { ReactNode } from "react";
2
+ import type { RenderOptions } from "./types.js";
3
+ import { render } from "./render.js";
4
+
5
+ type ChildrenBuilder<T> = (items: T[]) => ReactNode;
6
+
7
+ type RenderChildrenResult<T> = {
8
+ rerender: (items: T[]) => Promise<void>;
9
+ };
10
+
11
+ async function renderChildren<T>(
12
+ initial: T[],
13
+ build: ChildrenBuilder<T>,
14
+ options?: RenderOptions,
15
+ ): Promise<RenderChildrenResult<T>> {
16
+ const { rerender } = await render(build(initial), options);
17
+
18
+ return {
19
+ rerender: async (items: T[]) => {
20
+ await rerender(build(items));
21
+ },
22
+ };
23
+ }
24
+
25
+ export { renderChildren, type ChildrenBuilder };
package/src/render.tsx CHANGED
@@ -5,23 +5,28 @@ import {
5
5
  isRootElement,
6
6
  type ReconcilerRoot,
7
7
  setReconcilerErrorHandler,
8
+ settleAccessible,
8
9
  } from "@gtkx/react/internal";
9
10
  import { type ErrorInfo, type ReactNode, StrictMode } from "react";
10
11
  import type { RenderResult } from "./bound-queries.js";
11
- import type { QueryMap, RenderOptions, ScreenshotOptions, WindowSelector } from "./types.js";
12
+ import type { QueryMap, RenderOptions, ScreenshotOptions } from "./types.js";
12
13
  import { runInAct } from "./act.js";
13
14
  import { addToCleanupQueue, runCleanup } from "./cleanup-registry.js";
14
15
  import { scheduleAfterLayout } from "./frame-sync.js";
16
+ import { createHarnessWindow } from "./harness-window.js";
15
17
  import { logWidget, type PrettyWidgetOptions } from "./pretty-widget.js";
16
18
  import { logRoles } from "./role-helpers.js";
17
19
  import { clearScreen, setScreen } from "./screen.js";
18
- import { captureAndSaveScreenshot } from "./screenshot.js";
19
- import { type Container, TOPLEVELS, traverse } from "./traversal.js";
20
+ import { captureScreen } from "./screenshot.js";
21
+ import { type Container, roots, TOPLEVELS } from "./traversal.js";
20
22
  import { resetClipboard } from "./user-event/index.js";
21
23
  import { within } from "./within.js";
22
24
 
25
+ /** A mounted render tracked so cleanup can unmount it. */
23
26
  type ActiveRender = {
27
+ /** Root that rerenders drive and that cleanup tears the tree down through. */
24
28
  root: ReconcilerRoot;
29
+ /** Harness window created for the render, null when it renders into a container the caller supplied. */
25
30
  window: Gtk.Window | null;
26
31
  };
27
32
 
@@ -37,12 +42,13 @@ type ReconcilerErrorState = {
37
42
 
38
43
  const reconcilerErrors: ReconcilerErrorState = { lastError: null, isHandlerInstalled: false };
39
44
  const activeRenders: Set<ActiveRender> = new Set();
40
- const HARNESS_WINDOW_WIDTH = 800;
41
- const HARNESS_WINDOW_HEIGHT = 600;
42
45
 
43
46
  const flushLayout = (window: Gtk.Window | null): Promise<void> =>
44
- new Promise((resolve) => {
45
- scheduleAfterLayout(window, resolve);
47
+ new Promise<void>((resolve) => {
48
+ scheduleAfterLayout(window, () => {
49
+ settleAccessible();
50
+ resolve();
51
+ });
46
52
  });
47
53
 
48
54
  const update = async (element: ReactNode, root: ReconcilerRoot): Promise<void> => {
@@ -96,7 +102,7 @@ const resolveContainer = (container: RenderOptions["container"]): ResolvedContai
96
102
  return { containerInfo: container, window: null };
97
103
  }
98
104
 
99
- const window = new Gtk.Window({ defaultWidth: HARNESS_WINDOW_WIDTH, defaultHeight: HARNESS_WINDOW_HEIGHT });
105
+ const window = createHarnessWindow();
100
106
  window.setDecorated(false);
101
107
 
102
108
  return { containerInfo: window, window };
@@ -107,7 +113,7 @@ const firstToplevelWidget = (baseElement: Container): Gtk.Widget => {
107
113
  return baseElement;
108
114
  }
109
115
 
110
- const [first] = traverse(baseElement);
116
+ const [first] = roots(baseElement);
111
117
 
112
118
  if (first) {
113
119
  return first;
@@ -209,8 +215,7 @@ const render = async <Q extends QueryMap = Record<never, never>>(
209
215
  logRoles: () => {
210
216
  logRoles(baseElement);
211
217
  },
212
- screenshot: (selector?: WindowSelector, screenshotOptions?: ScreenshotOptions) =>
213
- captureAndSaveScreenshot(selector, screenshotOptions),
218
+ screenshot: (screenshotOptions?: ScreenshotOptions) => captureScreen(screenshotOptions),
214
219
  };
215
220
 
216
221
  setScreen(result);