@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
@@ -1,19 +1,24 @@
1
1
  import * as Gtk from "@gtkx/gi/gtk";
2
- import { getAccessibleMetadata } from "@gtkx/react/internal";
2
+ import {
3
+ isAccessibleNumberMatch,
4
+ readAccessibleBooleanProperty,
5
+ readAccessibleFlag,
6
+ readAccessibleInt,
7
+ readAccessibleRelation,
8
+ readAccessibleState,
9
+ readAccessibleNumber as readNativeNumber,
10
+ readAccessibleString as readNativeString,
11
+ } from "./accessible-native.js";
3
12
  import { EDITABLE_ROLES, isEditable, readEditableText } from "./editable.js";
4
- import { descendants } from "./traversal.js";
13
+ import { isNameFromAuthor, isNameProhibited } from "./role-naming.js";
14
+ import { children, descendants, relationCandidates } from "./traversal.js";
15
+ import { callBooleanGetter, callStringGetter, getCallableMethod } from "./widget-getters.js";
5
16
 
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 };
17
+ type WidgetValueField = "now" | "min" | "max";
14
18
  type CheckedState = "checked" | "unchecked" | "mixed";
15
19
 
16
- const DEFAULT_TEXT_GETTERS = ["getLabel", "getText", "getTitle"] as const;
20
+ const EDITABLE_TEXT_GETTER = "getText";
21
+ const DEFAULT_TEXT_GETTERS = ["getLabel", EDITABLE_TEXT_GETTER, "getTitle"];
17
22
 
18
23
  const SELECTABLE_ROLES: Set<Gtk.AccessibleRole> = new Set<Gtk.AccessibleRole>([
19
24
  Gtk.AccessibleRole.ROW,
@@ -23,55 +28,60 @@ const SELECTABLE_ROLES: Set<Gtk.AccessibleRole> = new Set<Gtk.AccessibleRole>([
23
28
  Gtk.AccessibleRole.TREE_ITEM,
24
29
  ]);
25
30
 
26
- const callStringGetter = (widget: Gtk.Widget, method: string): string | null => {
27
- const fn: unknown = Reflect.get(widget, method);
28
-
29
- if (typeof fn !== "function") {
30
- return null;
31
- }
31
+ const PRESSED_BY_TRISTATE: Map<number, boolean> = new Map<number, boolean>([
32
+ [Gtk.AccessibleTristate.FALSE, false],
33
+ [Gtk.AccessibleTristate.TRUE, true],
34
+ ]);
32
35
 
33
- const value = (fn as () => string | null).call(widget);
36
+ const CHECKED_BY_TRISTATE: Map<number, CheckedState> = new Map<number, CheckedState>([
37
+ [Gtk.AccessibleTristate.FALSE, "unchecked"],
38
+ [Gtk.AccessibleTristate.TRUE, "checked"],
39
+ [Gtk.AccessibleTristate.MIXED, "mixed"],
40
+ ]);
34
41
 
35
- return value ?? null;
42
+ const VALUE_PROPERTIES: Record<WidgetValueField, Gtk.AccessibleProperty> = {
43
+ now: Gtk.AccessibleProperty.VALUE_NOW,
44
+ min: Gtk.AccessibleProperty.VALUE_MIN,
45
+ max: Gtk.AccessibleProperty.VALUE_MAX,
36
46
  };
37
47
 
38
- const readAccessibleString = (widget: Gtk.Widget, key: string): string | null => {
39
- const value = getAccessibleMetadata(widget, key);
48
+ const readAccessibleString = (widget: Gtk.Widget, property: Gtk.AccessibleProperty): string | null =>
49
+ readNativeString(widget, property);
40
50
 
41
- return typeof value === "string" ? value : null;
42
- };
51
+ const readAccessibleNumber = (widget: Gtk.Widget, property: Gtk.AccessibleProperty): number | null =>
52
+ readNativeNumber(widget, property);
43
53
 
44
- const readAccessibleNumber = (widget: Gtk.Widget, key: string): number | null => {
45
- const value = getAccessibleMetadata(widget, key);
54
+ const readAccessibleWidgets = (widget: Gtk.Widget, relation: Gtk.AccessibleRelation): Gtk.Widget[] | null => {
55
+ const targets = readAccessibleRelation(widget, relation, relationCandidates(widget));
56
+ const widgets = targets.filter((target): target is Gtk.Widget => target instanceof Gtk.Widget);
46
57
 
47
- return typeof value === "number" ? value : null;
58
+ return widgets.length > 0 ? widgets : null;
48
59
  };
49
60
 
50
- const readAccessibleWidgets = (widget: Gtk.Widget, key: string): Gtk.Widget[] | null => {
51
- const value = getAccessibleMetadata(widget, key);
61
+ const readAccessibleBoolean = (widget: Gtk.Widget, state: Gtk.AccessibleState): boolean | null =>
62
+ readAccessibleFlag(widget, state);
52
63
 
53
- if (!Array.isArray(value)) {
54
- return null;
64
+ const getLabelText = (widget: Gtk.Widget): string | null => {
65
+ if (widget instanceof Gtk.Label) {
66
+ return widget.getLabel();
55
67
  }
56
68
 
57
- const widgets = value.filter((item): item is Gtk.Widget => item instanceof Gtk.Widget);
58
-
59
- return widgets.length > 0 ? widgets : null;
69
+ return readAccessibleString(widget, Gtk.AccessibleProperty.LABEL);
60
70
  };
61
71
 
62
- const readAccessibleBoolean = (widget: Gtk.Widget, key: string): boolean | null => {
63
- const value = getAccessibleMetadata(widget, key);
72
+ const stripMnemonic = (text: string): string => text.replaceAll(/_(.)/g, "$1");
73
+ const isUnderlineUsed = (widget: Gtk.Widget): boolean => callBooleanGetter(widget, "getUseUnderline") ?? false;
64
74
 
65
- return typeof value === "boolean" ? value : null;
66
- };
75
+ const readNamingText = (widget: Gtk.Widget, getter: string, value: string): string =>
76
+ getter !== EDITABLE_TEXT_GETTER && isUnderlineUsed(widget) ? stripMnemonic(value) : value;
67
77
 
68
- const getLabelText = (widget: Gtk.Widget): string | null => {
69
- if (widget instanceof Gtk.Label) {
70
- return widget.getLabel();
71
- }
78
+ const readFirstText = (widget: Gtk.Widget, getters: string[]): string | null => {
79
+ for (const getter of getters) {
80
+ const value = callStringGetter(widget, getter);
72
81
 
73
- if (widget instanceof Gtk.Inscription) {
74
- return widget.getText() ?? null;
82
+ if (value) {
83
+ return readNamingText(widget, getter, value);
84
+ }
75
85
  }
76
86
 
77
87
  return null;
@@ -83,19 +93,7 @@ const getLabelText = (widget: Gtk.Widget): string | null => {
83
93
  *
84
94
  * @param widget The widget to read text from.
85
95
  */
86
- const getWidgetNodeText = (widget: Gtk.Widget): string | null => {
87
- for (const getter of DEFAULT_TEXT_GETTERS) {
88
- const value = callStringGetter(widget, getter);
89
-
90
- if (value) {
91
- return value;
92
- }
93
- }
94
-
95
- return null;
96
- };
97
-
98
- const stripMnemonic = (text: string): string => text.replaceAll(/_(.)/g, "$1");
96
+ const getWidgetText = (widget: Gtk.Widget): string | null => readFirstText(widget, DEFAULT_TEXT_GETTERS);
99
97
 
100
98
  const namingLabelText = (widget: Gtk.Widget): string | null => {
101
99
  const text = getLabelText(widget);
@@ -104,7 +102,7 @@ const namingLabelText = (widget: Gtk.Widget): string | null => {
104
102
  return null;
105
103
  }
106
104
 
107
- return widget instanceof Gtk.Label && widget.getUseUnderline() ? stripMnemonic(text) : text;
105
+ return isUnderlineUsed(widget) ? stripMnemonic(text) : text;
108
106
  };
109
107
 
110
108
  const isNamingLabelRole = (role: Gtk.AccessibleRole, shouldIncludePresentation: boolean): boolean =>
@@ -135,49 +133,34 @@ const getWidgetLabelText = (widget: Gtk.Widget): string | null => {
135
133
  return null;
136
134
  }
137
135
 
138
- return getLabelText(widget);
139
- };
140
-
141
- const getChildren = function* (widget: Gtk.Widget): Generator<Gtk.Widget> {
142
- let child = widget.getFirstChild();
143
-
144
- while (child) {
145
- yield child;
146
- child = child.getNextSibling();
147
- }
136
+ return namingLabelText(widget);
148
137
  };
149
138
 
150
139
  const textContentParts = (widget: Gtk.Widget): string[] => {
151
- const own = getWidgetNodeText(widget);
140
+ const own = getWidgetText(widget);
152
141
 
153
142
  if (own !== null) {
154
143
  return [own];
155
144
  }
156
145
 
157
- return [...getChildren(widget)].flatMap((child) => textContentParts(child));
146
+ return [...children(widget)].flatMap((child) => textContentParts(child));
158
147
  };
159
148
 
160
- /**
161
- * Returns a widget's own text, or the space-joined text of its descendants when it has none of its
162
- * own, or null when neither produces a value.
163
- *
164
- * @param widget The widget whose subtree is read.
165
- */
166
149
  const getWidgetTextContent = (widget: Gtk.Widget): string | null => {
167
150
  const parts = textContentParts(widget);
168
151
 
169
152
  return parts.length > 0 ? parts.join(" ") : null;
170
153
  };
171
154
 
172
- const isComboBoxFaceCandidate = (widget: Gtk.Widget): boolean =>
155
+ const isDropDownFaceCandidate = (widget: Gtk.Widget): boolean =>
173
156
  !(widget instanceof Gtk.Popover) && widget.getChildVisible();
174
157
 
175
- const comboBoxChildFaceText = (child: Gtk.Widget): string | null =>
176
- isComboBoxFaceCandidate(child) ? getWidgetLabelText(child) ?? comboBoxFaceText(child) : null;
158
+ const dropDownChildFaceText = (child: Gtk.Widget): string | null =>
159
+ isDropDownFaceCandidate(child) ? getWidgetLabelText(child) ?? dropDownFaceText(child) : null;
177
160
 
178
- const comboBoxFaceText = (widget: Gtk.Widget): string | null => {
179
- for (const child of getChildren(widget)) {
180
- const text = comboBoxChildFaceText(child);
161
+ const dropDownFaceText = (widget: Gtk.Widget): string | null => {
162
+ for (const child of children(widget)) {
163
+ const text = dropDownChildFaceText(child);
181
164
 
182
165
  if (text !== null) {
183
166
  return text;
@@ -197,67 +180,77 @@ const tabPanelTitle = (widget: Gtk.Widget): string | null => {
197
180
  return null;
198
181
  };
199
182
 
200
- const getWidgetAccessibleName = (widget: Gtk.Widget): string | null => {
201
- const role = widget.getAccessibleRole();
183
+ const nameFromAuthor = (widget: Gtk.Widget): string | null =>
184
+ getWidgetLabelledByText(widget) ?? readAccessibleString(widget, Gtk.AccessibleProperty.LABEL);
202
185
 
203
- if (role === Gtk.AccessibleRole.TAB_PANEL) {
204
- return tabPanelTitle(widget);
186
+ const nameFromContent = (widget: Gtk.Widget): string | null => {
187
+ const ownText = getWidgetText(widget);
188
+
189
+ if (ownText) {
190
+ return ownText;
205
191
  }
206
192
 
207
- const labelledByText = getWidgetLabelledByText(widget);
193
+ const childLabels = collectLabels(widget);
208
194
 
209
- if (labelledByText) {
210
- return labelledByText;
211
- }
195
+ return childLabels.length > 0 ? childLabels.join(" ") : null;
196
+ };
212
197
 
213
- const accessibleLabel = readAccessibleString(widget, "accessibleLabel");
198
+ const getWidgetAccessibleName = (widget: Gtk.Widget): string | null => {
199
+ const role = widget.getAccessibleRole();
214
200
 
215
- if (accessibleLabel) {
216
- return accessibleLabel;
201
+ if (isNameProhibited(role)) {
202
+ return null;
217
203
  }
218
204
 
219
- const ownText = getWidgetNodeText(widget);
220
-
221
- if (ownText) {
222
- return ownText;
205
+ if (role === Gtk.AccessibleRole.TAB_PANEL) {
206
+ return tabPanelTitle(widget);
223
207
  }
224
208
 
225
- const childLabels = collectLabels(widget);
209
+ const authored = isNameFromAuthor(role) ? nameFromAuthor(widget) : null;
226
210
 
227
- if (childLabels.length > 0) {
228
- return childLabels.join(" ");
211
+ if (authored) {
212
+ return authored;
229
213
  }
230
214
 
231
- return callStringGetter(widget, "getTooltipText");
215
+ return nameFromContent(widget) ?? callStringGetter(widget, "getTooltipText");
232
216
  };
233
217
 
234
218
  const getWidgetName = (widget: Gtk.Widget): string | null => {
235
219
  return widget.getName();
236
220
  };
237
221
 
238
- const getWidgetPlaceholderText = (widget: Gtk.Widget): string | null => {
239
- if (!EDITABLE_ROLES.has(widget.getAccessibleRole()) || !(widget instanceof Gtk.Editable)) {
240
- return null;
241
- }
222
+ const nonEmptyText = (text: string | null): string | null => (text === null || text === "" ? null : text);
242
223
 
243
- const fromGetter = callStringGetter(widget, "getPlaceholderText");
224
+ const readPlaceholderProperty = (widget: Gtk.Widget): string | null => {
225
+ const value: unknown = Reflect.get(widget, "placeholderText");
244
226
 
245
- if (fromGetter !== null) {
246
- return fromGetter;
247
- }
227
+ return typeof value === "string" ? nonEmptyText(value) : null;
228
+ };
248
229
 
249
- const fromAccessor: unknown = Reflect.get(widget, "placeholderText");
230
+ const getWidgetPlaceholderText = (widget: Gtk.Widget): string | null => {
231
+ if (!EDITABLE_ROLES.has(widget.getAccessibleRole())) {
232
+ return null;
233
+ }
250
234
 
251
- return typeof fromAccessor === "string" && fromAccessor !== "" ? fromAccessor : null;
235
+ return (
236
+ nonEmptyText(callStringGetter(widget, "getPlaceholderText")) ??
237
+ readPlaceholderProperty(widget) ??
238
+ nonEmptyText(readAccessibleString(widget, Gtk.AccessibleProperty.PLACEHOLDER))
239
+ );
252
240
  };
253
241
 
242
+ const comboBoxDisplayValue = (widget: Gtk.Widget): string | null =>
243
+ widget instanceof Gtk.DropDown
244
+ ? (dropDownFaceText(widget) ?? getWidgetValueText(widget))
245
+ : (getWidgetValueText(widget) ?? dropDownFaceText(widget));
246
+
254
247
  const getWidgetDisplayValue = (widget: Gtk.Widget): string | null => {
255
248
  if (EDITABLE_ROLES.has(widget.getAccessibleRole()) && isEditable(widget)) {
256
249
  return readEditableText(widget);
257
250
  }
258
251
 
259
252
  if (widget.getAccessibleRole() === Gtk.AccessibleRole.COMBO_BOX) {
260
- return comboBoxFaceText(widget);
253
+ return comboBoxDisplayValue(widget);
261
254
  }
262
255
 
263
256
  return null;
@@ -288,7 +281,7 @@ const hasZeroOpacityAncestor = (widget: Gtk.Widget): boolean => {
288
281
  const isWidgetVisible = (widget: Gtk.Widget): boolean => widget.isVisible() && !hasZeroOpacityAncestor(widget);
289
282
 
290
283
  const getWidgetRequiredState = (widget: Gtk.Widget): boolean | null =>
291
- readAccessibleBoolean(widget, "accessibleRequired");
284
+ readAccessibleBooleanProperty(widget, Gtk.AccessibleProperty.REQUIRED);
292
285
 
293
286
  const readTextViewSelection = (view: Gtk.TextView): string | null => {
294
287
  const buffer = view.getBuffer();
@@ -297,40 +290,48 @@ const readTextViewSelection = (view: Gtk.TextView): string | null => {
297
290
  return hasSelection ? buffer.getText(start, end, true) : null;
298
291
  };
299
292
 
300
- const readEditableSelection = (editable: Gtk.Editable): string | null => {
301
- const [hasSelection, start, end] = editable.getSelectionBounds();
293
+ const sliceSelectedText = (widget: Gtk.Widget, start: number, end: number): string | null => {
294
+ const chars = getCallableMethod<[number, number], string | null>(widget, "getChars");
295
+
296
+ if (chars) {
297
+ return chars(start, end) ?? null;
298
+ }
299
+
300
+ const text = callStringGetter(widget, "getText");
302
301
 
303
- return hasSelection ? editable.getChars(start, end) : null;
302
+ /* eslint-disable-next-line @typescript-eslint/no-misused-spread -- bounds are code point offsets */
303
+ return text === null ? null : [...text].slice(start, end).join("");
304
304
  };
305
305
 
306
- const getWidgetSelection = (widget: Gtk.Widget): string | null => {
307
- if (widget instanceof Gtk.TextView) {
308
- return readTextViewSelection(widget);
309
- }
306
+ const readBoundedSelection = (widget: Gtk.Widget): string | null => {
307
+ const bounds = getCallableMethod<[], [boolean, number, number]>(widget, "getSelectionBounds");
310
308
 
311
- if (widget instanceof Gtk.Editable) {
312
- return readEditableSelection(widget);
309
+ if (!bounds) {
310
+ return null;
313
311
  }
314
312
 
315
- return null;
313
+ const [hasSelection, start, end] = bounds();
314
+
315
+ return hasSelection ? sliceSelectedText(widget, start, end) : null;
316
316
  };
317
317
 
318
+ const getWidgetSelection = (widget: Gtk.Widget): string | null =>
319
+ widget instanceof Gtk.TextView ? readTextViewSelection(widget) : readBoundedSelection(widget);
320
+
318
321
  const checkedFromActive = (isActive: boolean): CheckedState => (isActive ? "checked" : "unchecked");
319
322
 
320
- const isCheckableToggle = (widget: Gtk.Widget): boolean =>
321
- widget instanceof Gtk.Switch ||
322
- (widget instanceof Gtk.ToggleButton && widget.getAccessibleRole() === Gtk.AccessibleRole.RADIO);
323
+ const isRadioToggle = (widget: Gtk.Widget): widget is Gtk.ToggleButton =>
324
+ widget instanceof Gtk.ToggleButton && widget.getAccessibleRole() === Gtk.AccessibleRole.RADIO;
323
325
 
324
326
  const getWidgetCheckedState = (widget: Gtk.Widget): CheckedState | null => {
325
- if (widget instanceof Gtk.CheckButton) {
326
- return widget.getInconsistent() ? "mixed" : checkedFromActive(widget.getActive());
327
- }
327
+ const tristate = readAccessibleState(widget, Gtk.AccessibleState.CHECKED);
328
+ const state = tristate === null ? undefined : CHECKED_BY_TRISTATE.get(tristate);
328
329
 
329
- if (isCheckableToggle(widget)) {
330
- return checkedFromActive(Reflect.get(widget, "active") === true);
330
+ if (state !== undefined) {
331
+ return state;
331
332
  }
332
333
 
333
- return null;
334
+ return isRadioToggle(widget) ? checkedFromActive(widget.getActive()) : null;
334
335
  };
335
336
 
336
337
  const isWidgetChecked = (widget: Gtk.Widget): boolean | null => {
@@ -340,28 +341,24 @@ const isWidgetChecked = (widget: Gtk.Widget): boolean | null => {
340
341
  };
341
342
 
342
343
  const getWidgetPressedState = (widget: Gtk.Widget): boolean | null => {
343
- if (widget instanceof Gtk.ToggleButton) {
344
- return widget.getActive();
345
- }
344
+ const tristate = readAccessibleState(widget, Gtk.AccessibleState.PRESSED);
346
345
 
347
- return null;
346
+ return tristate === null ? null : PRESSED_BY_TRISTATE.get(tristate) ?? null;
348
347
  };
349
348
 
350
349
  const getWidgetExpandedState = (widget: Gtk.Widget): boolean | null => {
351
- if (widget instanceof Gtk.Expander) {
352
- return widget.getExpanded();
353
- }
354
-
355
350
  if (widget instanceof Gtk.TreeExpander) {
356
351
  return widget.getListRow()?.getExpanded() ?? null;
357
352
  }
358
353
 
359
- return null;
354
+ return readAccessibleBoolean(widget, Gtk.AccessibleState.EXPANDED);
360
355
  };
361
356
 
362
357
  const getWidgetSelectedState = (widget: Gtk.Widget): boolean | null => {
363
- if (widget instanceof Gtk.ListBoxRow || widget instanceof Gtk.FlowBoxChild) {
364
- return widget.isSelected();
358
+ const selected = readAccessibleBoolean(widget, Gtk.AccessibleState.SELECTED);
359
+
360
+ if (selected !== null) {
361
+ return selected;
365
362
  }
366
363
 
367
364
  if (SELECTABLE_ROLES.has(widget.getAccessibleRole())) {
@@ -372,99 +369,37 @@ const getWidgetSelectedState = (widget: Gtk.Widget): boolean | null => {
372
369
  };
373
370
 
374
371
  const getWidgetLevel = (widget: Gtk.Widget): number | null => {
375
- return readAccessibleNumber(widget, "accessibleLevel");
372
+ return readAccessibleInt(widget, Gtk.AccessibleProperty.LEVEL);
376
373
  };
377
374
 
378
- /**
379
- * Returns the accessible invalid state declared on a widget, or null when it declares none.
380
- *
381
- * @param widget The widget to read the state from.
382
- */
383
375
  const getWidgetInvalidState = (widget: Gtk.Widget): Gtk.AccessibleInvalidState | null => {
384
- const value = readAccessibleNumber(widget, "accessibleInvalid");
376
+ const value = readAccessibleState(widget, Gtk.AccessibleState.INVALID);
385
377
 
386
378
  return value ?? null;
387
379
  };
388
380
 
389
- /**
390
- * Returns the widgets a widget's accessible error-message relation points at, or null when it
391
- * declares none.
392
- *
393
- * @param widget The widget to read the relation from.
394
- */
395
381
  const getWidgetErrorMessage = (widget: Gtk.Widget): Gtk.Widget[] | null =>
396
- readAccessibleWidgets(widget, "accessibleErrorMessage");
382
+ readAccessibleWidgets(widget, Gtk.AccessibleRelation.ERROR_MESSAGE);
397
383
 
398
384
  const getWidgetBusyState = (widget: Gtk.Widget): boolean | null => {
399
- return readAccessibleBoolean(widget, "accessibleBusy");
385
+ return readAccessibleBoolean(widget, Gtk.AccessibleState.BUSY);
400
386
  };
401
387
 
402
388
  const getWidgetDescription = (widget: Gtk.Widget): string | null => {
403
- return readAccessibleString(widget, "accessibleDescription");
404
- };
405
-
406
- const adjustmentValue = (adjustment: Gtk.Adjustment): ValueTriplet => ({
407
- now: adjustment.getValue(),
408
- min: adjustment.getLower(),
409
- max: adjustment.getUpper(),
410
- });
411
-
412
- const adjustmentWidgetValue = (widget: Gtk.Widget): ValueTriplet | null => {
413
- if (widget instanceof Gtk.Range) {
414
- return adjustmentValue(widget.getAdjustment());
415
- }
416
-
417
- if (widget instanceof Gtk.Scrollbar) {
418
- return adjustmentValue(widget.getAdjustment());
419
- }
420
-
421
- if (widget instanceof Gtk.SpinButton) {
422
- return adjustmentValue(widget.getAdjustment());
423
- }
424
-
425
- if (widget instanceof Gtk.ScaleButton) {
426
- return adjustmentValue(widget.getAdjustment());
427
- }
428
-
429
- return null;
430
- };
431
-
432
- const getWidgetLiveValue = (widget: Gtk.Widget): ValueTriplet | null => {
433
- const adjustmentBased = adjustmentWidgetValue(widget);
434
-
435
- if (adjustmentBased) {
436
- return adjustmentBased;
437
- }
438
-
439
- if (widget instanceof Gtk.LevelBar) {
440
- return { now: widget.getValue(), min: widget.getMinValue(), max: widget.getMaxValue() };
441
- }
442
-
443
- if (widget instanceof Gtk.ProgressBar) {
444
- return { now: widget.getFraction(), min: 0, max: 1 };
445
- }
446
-
447
- return null;
389
+ return readAccessibleString(widget, Gtk.AccessibleProperty.DESCRIPTION);
448
390
  };
449
391
 
450
- const getWidgetValue = (widget: Gtk.Widget): WidgetValue => {
451
- const text = readAccessibleString(widget, "accessibleValueText");
452
- const live = getWidgetLiveValue(widget);
392
+ const getWidgetValueNow = (widget: Gtk.Widget): number | null =>
393
+ readAccessibleNumber(widget, Gtk.AccessibleProperty.VALUE_NOW);
453
394
 
454
- if (live) {
455
- return { ...live, text };
456
- }
395
+ const getWidgetValueText = (widget: Gtk.Widget): string | null =>
396
+ readAccessibleString(widget, Gtk.AccessibleProperty.VALUE_TEXT);
457
397
 
458
- return {
459
- now: readAccessibleNumber(widget, "accessibleValueNow"),
460
- min: readAccessibleNumber(widget, "accessibleValueMin"),
461
- max: readAccessibleNumber(widget, "accessibleValueMax"),
462
- text,
463
- };
464
- };
398
+ const isWidgetValueMatch = (widget: Gtk.Widget, field: WidgetValueField, expected: number): boolean =>
399
+ isAccessibleNumberMatch(widget, VALUE_PROPERTIES[field], expected);
465
400
 
466
401
  const getWidgetOwnLabel = (widget: Gtk.Widget): string | null => {
467
- return readAccessibleString(widget, "accessibleLabel");
402
+ return readAccessibleString(widget, Gtk.AccessibleProperty.LABEL);
468
403
  };
469
404
 
470
405
  const collectAccessibleNames = (targets: Gtk.Widget[]): string[] => {
@@ -473,7 +408,7 @@ const collectAccessibleNames = (targets: Gtk.Widget[]): string[] => {
473
408
  for (const target of targets) {
474
409
  const text = getWidgetAccessibleName(target);
475
410
 
476
- if (text !== null) {
411
+ if (text) {
477
412
  texts.push(text);
478
413
  }
479
414
  }
@@ -481,8 +416,14 @@ const collectAccessibleNames = (targets: Gtk.Widget[]): string[] => {
481
416
  return texts;
482
417
  };
483
418
 
419
+ const isSelfLabelling = (widget: Gtk.Widget, targets: Gtk.Widget[]): boolean => {
420
+ const own = new Set(descendants(widget));
421
+
422
+ return targets.every((target) => own.has(target));
423
+ };
424
+
484
425
  const getWidgetLabelledByText = (widget: Gtk.Widget): string | null => {
485
- const targets = readAccessibleWidgets(widget, "accessibleLabelledBy");
426
+ const targets = readAccessibleWidgets(widget, Gtk.AccessibleRelation.LABELLED_BY);
486
427
 
487
428
  if (targets === null) {
488
429
  return null;
@@ -493,20 +434,26 @@ const getWidgetLabelledByText = (widget: Gtk.Widget): string | null => {
493
434
  return texts.length > 0 ? texts.join(" ") : null;
494
435
  };
495
436
 
437
+ const getWidgetExternalLabelText = (widget: Gtk.Widget): string | null => {
438
+ const targets = readAccessibleWidgets(widget, Gtk.AccessibleRelation.LABELLED_BY);
439
+
440
+ return targets === null || isSelfLabelling(widget, targets) ? null : getWidgetLabelledByText(widget);
441
+ };
442
+
496
443
  const isTooltipUsedAsName = (widget: Gtk.Widget): boolean =>
497
- readAccessibleString(widget, "accessibleLabel") === null &&
498
- getWidgetNodeText(widget) === null &&
444
+ readAccessibleString(widget, Gtk.AccessibleProperty.LABEL) === null &&
445
+ getWidgetText(widget) === null &&
499
446
  collectLabels(widget).length === 0;
500
447
 
501
448
  const getWidgetDescribedByText = (widget: Gtk.Widget): string | null => {
502
- const targets = readAccessibleWidgets(widget, "accessibleDescribedBy");
449
+ const targets = readAccessibleWidgets(widget, Gtk.AccessibleRelation.DESCRIBED_BY);
503
450
  const texts = targets === null ? [] : collectAccessibleNames(targets);
504
451
 
505
452
  if (texts.length > 0) {
506
453
  return texts.join(" ");
507
454
  }
508
455
 
509
- const description = readAccessibleString(widget, "accessibleDescription");
456
+ const description = readAccessibleString(widget, Gtk.AccessibleProperty.DESCRIPTION);
510
457
 
511
458
  if (description !== null) {
512
459
  return description;
@@ -515,15 +462,17 @@ const getWidgetDescribedByText = (widget: Gtk.Widget): string | null => {
515
462
  return isTooltipUsedAsName(widget) ? null : callStringGetter(widget, "getTooltipText");
516
463
  };
517
464
 
465
+ /**
466
+ * Returns whether a widget is excluded from the accessibility tree, because it or one of its
467
+ * ancestors is marked hidden.
468
+ *
469
+ * @param widget The widget to test.
470
+ */
518
471
  const isInaccessible = (widget: Gtk.Widget): boolean => {
519
472
  let current: Gtk.Widget | null = widget;
520
473
 
521
474
  while (current) {
522
- if (!current.getVisible()) {
523
- return true;
524
- }
525
-
526
- if (readAccessibleBoolean(current, "accessibleHidden") === true) {
475
+ if (readAccessibleBoolean(current, Gtk.AccessibleState.HIDDEN) === true) {
527
476
  return true;
528
477
  }
529
478
 
@@ -534,7 +483,8 @@ const isInaccessible = (widget: Gtk.Widget): boolean => {
534
483
  };
535
484
 
536
485
  export {
537
- getWidgetNodeText,
486
+ namingLabelText,
487
+ getWidgetText,
538
488
  getWidgetTextContent,
539
489
  getWidgetLabelText,
540
490
  getWidgetAccessibleName,
@@ -557,10 +507,13 @@ export {
557
507
  getWidgetErrorMessage,
558
508
  getWidgetBusyState,
559
509
  getWidgetDescription,
560
- getWidgetValue,
510
+ getWidgetValueNow,
511
+ getWidgetValueText,
561
512
  getWidgetOwnLabel,
513
+ getWidgetExternalLabelText,
562
514
  getWidgetLabelledByText,
563
515
  isInaccessible,
516
+ isWidgetValueMatch,
564
517
  type CheckedState,
565
- type WidgetValue,
518
+ type WidgetValueField,
566
519
  };