@gtkx/testing 1.0.0-rc.1 → 1.0.0-rc.3

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 (187) hide show
  1. package/dist/act.d.ts +6 -6
  2. package/dist/act.d.ts.map +1 -1
  3. package/dist/act.js +34 -26
  4. package/dist/act.js.map +1 -1
  5. package/dist/bound-queries.d.ts +5 -5
  6. package/dist/bound-queries.d.ts.map +1 -1
  7. package/dist/bound-queries.js.map +1 -1
  8. package/dist/build-queries.d.ts +5 -5
  9. package/dist/build-queries.d.ts.map +1 -1
  10. package/dist/build-queries.js +21 -9
  11. package/dist/build-queries.js.map +1 -1
  12. package/dist/cleanup-registry.d.ts +4 -3
  13. package/dist/cleanup-registry.d.ts.map +1 -1
  14. package/dist/cleanup-registry.js +13 -3
  15. package/dist/cleanup-registry.js.map +1 -1
  16. package/dist/config.d.ts +5 -4
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +11 -9
  19. package/dist/config.js.map +1 -1
  20. package/dist/editable.d.ts +6 -5
  21. package/dist/editable.d.ts.map +1 -1
  22. package/dist/editable.js +7 -5
  23. package/dist/editable.js.map +1 -1
  24. package/dist/errors.d.ts +7 -6
  25. package/dist/errors.d.ts.map +1 -1
  26. package/dist/errors.js +64 -48
  27. package/dist/errors.js.map +1 -1
  28. package/dist/fire-event.d.ts +2 -1
  29. package/dist/fire-event.d.ts.map +1 -1
  30. package/dist/fire-event.js +2 -1
  31. package/dist/fire-event.js.map +1 -1
  32. package/dist/frame-sync.d.ts +2 -1
  33. package/dist/frame-sync.d.ts.map +1 -1
  34. package/dist/frame-sync.js +28 -21
  35. package/dist/frame-sync.js.map +1 -1
  36. package/dist/index.d.ts +9 -6
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +5 -3
  39. package/dist/index.js.map +1 -1
  40. package/dist/matchers.d.ts +99 -35
  41. package/dist/matchers.d.ts.map +1 -1
  42. package/dist/matchers.js +314 -63
  43. package/dist/matchers.js.map +1 -1
  44. package/dist/normalize.d.ts +10 -0
  45. package/dist/normalize.d.ts.map +1 -0
  46. package/dist/normalize.js +20 -0
  47. package/dist/normalize.js.map +1 -0
  48. package/dist/pretty-widget.d.ts +6 -4
  49. package/dist/pretty-widget.d.ts.map +1 -1
  50. package/dist/pretty-widget.js +64 -24
  51. package/dist/pretty-widget.js.map +1 -1
  52. package/dist/queries.d.ts +129 -90
  53. package/dist/queries.d.ts.map +1 -1
  54. package/dist/queries.js +235 -164
  55. package/dist/queries.js.map +1 -1
  56. package/dist/register-test-hooks.js +2 -1
  57. package/dist/register-test-hooks.js.map +1 -1
  58. package/dist/render-hook.d.ts +3 -2
  59. package/dist/render-hook.d.ts.map +1 -1
  60. package/dist/render-hook.js +2 -1
  61. package/dist/render-hook.js.map +1 -1
  62. package/dist/render.d.ts +3 -2
  63. package/dist/render.d.ts.map +1 -1
  64. package/dist/render.js +25 -19
  65. package/dist/render.js.map +1 -1
  66. package/dist/role-helpers.d.ts +6 -5
  67. package/dist/role-helpers.d.ts.map +1 -1
  68. package/dist/role-helpers.js +16 -12
  69. package/dist/role-helpers.js.map +1 -1
  70. package/dist/screen.d.ts +4 -3
  71. package/dist/screen.d.ts.map +1 -1
  72. package/dist/screen.js +13 -11
  73. package/dist/screen.js.map +1 -1
  74. package/dist/screenshot.d.ts +4 -3
  75. package/dist/screenshot.d.ts.map +1 -1
  76. package/dist/screenshot.js +43 -36
  77. package/dist/screenshot.js.map +1 -1
  78. package/dist/suggestions.d.ts +11 -6
  79. package/dist/suggestions.d.ts.map +1 -1
  80. package/dist/suggestions.js +10 -5
  81. package/dist/suggestions.js.map +1 -1
  82. package/dist/traversal.d.ts +7 -7
  83. package/dist/traversal.d.ts.map +1 -1
  84. package/dist/traversal.js +17 -11
  85. package/dist/traversal.js.map +1 -1
  86. package/dist/types.d.ts +65 -24
  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 +5 -3
  90. package/dist/user-event/adjustment.d.ts.map +1 -1
  91. package/dist/user-event/adjustment.js +6 -4
  92. package/dist/user-event/adjustment.js.map +1 -1
  93. package/dist/user-event/click.d.ts +6 -5
  94. package/dist/user-event/click.d.ts.map +1 -1
  95. package/dist/user-event/click.js +29 -21
  96. package/dist/user-event/click.js.map +1 -1
  97. package/dist/user-event/controller.d.ts +7 -5
  98. package/dist/user-event/controller.d.ts.map +1 -1
  99. package/dist/user-event/controller.js +16 -6
  100. package/dist/user-event/controller.js.map +1 -1
  101. package/dist/user-event/dispatch.d.ts +3 -3
  102. package/dist/user-event/dispatch.d.ts.map +1 -1
  103. package/dist/user-event/dispatch.js +3 -2
  104. package/dist/user-event/dispatch.js.map +1 -1
  105. package/dist/user-event/event-wrapper.d.ts +2 -1
  106. package/dist/user-event/event-wrapper.d.ts.map +1 -1
  107. package/dist/user-event/event-wrapper.js +27 -16
  108. package/dist/user-event/event-wrapper.js.map +1 -1
  109. package/dist/user-event/gesture.d.ts +18 -14
  110. package/dist/user-event/gesture.d.ts.map +1 -1
  111. package/dist/user-event/gesture.js +70 -37
  112. package/dist/user-event/gesture.js.map +1 -1
  113. package/dist/user-event/index.d.ts +17 -10
  114. package/dist/user-event/index.d.ts.map +1 -1
  115. package/dist/user-event/index.js +7 -3
  116. package/dist/user-event/index.js.map +1 -1
  117. package/dist/user-event/keyboard.d.ts +4 -3
  118. package/dist/user-event/keyboard.d.ts.map +1 -1
  119. package/dist/user-event/keyboard.js +89 -58
  120. package/dist/user-event/keyboard.js.map +1 -1
  121. package/dist/user-event/pointer.d.ts +7 -3
  122. package/dist/user-event/pointer.d.ts.map +1 -1
  123. package/dist/user-event/pointer.js +4 -1
  124. package/dist/user-event/pointer.js.map +1 -1
  125. package/dist/user-event/selection.d.ts +3 -2
  126. package/dist/user-event/selection.d.ts.map +1 -1
  127. package/dist/user-event/selection.js +26 -27
  128. package/dist/user-event/selection.js.map +1 -1
  129. package/dist/user-event/state.d.ts +3 -2
  130. package/dist/user-event/state.d.ts.map +1 -1
  131. package/dist/user-event/state.js +2 -1
  132. package/dist/user-event/state.js.map +1 -1
  133. package/dist/user-event/text.d.ts +8 -7
  134. package/dist/user-event/text.d.ts.map +1 -1
  135. package/dist/user-event/text.js +39 -33
  136. package/dist/user-event/text.js.map +1 -1
  137. package/dist/wait-for.d.ts +5 -5
  138. package/dist/wait-for.d.ts.map +1 -1
  139. package/dist/wait-for.js +45 -30
  140. package/dist/wait-for.js.map +1 -1
  141. package/dist/widget-accessible-properties.d.ts +36 -25
  142. package/dist/widget-accessible-properties.d.ts.map +1 -1
  143. package/dist/widget-accessible-properties.js +215 -68
  144. package/dist/widget-accessible-properties.js.map +1 -1
  145. package/dist/within.d.ts +2 -1
  146. package/dist/within.d.ts.map +1 -1
  147. package/dist/within.js +2 -1
  148. package/dist/within.js.map +1 -1
  149. package/package.json +11 -9
  150. package/src/act.ts +44 -35
  151. package/src/bound-queries.ts +6 -6
  152. package/src/build-queries.ts +84 -43
  153. package/src/cleanup-registry.ts +18 -5
  154. package/src/config.ts +16 -12
  155. package/src/editable.ts +13 -6
  156. package/src/errors.ts +106 -45
  157. package/src/fire-event.ts +3 -1
  158. package/src/frame-sync.ts +38 -21
  159. package/src/index.ts +13 -3
  160. package/src/matchers.ts +568 -114
  161. package/src/normalize.ts +28 -0
  162. package/src/pretty-widget.ts +114 -42
  163. package/src/queries.ts +418 -256
  164. package/src/register-test-hooks.ts +4 -1
  165. package/src/render-hook.tsx +10 -4
  166. package/src/render.tsx +59 -34
  167. package/src/role-helpers.ts +29 -16
  168. package/src/screen.ts +17 -12
  169. package/src/screenshot.ts +61 -38
  170. package/src/suggestions.ts +29 -10
  171. package/src/traversal.ts +35 -15
  172. package/src/types.ts +111 -32
  173. package/src/user-event/adjustment.ts +23 -10
  174. package/src/user-event/click.ts +49 -20
  175. package/src/user-event/controller.ts +41 -7
  176. package/src/user-event/dispatch.ts +4 -2
  177. package/src/user-event/event-wrapper.ts +42 -16
  178. package/src/user-event/gesture.ts +135 -50
  179. package/src/user-event/index.ts +19 -12
  180. package/src/user-event/keyboard.ts +147 -61
  181. package/src/user-event/pointer.ts +20 -8
  182. package/src/user-event/selection.ts +69 -32
  183. package/src/user-event/state.ts +4 -2
  184. package/src/user-event/text.ts +73 -38
  185. package/src/wait-for.ts +77 -36
  186. package/src/widget-accessible-properties.ts +385 -79
  187. package/src/within.ts +8 -5
@@ -3,136 +3,346 @@ import { getAccessibleMetadata } from "@gtkx/react/internal";
3
3
  import { EDITABLE_ROLES, isEditable, readEditableText } from "./editable.js";
4
4
  import { descendants } from "./traversal.js";
5
5
 
6
+ type WidgetValue = {
7
+ now: number | null;
8
+ min: number | null;
9
+ max: number | null;
10
+ text: string | null;
11
+ };
12
+
13
+ type ValueTriplet = { now: number | null; min: number | null; max: number | null };
14
+ /** A widget's checked state: on, off, or the mixed state of an inconsistent check button. */
15
+ type CheckedState = "checked" | "unchecked" | "mixed";
16
+
17
+ const DEFAULT_TEXT_GETTERS = ["getLabel", "getText", "getTitle"] as const;
18
+
19
+ const SELECTABLE_ROLES: Set<Gtk.AccessibleRole> = new Set<Gtk.AccessibleRole>([
20
+ Gtk.AccessibleRole.ROW,
21
+ Gtk.AccessibleRole.LIST_ITEM,
22
+ Gtk.AccessibleRole.GRID_CELL,
23
+ Gtk.AccessibleRole.OPTION,
24
+ Gtk.AccessibleRole.TREE_ITEM,
25
+ ]);
26
+
6
27
  const callStringGetter = (widget: Gtk.Widget, method: string): string | null => {
7
28
  const fn: unknown = Reflect.get(widget, method);
8
- if (typeof fn !== "function") return null;
29
+
30
+ if (typeof fn !== "function") {
31
+ return null;
32
+ }
33
+
9
34
  const value = (fn as () => string | null).call(widget);
35
+
10
36
  return value ?? null;
11
37
  };
12
38
 
13
39
  const readAccessibleString = (widget: Gtk.Widget, key: string): string | null => {
14
- const value = getAccessibleMetadata<string>(widget, key);
40
+ const value = getAccessibleMetadata(widget, key);
41
+
15
42
  return typeof value === "string" ? value : null;
16
43
  };
17
44
 
18
45
  const readAccessibleNumber = (widget: Gtk.Widget, key: string): number | null => {
19
- const value = getAccessibleMetadata<number>(widget, key);
46
+ const value = getAccessibleMetadata(widget, key);
47
+
20
48
  return typeof value === "number" ? value : null;
21
49
  };
22
50
 
51
+ const readAccessibleWidgets = (widget: Gtk.Widget, key: string): Gtk.Widget[] | null => {
52
+ const value = getAccessibleMetadata(widget, key);
53
+
54
+ if (!Array.isArray(value)) {
55
+ return null;
56
+ }
57
+
58
+ const widgets = value.filter((item): item is Gtk.Widget => item instanceof Gtk.Widget);
59
+
60
+ return widgets.length > 0 ? widgets : null;
61
+ };
62
+
23
63
  const readAccessibleBoolean = (widget: Gtk.Widget, key: string): boolean | null => {
24
- const value = getAccessibleMetadata<boolean>(widget, key);
64
+ const value = getAccessibleMetadata(widget, key);
65
+
25
66
  return typeof value === "boolean" ? value : null;
26
67
  };
27
68
 
28
69
  const getLabelText = (widget: Gtk.Widget): string | null => {
29
- if (widget instanceof Gtk.Label) return widget.getLabel();
30
- if (widget instanceof Gtk.Inscription) return widget.getText() ?? null;
70
+ if (widget instanceof Gtk.Label) {
71
+ return widget.getLabel();
72
+ }
73
+
74
+ if (widget instanceof Gtk.Inscription) {
75
+ return widget.getText() ?? null;
76
+ }
77
+
31
78
  return null;
32
79
  };
33
80
 
34
- const DEFAULT_TEXT_GETTERS = ["getLabel", "getText", "getTitle"] as const;
35
-
36
81
  /**
37
82
  * Returns a widget's own text by trying its label, text, and title getters in
38
83
  * order, or null when none produce a value.
39
84
  *
40
85
  * @param widget The widget to read text from.
41
86
  */
42
- export const getWidgetNodeText = (widget: Gtk.Widget): string | null => {
87
+ const getWidgetNodeText = (widget: Gtk.Widget): string | null => {
43
88
  for (const getter of DEFAULT_TEXT_GETTERS) {
44
89
  const value = callStringGetter(widget, getter);
45
- if (value) return value;
90
+
91
+ if (value) {
92
+ return value;
93
+ }
46
94
  }
95
+
47
96
  return null;
48
97
  };
49
98
 
50
- const stripMnemonic = (text: string): string => text.replace(/_(.)/g, "$1");
99
+ const stripMnemonic = (text: string): string => text.replaceAll(/_(.)/g, "$1");
51
100
 
52
101
  const namingLabelText = (widget: Gtk.Widget): string | null => {
53
102
  const text = getLabelText(widget);
54
- if (text === null) return null;
103
+
104
+ if (text === null) {
105
+ return null;
106
+ }
107
+
55
108
  return widget instanceof Gtk.Label && widget.getUseUnderline() ? stripMnemonic(text) : text;
56
109
  };
57
110
 
58
- const isNamingLabelRole = (role: Gtk.AccessibleRole, includePresentation: boolean): boolean =>
59
- role === Gtk.AccessibleRole.LABEL || (includePresentation && role === Gtk.AccessibleRole.PRESENTATION);
111
+ const isNamingLabelRole = (role: Gtk.AccessibleRole, shouldIncludePresentation: boolean): boolean =>
112
+ role === Gtk.AccessibleRole.LABEL ||
113
+ (shouldIncludePresentation && role === Gtk.AccessibleRole.PRESENTATION);
60
114
 
61
115
  const collectLabels = (widget: Gtk.Widget): string[] => {
62
- const includePresentation = widget.getAccessibleRole() === Gtk.AccessibleRole.MENU_ITEM;
116
+ const isIncludePresentation = widget.getAccessibleRole() === Gtk.AccessibleRole.MENU_ITEM;
63
117
  const labels: string[] = [];
118
+
64
119
  for (const descendant of descendants(widget)) {
65
- if (!isNamingLabelRole(descendant.getAccessibleRole(), includePresentation)) continue;
120
+ if (!isNamingLabelRole(descendant.getAccessibleRole(), isIncludePresentation)) {
121
+ continue;
122
+ }
123
+
66
124
  const labelText = namingLabelText(descendant);
67
- if (labelText) labels.push(labelText);
125
+
126
+ if (labelText) {
127
+ labels.push(labelText);
128
+ }
68
129
  }
130
+
69
131
  return labels;
70
132
  };
71
133
 
72
- export const getWidgetLabelText = (widget: Gtk.Widget): string | null => {
73
- if (widget.getAccessibleRole() !== Gtk.AccessibleRole.LABEL) return null;
134
+ const getWidgetLabelText = (widget: Gtk.Widget): string | null => {
135
+ if (widget.getAccessibleRole() !== Gtk.AccessibleRole.LABEL) {
136
+ return null;
137
+ }
138
+
74
139
  return getLabelText(widget);
75
140
  };
76
141
 
77
- export const getWidgetAccessibleName = (widget: Gtk.Widget): string | null => {
142
+ const getChildren = function* (widget: Gtk.Widget): Generator<Gtk.Widget> {
143
+ let child = widget.getFirstChild();
144
+
145
+ while (child) {
146
+ yield child;
147
+ child = child.getNextSibling();
148
+ }
149
+ };
150
+
151
+ const textContentParts = (widget: Gtk.Widget): string[] => {
152
+ const own = getWidgetNodeText(widget);
153
+
154
+ if (own !== null) {
155
+ return [own];
156
+ }
157
+
158
+ return [...getChildren(widget)].flatMap((child) => textContentParts(child));
159
+ };
160
+
161
+ const getWidgetTextContent = (widget: Gtk.Widget): string | null => {
162
+ const parts = textContentParts(widget);
163
+
164
+ return parts.length > 0 ? parts.join(" ") : null;
165
+ };
166
+
167
+ const isComboBoxFaceCandidate = (widget: Gtk.Widget): boolean =>
168
+ !(widget instanceof Gtk.Popover) && widget.getChildVisible();
169
+
170
+ const comboBoxChildFaceText = (child: Gtk.Widget): string | null =>
171
+ isComboBoxFaceCandidate(child) ? getWidgetLabelText(child) ?? comboBoxFaceText(child) : null;
172
+
173
+ const comboBoxFaceText = (widget: Gtk.Widget): string | null => {
174
+ for (const child of getChildren(widget)) {
175
+ const text = comboBoxChildFaceText(child);
176
+
177
+ if (text !== null) {
178
+ return text;
179
+ }
180
+ }
181
+
182
+ return null;
183
+ };
184
+
185
+ const tabPanelTitle = (widget: Gtk.Widget): string | null => {
186
+ const parent = widget.getParent();
187
+
188
+ if (parent instanceof Gtk.Stack) {
189
+ return parent.getPage(widget).getTitle() ?? null;
190
+ }
191
+
192
+ return null;
193
+ };
194
+
195
+ const getWidgetAccessibleName = (widget: Gtk.Widget): string | null => {
78
196
  const role = widget.getAccessibleRole();
79
197
 
80
198
  if (role === Gtk.AccessibleRole.TAB_PANEL) {
81
- const parent = widget.getParent();
82
- if (parent instanceof Gtk.Stack) {
83
- const page = parent.getPage(widget);
84
- return page.getTitle() ?? null;
85
- }
86
- return null;
199
+ return tabPanelTitle(widget);
200
+ }
201
+
202
+ const labelledByText = getWidgetLabelledByText(widget);
203
+
204
+ if (labelledByText) {
205
+ return labelledByText;
87
206
  }
88
207
 
89
- const accessibleLabel = getAccessibleMetadata<string>(widget, "accessibleLabel");
90
- if (accessibleLabel) return accessibleLabel;
208
+ const accessibleLabel = readAccessibleString(widget, "accessibleLabel");
209
+
210
+ if (accessibleLabel) {
211
+ return accessibleLabel;
212
+ }
91
213
 
92
214
  const ownText = getWidgetNodeText(widget);
93
- if (ownText) return ownText;
215
+
216
+ if (ownText) {
217
+ return ownText;
218
+ }
94
219
 
95
220
  const childLabels = collectLabels(widget);
96
- if (childLabels.length > 0) return childLabels.join(" ");
221
+
222
+ if (childLabels.length > 0) {
223
+ return childLabels.join(" ");
224
+ }
97
225
 
98
226
  return callStringGetter(widget, "getTooltipText");
99
227
  };
100
228
 
101
- export const getWidgetName = (widget: Gtk.Widget): string | null => {
229
+ const getWidgetName = (widget: Gtk.Widget): string | null => {
102
230
  return widget.getName();
103
231
  };
104
232
 
105
- export const getWidgetPlaceholderText = (widget: Gtk.Widget): string | null => {
233
+ const getWidgetPlaceholderText = (widget: Gtk.Widget): string | null => {
106
234
  if (!EDITABLE_ROLES.has(widget.getAccessibleRole()) || !(widget instanceof Gtk.Editable)) {
107
235
  return null;
108
236
  }
109
237
 
110
- return callStringGetter(widget, "getPlaceholderText");
238
+ const fromGetter = callStringGetter(widget, "getPlaceholderText");
239
+
240
+ if (fromGetter !== null) {
241
+ return fromGetter;
242
+ }
243
+
244
+ const fromAccessor: unknown = Reflect.get(widget, "placeholderText");
245
+
246
+ return typeof fromAccessor === "string" && fromAccessor !== "" ? fromAccessor : null;
111
247
  };
112
248
 
113
- export const getWidgetDisplayValue = (widget: Gtk.Widget): string | null => {
249
+ const getWidgetDisplayValue = (widget: Gtk.Widget): string | null => {
114
250
  if (EDITABLE_ROLES.has(widget.getAccessibleRole()) && isEditable(widget)) {
115
251
  return readEditableText(widget);
116
252
  }
117
253
 
254
+ if (widget.getAccessibleRole() === Gtk.AccessibleRole.COMBO_BOX) {
255
+ return comboBoxFaceText(widget);
256
+ }
257
+
118
258
  return null;
119
259
  };
120
260
 
121
- export const getWidgetCheckedState = (widget: Gtk.Widget): boolean | null => {
122
- if (widget instanceof Gtk.CheckButton) return widget.getActive();
123
- if (widget instanceof Gtk.Switch) return widget.getActive();
124
- if (widget instanceof Gtk.ToggleButton && widget.getAccessibleRole() === Gtk.AccessibleRole.RADIO) {
125
- return widget.getActive();
261
+ const hasDisplayValue = (widget: Gtk.Widget): boolean => {
262
+ const role = widget.getAccessibleRole();
263
+
264
+ return (EDITABLE_ROLES.has(role) && isEditable(widget)) || role === Gtk.AccessibleRole.COMBO_BOX;
265
+ };
266
+
267
+ const isWidgetDisabled = (widget: Gtk.Widget): boolean => !widget.isSensitive();
268
+
269
+ const hasZeroOpacityAncestor = (widget: Gtk.Widget): boolean => {
270
+ let current: Gtk.Widget | null = widget;
271
+
272
+ while (current) {
273
+ if (current.getOpacity() === 0) {
274
+ return true;
275
+ }
276
+
277
+ current = current.getParent();
126
278
  }
279
+
280
+ return false;
281
+ };
282
+
283
+ const isWidgetVisible = (widget: Gtk.Widget): boolean => widget.isVisible() && !hasZeroOpacityAncestor(widget);
284
+
285
+ const getWidgetRequiredState = (widget: Gtk.Widget): boolean | null =>
286
+ readAccessibleBoolean(widget, "accessibleRequired");
287
+
288
+ const readTextViewSelection = (view: Gtk.TextView): string | null => {
289
+ const buffer = view.getBuffer();
290
+ const [hasSelection, start, end] = buffer.getSelectionBounds();
291
+
292
+ return hasSelection ? buffer.getText(start, end, true) : null;
293
+ };
294
+
295
+ const readEditableSelection = (editable: Gtk.Editable): string | null => {
296
+ const [hasSelection, start, end] = editable.getSelectionBounds();
297
+
298
+ return hasSelection ? editable.getChars(start, end) : null;
299
+ };
300
+
301
+ const getWidgetSelection = (widget: Gtk.Widget): string | null => {
302
+ if (widget instanceof Gtk.TextView) {
303
+ return readTextViewSelection(widget);
304
+ }
305
+
306
+ if (widget instanceof Gtk.Editable) {
307
+ return readEditableSelection(widget);
308
+ }
309
+
127
310
  return null;
128
311
  };
129
312
 
130
- export const getWidgetPressedState = (widget: Gtk.Widget): boolean | null => {
131
- if (widget instanceof Gtk.ToggleButton) return widget.getActive();
313
+ const checkedFromActive = (isActive: boolean): CheckedState => (isActive ? "checked" : "unchecked");
314
+
315
+ const isCheckableToggle = (widget: Gtk.Widget): boolean =>
316
+ widget instanceof Gtk.Switch ||
317
+ (widget instanceof Gtk.ToggleButton && widget.getAccessibleRole() === Gtk.AccessibleRole.RADIO);
318
+
319
+ const getWidgetCheckedState = (widget: Gtk.Widget): CheckedState | null => {
320
+ if (widget instanceof Gtk.CheckButton) {
321
+ return widget.getInconsistent() ? "mixed" : checkedFromActive(widget.getActive());
322
+ }
323
+
324
+ if (isCheckableToggle(widget)) {
325
+ return checkedFromActive(Reflect.get(widget, "active") === true);
326
+ }
327
+
328
+ return null;
329
+ };
330
+
331
+ const isWidgetChecked = (widget: Gtk.Widget): boolean | null => {
332
+ const state = getWidgetCheckedState(widget);
333
+
334
+ return state === null || state === "mixed" ? null : state === "checked";
335
+ };
336
+
337
+ const getWidgetPressedState = (widget: Gtk.Widget): boolean | null => {
338
+ if (widget instanceof Gtk.ToggleButton) {
339
+ return widget.getActive();
340
+ }
341
+
132
342
  return null;
133
343
  };
134
344
 
135
- export const getWidgetExpandedState = (widget: Gtk.Widget): boolean | null => {
345
+ const getWidgetExpandedState = (widget: Gtk.Widget): boolean | null => {
136
346
  if (widget instanceof Gtk.Expander) {
137
347
  return widget.getExpanded();
138
348
  }
@@ -144,70 +354,91 @@ export const getWidgetExpandedState = (widget: Gtk.Widget): boolean | null => {
144
354
  return null;
145
355
  };
146
356
 
147
- export const getWidgetSelectedState = (widget: Gtk.Widget): boolean | null => {
148
- if (widget instanceof Gtk.ListBoxRow) {
357
+ const getWidgetSelectedState = (widget: Gtk.Widget): boolean | null => {
358
+ if (widget instanceof Gtk.ListBoxRow || widget instanceof Gtk.FlowBoxChild) {
149
359
  return widget.isSelected();
150
360
  }
151
361
 
152
- if (widget.getAccessibleRole() === Gtk.AccessibleRole.ROW) {
362
+ if (SELECTABLE_ROLES.has(widget.getAccessibleRole())) {
153
363
  return (widget.getStateFlags() & Gtk.StateFlags.SELECTED) !== 0;
154
364
  }
155
365
 
156
366
  return null;
157
367
  };
158
368
 
159
- export const getWidgetLevel = (widget: Gtk.Widget): number | null => {
369
+ const getWidgetLevel = (widget: Gtk.Widget): number | null => {
160
370
  return readAccessibleNumber(widget, "accessibleLevel");
161
371
  };
162
372
 
163
- export const getWidgetInvalidState = (widget: Gtk.Widget): Gtk.AccessibleInvalidState | null => {
164
- return getAccessibleMetadata<Gtk.AccessibleInvalidState>(widget, "accessibleInvalid");
165
- };
373
+ const getWidgetInvalidState = (widget: Gtk.Widget): Gtk.AccessibleInvalidState | null => {
374
+ const value = readAccessibleNumber(widget, "accessibleInvalid");
166
375
 
167
- export const getWidgetErrorMessage = (widget: Gtk.Widget): Gtk.Widget[] | null => {
168
- const targets = getAccessibleMetadata<Gtk.Widget[]>(widget, "accessibleErrorMessage");
169
- return Array.isArray(targets) && targets.length > 0 ? targets : null;
376
+ return value ?? null;
170
377
  };
171
378
 
172
- export const getWidgetBusyState = (widget: Gtk.Widget): boolean | null => {
379
+ const getWidgetErrorMessage = (widget: Gtk.Widget): Gtk.Widget[] | null =>
380
+ readAccessibleWidgets(widget, "accessibleErrorMessage");
381
+
382
+ const getWidgetBusyState = (widget: Gtk.Widget): boolean | null => {
173
383
  return readAccessibleBoolean(widget, "accessibleBusy");
174
384
  };
175
385
 
176
- export const getWidgetDescription = (widget: Gtk.Widget): string | null => {
386
+ const getWidgetDescription = (widget: Gtk.Widget): string | null => {
177
387
  return readAccessibleString(widget, "accessibleDescription");
178
388
  };
179
389
 
180
- export type WidgetValue = {
181
- now: number | null;
182
- min: number | null;
183
- max: number | null;
184
- text: string | null;
185
- };
186
-
187
- type ValueTriplet = { now: number | null; min: number | null; max: number | null };
188
-
189
390
  const adjustmentValue = (adjustment: Gtk.Adjustment): ValueTriplet => ({
190
391
  now: adjustment.getValue(),
191
392
  min: adjustment.getLower(),
192
393
  max: adjustment.getUpper(),
193
394
  });
194
395
 
396
+ const adjustmentWidgetValue = (widget: Gtk.Widget): ValueTriplet | null => {
397
+ if (widget instanceof Gtk.Range) {
398
+ return adjustmentValue(widget.getAdjustment());
399
+ }
400
+
401
+ if (widget instanceof Gtk.Scrollbar) {
402
+ return adjustmentValue(widget.getAdjustment());
403
+ }
404
+
405
+ if (widget instanceof Gtk.SpinButton) {
406
+ return adjustmentValue(widget.getAdjustment());
407
+ }
408
+
409
+ if (widget instanceof Gtk.ScaleButton) {
410
+ return adjustmentValue(widget.getAdjustment());
411
+ }
412
+
413
+ return null;
414
+ };
415
+
195
416
  const getWidgetLiveValue = (widget: Gtk.Widget): ValueTriplet | null => {
196
- if (widget instanceof Gtk.Range) return adjustmentValue(widget.getAdjustment());
197
- if (widget instanceof Gtk.Scrollbar) return adjustmentValue(widget.getAdjustment());
198
- if (widget instanceof Gtk.SpinButton) return adjustmentValue(widget.getAdjustment());
199
- if (widget instanceof Gtk.ScaleButton) return adjustmentValue(widget.getAdjustment());
417
+ const adjustmentBased = adjustmentWidgetValue(widget);
418
+
419
+ if (adjustmentBased) {
420
+ return adjustmentBased;
421
+ }
422
+
200
423
  if (widget instanceof Gtk.LevelBar) {
201
424
  return { now: widget.getValue(), min: widget.getMinValue(), max: widget.getMaxValue() };
202
425
  }
203
- if (widget instanceof Gtk.ProgressBar) return { now: widget.getFraction(), min: 0, max: 1 };
426
+
427
+ if (widget instanceof Gtk.ProgressBar) {
428
+ return { now: widget.getFraction(), min: 0, max: 1 };
429
+ }
430
+
204
431
  return null;
205
432
  };
206
433
 
207
- export const getWidgetValue = (widget: Gtk.Widget): WidgetValue => {
434
+ const getWidgetValue = (widget: Gtk.Widget): WidgetValue => {
208
435
  const text = readAccessibleString(widget, "accessibleValueText");
209
436
  const live = getWidgetLiveValue(widget);
210
- if (live) return { ...live, text };
437
+
438
+ if (live) {
439
+ return { ...live, text };
440
+ }
441
+
211
442
  return {
212
443
  now: readAccessibleNumber(widget, "accessibleValueNow"),
213
444
  min: readAccessibleNumber(widget, "accessibleValueMin"),
@@ -216,29 +447,104 @@ export const getWidgetValue = (widget: Gtk.Widget): WidgetValue => {
216
447
  };
217
448
  };
218
449
 
219
- export const getWidgetOwnLabel = (widget: Gtk.Widget): string | null => {
450
+ const getWidgetOwnLabel = (widget: Gtk.Widget): string | null => {
220
451
  return readAccessibleString(widget, "accessibleLabel");
221
452
  };
222
453
 
223
- export const getWidgetLabelledByText = (widget: Gtk.Widget): string | null => {
224
- const targets = getAccessibleMetadata<Gtk.Widget[]>(widget, "accessibleLabelledBy");
225
- if (!Array.isArray(targets) || targets.length === 0) return null;
226
-
454
+ const collectAccessibleNames = (targets: Gtk.Widget[]): string[] => {
227
455
  const texts: string[] = [];
456
+
228
457
  for (const target of targets) {
229
458
  const text = getWidgetAccessibleName(target);
230
- if (text !== null) texts.push(text);
459
+
460
+ if (text !== null) {
461
+ texts.push(text);
462
+ }
231
463
  }
232
464
 
465
+ return texts;
466
+ };
467
+
468
+ const getWidgetLabelledByText = (widget: Gtk.Widget): string | null => {
469
+ const targets = readAccessibleWidgets(widget, "accessibleLabelledBy");
470
+
471
+ if (targets === null) {
472
+ return null;
473
+ }
474
+
475
+ const texts = collectAccessibleNames(targets);
476
+
233
477
  return texts.length > 0 ? texts.join(" ") : null;
234
478
  };
235
479
 
236
- export const isInaccessible = (widget: Gtk.Widget): boolean => {
480
+ const isTooltipUsedAsName = (widget: Gtk.Widget): boolean =>
481
+ readAccessibleString(widget, "accessibleLabel") === null &&
482
+ getWidgetNodeText(widget) === null &&
483
+ collectLabels(widget).length === 0;
484
+
485
+ const getWidgetDescribedByText = (widget: Gtk.Widget): string | null => {
486
+ const targets = readAccessibleWidgets(widget, "accessibleDescribedBy");
487
+ const texts = targets === null ? [] : collectAccessibleNames(targets);
488
+
489
+ if (texts.length > 0) {
490
+ return texts.join(" ");
491
+ }
492
+
493
+ const description = readAccessibleString(widget, "accessibleDescription");
494
+
495
+ if (description !== null) {
496
+ return description;
497
+ }
498
+
499
+ return isTooltipUsedAsName(widget) ? null : callStringGetter(widget, "getTooltipText");
500
+ };
501
+
502
+ const isInaccessible = (widget: Gtk.Widget): boolean => {
237
503
  let current: Gtk.Widget | null = widget;
504
+
238
505
  while (current) {
239
- if (!current.getVisible()) return true;
240
- if (readAccessibleBoolean(current, "accessibleHidden") === true) return true;
506
+ if (!current.getVisible()) {
507
+ return true;
508
+ }
509
+
510
+ if (readAccessibleBoolean(current, "accessibleHidden") === true) {
511
+ return true;
512
+ }
513
+
241
514
  current = current.getParent();
242
515
  }
516
+
243
517
  return false;
244
518
  };
519
+
520
+ export {
521
+ getWidgetNodeText,
522
+ getWidgetTextContent,
523
+ getWidgetLabelText,
524
+ getWidgetAccessibleName,
525
+ getWidgetName,
526
+ getWidgetPlaceholderText,
527
+ getWidgetDisplayValue,
528
+ hasDisplayValue,
529
+ isWidgetDisabled,
530
+ isWidgetVisible,
531
+ getWidgetRequiredState,
532
+ getWidgetSelection,
533
+ getWidgetDescribedByText,
534
+ getWidgetCheckedState,
535
+ isWidgetChecked,
536
+ getWidgetPressedState,
537
+ getWidgetExpandedState,
538
+ getWidgetSelectedState,
539
+ getWidgetLevel,
540
+ getWidgetInvalidState,
541
+ getWidgetErrorMessage,
542
+ getWidgetBusyState,
543
+ getWidgetDescription,
544
+ getWidgetValue,
545
+ getWidgetOwnLabel,
546
+ getWidgetLabelledByText,
547
+ isInaccessible,
548
+ type CheckedState,
549
+ type WidgetValue,
550
+ };
package/src/within.ts CHANGED
@@ -1,15 +1,16 @@
1
1
  import type { BoundQueries } from "./bound-queries.js";
2
- import { builtinQueries } from "./queries.js";
3
2
  import type { Container } from "./traversal.js";
4
3
  import type { BoundCustomQueries, QueryMap } from "./types.js";
4
+ import { builtinQueries } from "./queries.js";
5
5
 
6
6
  const bindQuery =
7
7
  <Args extends unknown[], Result>(query: (container: Container, ...args: Args) => Result, container: Container) =>
8
- (...args: Args): Result =>
9
- query(container, ...args);
8
+ (...args: Args): Result =>
9
+ query(container, ...args);
10
10
 
11
11
  const bindCustomQueries = <Q extends QueryMap>(customQueries: Q, container: Container): BoundCustomQueries<Q> => {
12
12
  const entries = Object.entries(customQueries).map(([key, query]) => [key, bindQuery(query, container)] as const);
13
+
13
14
  return Object.fromEntries(entries) as BoundCustomQueries<Q>;
14
15
  };
15
16
 
@@ -21,10 +22,12 @@ const bindCustomQueries = <Q extends QueryMap>(customQueries: Q, container: Cont
21
22
  * @param queries Optional custom queries to bind alongside the built-in ones.
22
23
  * @returns The queries bound to the container.
23
24
  */
24
- export const within = <Q extends QueryMap = Record<never, never>>(
25
+ const within = <Q extends QueryMap = Record<never, never>>(
25
26
  container: Container,
26
27
  queries?: Q,
27
28
  ): BoundQueries & BoundCustomQueries<Q> => ({
28
- ...bindCustomQueries(builtinQueries, container),
29
+ ...(bindCustomQueries(builtinQueries, container) as BoundQueries),
29
30
  ...(queries ? bindCustomQueries(queries, container) : ({} as BoundCustomQueries<Q>)),
30
31
  });
32
+
33
+ export { within };