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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) 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 +4 -3
  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 +2 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/matchers.d.ts +38 -34
  41. package/dist/matchers.d.ts.map +1 -1
  42. package/dist/matchers.js +72 -58
  43. package/dist/matchers.js.map +1 -1
  44. package/dist/pretty-widget.d.ts +6 -4
  45. package/dist/pretty-widget.d.ts.map +1 -1
  46. package/dist/pretty-widget.js +64 -24
  47. package/dist/pretty-widget.js.map +1 -1
  48. package/dist/queries.d.ts +143 -86
  49. package/dist/queries.d.ts.map +1 -1
  50. package/dist/queries.js +231 -147
  51. package/dist/queries.js.map +1 -1
  52. package/dist/register-test-hooks.js +2 -1
  53. package/dist/register-test-hooks.js.map +1 -1
  54. package/dist/render-hook.d.ts +3 -2
  55. package/dist/render-hook.d.ts.map +1 -1
  56. package/dist/render-hook.js +2 -1
  57. package/dist/render-hook.js.map +1 -1
  58. package/dist/render.d.ts +3 -2
  59. package/dist/render.d.ts.map +1 -1
  60. package/dist/render.js +24 -18
  61. package/dist/render.js.map +1 -1
  62. package/dist/role-helpers.d.ts +6 -5
  63. package/dist/role-helpers.d.ts.map +1 -1
  64. package/dist/role-helpers.js +16 -12
  65. package/dist/role-helpers.js.map +1 -1
  66. package/dist/screen.d.ts +4 -3
  67. package/dist/screen.d.ts.map +1 -1
  68. package/dist/screen.js +13 -11
  69. package/dist/screen.js.map +1 -1
  70. package/dist/screenshot.d.ts +4 -3
  71. package/dist/screenshot.d.ts.map +1 -1
  72. package/dist/screenshot.js +43 -36
  73. package/dist/screenshot.js.map +1 -1
  74. package/dist/suggestions.d.ts +11 -6
  75. package/dist/suggestions.d.ts.map +1 -1
  76. package/dist/suggestions.js +10 -5
  77. package/dist/suggestions.js.map +1 -1
  78. package/dist/traversal.d.ts +7 -7
  79. package/dist/traversal.d.ts.map +1 -1
  80. package/dist/traversal.js +17 -11
  81. package/dist/traversal.js.map +1 -1
  82. package/dist/types.d.ts +39 -24
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/types.js.map +1 -1
  85. package/dist/user-event/adjustment.d.ts +5 -3
  86. package/dist/user-event/adjustment.d.ts.map +1 -1
  87. package/dist/user-event/adjustment.js +6 -4
  88. package/dist/user-event/adjustment.js.map +1 -1
  89. package/dist/user-event/click.d.ts +6 -5
  90. package/dist/user-event/click.d.ts.map +1 -1
  91. package/dist/user-event/click.js +29 -21
  92. package/dist/user-event/click.js.map +1 -1
  93. package/dist/user-event/controller.d.ts +6 -5
  94. package/dist/user-event/controller.d.ts.map +1 -1
  95. package/dist/user-event/controller.js +9 -6
  96. package/dist/user-event/controller.js.map +1 -1
  97. package/dist/user-event/dispatch.d.ts +3 -3
  98. package/dist/user-event/dispatch.d.ts.map +1 -1
  99. package/dist/user-event/dispatch.js +3 -2
  100. package/dist/user-event/dispatch.js.map +1 -1
  101. package/dist/user-event/event-wrapper.d.ts +2 -1
  102. package/dist/user-event/event-wrapper.d.ts.map +1 -1
  103. package/dist/user-event/event-wrapper.js +27 -16
  104. package/dist/user-event/event-wrapper.js.map +1 -1
  105. package/dist/user-event/gesture.d.ts +18 -14
  106. package/dist/user-event/gesture.d.ts.map +1 -1
  107. package/dist/user-event/gesture.js +70 -37
  108. package/dist/user-event/gesture.js.map +1 -1
  109. package/dist/user-event/index.d.ts +17 -10
  110. package/dist/user-event/index.d.ts.map +1 -1
  111. package/dist/user-event/index.js +7 -3
  112. package/dist/user-event/index.js.map +1 -1
  113. package/dist/user-event/keyboard.d.ts +4 -3
  114. package/dist/user-event/keyboard.d.ts.map +1 -1
  115. package/dist/user-event/keyboard.js +89 -58
  116. package/dist/user-event/keyboard.js.map +1 -1
  117. package/dist/user-event/pointer.d.ts +7 -3
  118. package/dist/user-event/pointer.d.ts.map +1 -1
  119. package/dist/user-event/pointer.js +4 -1
  120. package/dist/user-event/pointer.js.map +1 -1
  121. package/dist/user-event/selection.d.ts +3 -2
  122. package/dist/user-event/selection.d.ts.map +1 -1
  123. package/dist/user-event/selection.js +26 -27
  124. package/dist/user-event/selection.js.map +1 -1
  125. package/dist/user-event/state.d.ts +3 -2
  126. package/dist/user-event/state.d.ts.map +1 -1
  127. package/dist/user-event/state.js +2 -1
  128. package/dist/user-event/state.js.map +1 -1
  129. package/dist/user-event/text.d.ts +8 -7
  130. package/dist/user-event/text.d.ts.map +1 -1
  131. package/dist/user-event/text.js +39 -33
  132. package/dist/user-event/text.js.map +1 -1
  133. package/dist/wait-for.d.ts +5 -5
  134. package/dist/wait-for.d.ts.map +1 -1
  135. package/dist/wait-for.js +45 -30
  136. package/dist/wait-for.js.map +1 -1
  137. package/dist/widget-accessible-properties.d.ts +26 -25
  138. package/dist/widget-accessible-properties.d.ts.map +1 -1
  139. package/dist/widget-accessible-properties.js +106 -61
  140. package/dist/widget-accessible-properties.js.map +1 -1
  141. package/dist/within.d.ts +2 -1
  142. package/dist/within.d.ts.map +1 -1
  143. package/dist/within.js +2 -1
  144. package/dist/within.js.map +1 -1
  145. package/package.json +11 -9
  146. package/src/act.ts +44 -35
  147. package/src/bound-queries.ts +5 -4
  148. package/src/build-queries.ts +84 -43
  149. package/src/cleanup-registry.ts +18 -5
  150. package/src/config.ts +16 -12
  151. package/src/editable.ts +13 -6
  152. package/src/errors.ts +106 -45
  153. package/src/fire-event.ts +3 -1
  154. package/src/frame-sync.ts +38 -21
  155. package/src/index.ts +2 -1
  156. package/src/matchers.ts +158 -107
  157. package/src/pretty-widget.ts +114 -42
  158. package/src/queries.ts +422 -233
  159. package/src/register-test-hooks.ts +4 -1
  160. package/src/render-hook.tsx +10 -4
  161. package/src/render.tsx +58 -33
  162. package/src/role-helpers.ts +29 -16
  163. package/src/screen.ts +17 -12
  164. package/src/screenshot.ts +61 -38
  165. package/src/suggestions.ts +29 -10
  166. package/src/traversal.ts +35 -15
  167. package/src/types.ts +65 -32
  168. package/src/user-event/adjustment.ts +23 -10
  169. package/src/user-event/click.ts +49 -20
  170. package/src/user-event/controller.ts +21 -7
  171. package/src/user-event/dispatch.ts +4 -2
  172. package/src/user-event/event-wrapper.ts +42 -16
  173. package/src/user-event/gesture.ts +135 -50
  174. package/src/user-event/index.ts +19 -12
  175. package/src/user-event/keyboard.ts +147 -61
  176. package/src/user-event/pointer.ts +20 -8
  177. package/src/user-event/selection.ts +69 -32
  178. package/src/user-event/state.ts +4 -2
  179. package/src/user-event/text.ts +73 -38
  180. package/src/wait-for.ts +77 -36
  181. package/src/widget-accessible-properties.ts +218 -74
  182. package/src/within.ts +7 -4
@@ -1,21 +1,22 @@
1
1
  import type * as Gtk from "@gtkx/gi/gtk";
2
+ import type { Container } from "./traversal.js";
3
+ import type { MatcherOptions, WaitForOptions } from "./types.js";
2
4
  import { getConfig } from "./config.js";
3
5
  import { runWithExpensiveErrorDiagnosticsDisabled, suggestionError } from "./errors.js";
4
6
  import { getSuggestedQuery, type Method, type Variant } from "./suggestions.js";
5
- import type { Container } from "./traversal.js";
6
- import type { MatcherOptions, WaitForOptions } from "./types.js";
7
7
  import { waitFor } from "./wait-for.js";
8
8
 
9
- export type QueryAllBy<Args extends unknown[]> = (container: Container, ...args: Args) => Gtk.Widget[];
9
+ type QueryAllBy<Args extends unknown[]> = (container: Container, ...args: Args) => Gtk.Widget[];
10
10
 
11
11
  type MultipleErrorBuilder<Args extends unknown[]> = (
12
12
  container: Container,
13
13
  matches: Gtk.Widget[],
14
14
  ...args: Args
15
15
  ) => Error;
16
+
16
17
  type MissingErrorBuilder<Args extends unknown[]> = (container: Container, ...args: Args) => Error;
17
18
 
18
- export type BuiltQueries<Args extends unknown[]> = {
19
+ type BuiltQueries<Args extends unknown[]> = {
19
20
  queryBy: (container: Container, ...args: Args) => Gtk.Widget | null;
20
21
  getAllBy: QueryAllBy<Args>;
21
22
  getBy: (container: Container, ...args: Args) => Gtk.Widget;
@@ -23,20 +24,28 @@ export type BuiltQueries<Args extends unknown[]> = {
23
24
  findBy: (container: Container, ...args: Args) => Promise<Gtk.Widget>;
24
25
  };
25
26
 
27
+ type FindQuery<Args extends unknown[]> = (container: Container, ...args: Args) => unknown;
28
+ type SingleQuery<Args extends unknown[]> = (container: Container, ...args: Args) => Gtk.Widget | null;
29
+
26
30
  const extractWaitForOptions = (args: unknown[]): WaitForOptions => {
27
- const last = args[args.length - 1];
31
+ const last = args.at(-1);
32
+
28
33
  if (last && typeof last === "object" && !(last instanceof RegExp)) {
29
34
  const { timeout, interval, onTimeout } = last as WaitForOptions;
35
+
30
36
  return { timeout, interval, onTimeout };
31
37
  }
38
+
32
39
  return {};
33
40
  };
34
41
 
35
42
  const extractSuggestOption = (args: unknown[]): boolean | undefined => {
36
- const last = args[args.length - 1];
43
+ const last = args.at(-1);
44
+
37
45
  if (last && typeof last === "object" && !(last instanceof RegExp)) {
38
46
  return (last as MatcherOptions).suggest;
39
47
  }
48
+
40
49
  return undefined;
41
50
  };
42
51
 
@@ -49,16 +58,18 @@ const maybeThrowSuggestion = (options: {
49
58
  }): void => {
50
59
  const { container, match, queryName, variant, suggest } = options;
51
60
  const shouldSuggest = suggest ?? getConfig().throwSuggestions;
52
- if (!shouldSuggest) return;
61
+
62
+ if (!shouldSuggest) {
63
+ return;
64
+ }
53
65
 
54
66
  const suggestion = getSuggestedQuery(match, variant);
67
+
55
68
  if (suggestion && suggestion.queryName !== queryName) {
56
69
  throw suggestionError(suggestion.toString(), container);
57
70
  }
58
71
  };
59
72
 
60
- type FindQuery<Args extends unknown[]> = (container: Container, ...args: Args) => unknown;
61
-
62
73
  const reRunForDiagnostics = <Args extends unknown[]>(
63
74
  query: FindQuery<Args>,
64
75
  container: Container,
@@ -68,8 +79,11 @@ const reRunForDiagnostics = <Args extends unknown[]>(
68
79
  try {
69
80
  query(container, ...args);
70
81
  } catch (error) {
71
- if (error instanceof Error) return error;
82
+ if (error instanceof Error) {
83
+ return error;
84
+ }
72
85
  }
86
+
73
87
  return fallback;
74
88
  };
75
89
 
@@ -79,8 +93,10 @@ const findOptions = <Args extends unknown[]>(
79
93
  args: Args,
80
94
  ): WaitForOptions => {
81
95
  const userOptions = extractWaitForOptions(args);
96
+
82
97
  const onTimeout =
83
98
  userOptions.onTimeout ?? ((fallback: Error) => reRunForDiagnostics(query, container, args, fallback));
99
+
84
100
  return {
85
101
  stackTraceError: new Error("STACK_TRACE_MESSAGE"),
86
102
  onTimeout,
@@ -89,60 +105,80 @@ const findOptions = <Args extends unknown[]>(
89
105
  };
90
106
  };
91
107
 
92
- type SingleQuery<Args extends unknown[]> = (container: Container, ...args: Args) => Gtk.Widget | null;
93
-
94
108
  const singleFrom =
95
109
  <Args extends unknown[]>(
96
110
  allQuery: QueryAllBy<Args>,
97
111
  getMultipleError: MultipleErrorBuilder<Args>,
98
112
  ): SingleQuery<Args> =>
99
- (container, ...args) => {
100
- const matches = allQuery(container, ...args);
101
- if (matches.length > 1) throw getMultipleError(container, matches, ...args);
102
- return matches[0] ?? null;
103
- };
113
+ (container, ...args) => {
114
+ const matches = allQuery(container, ...args);
115
+
116
+ if (matches.length > 1) {
117
+ throw getMultipleError(container, matches, ...args);
118
+ }
119
+
120
+ return matches[0] ?? null;
121
+ };
104
122
 
105
123
  const allOrThrow =
106
124
  <Args extends unknown[]>(
107
125
  allQuery: QueryAllBy<Args>,
108
126
  getMissingError: MissingErrorBuilder<Args>,
109
127
  ): QueryAllBy<Args> =>
110
- (container, ...args) => {
111
- const matches = allQuery(container, ...args);
112
- if (matches.length === 0) throw getMissingError(container, ...args);
113
- return matches;
114
- };
128
+ (container, ...args) => {
129
+ const matches = allQuery(container, ...args);
130
+
131
+ if (matches.length === 0) {
132
+ throw getMissingError(container, ...args);
133
+ }
134
+
135
+ return matches;
136
+ };
115
137
 
116
138
  const wrapSingleWithSuggestion =
117
139
  <Args extends unknown[]>(query: SingleQuery<Args>, queryName: Method, variant: Variant): SingleQuery<Args> =>
118
- (container, ...args) => {
119
- const match = query(container, ...args);
120
- if (match) {
121
- maybeThrowSuggestion({ container, match, queryName, variant, suggest: extractSuggestOption(args) });
122
- }
123
- return match;
124
- };
140
+ (container, ...args) => {
141
+ const match = query(container, ...args);
142
+
143
+ if (match) {
144
+ maybeThrowSuggestion({ container, match, queryName, variant, suggest: extractSuggestOption(args) });
145
+ }
146
+
147
+ return match;
148
+ };
125
149
 
126
150
  const wrapAllWithSuggestion =
127
151
  <Args extends unknown[]>(query: QueryAllBy<Args>, queryName: Method, variant: Variant): QueryAllBy<Args> =>
128
- (container, ...args) => {
129
- const matches = query(container, ...args);
130
- const [first] = matches;
131
- if (first !== undefined) {
132
- maybeThrowSuggestion({ container, match: first, queryName, variant, suggest: extractSuggestOption(args) });
133
- }
134
- return matches;
135
- };
152
+ (container, ...args) => {
153
+ const matches = query(container, ...args);
154
+ const [first] = matches;
155
+
156
+ if (first !== undefined) {
157
+ maybeThrowSuggestion({
158
+ container,
159
+ match: first,
160
+ queryName,
161
+ variant,
162
+ suggest: extractSuggestOption(args),
163
+ });
164
+ }
165
+
166
+ return matches;
167
+ };
136
168
 
137
169
  const requireSingle =
138
170
  <Args extends unknown[]>(query: SingleQuery<Args>, getMissingError: MissingErrorBuilder<Args>) =>
139
- (container: Container, ...args: Args): Gtk.Widget => {
140
- const match = query(container, ...args);
141
- if (match === null) throw getMissingError(container, ...args);
142
- return match;
143
- };
171
+ (container: Container, ...args: Args): Gtk.Widget => {
172
+ const match = query(container, ...args);
173
+
174
+ if (match === null) {
175
+ throw getMissingError(container, ...args);
176
+ }
144
177
 
145
- export const buildQueries = <Args extends unknown[]>(
178
+ return match;
179
+ };
180
+
181
+ const buildQueries = <Args extends unknown[]>(
146
182
  queryName: Method,
147
183
  queryAllBy: QueryAllBy<Args>,
148
184
  getMultipleError: MultipleErrorBuilder<Args>,
@@ -156,10 +192,13 @@ export const buildQueries = <Args extends unknown[]>(
156
192
  queryName,
157
193
  "query",
158
194
  );
195
+
159
196
  const getAllByWithSuggestion = wrapAllWithSuggestion(getAllBy, queryName, "getAll");
197
+
160
198
  const getByWithSuggestion: (container: Container, ...args: Args) => Gtk.Widget = (container, ...args) => {
161
199
  const match = getBy(container, ...args);
162
200
  maybeThrowSuggestion({ container, match, queryName, variant: "get", suggest: extractSuggestOption(args) });
201
+
163
202
  return match;
164
203
  };
165
204
 
@@ -183,3 +222,5 @@ export const buildQueries = <Args extends unknown[]>(
183
222
  findBy,
184
223
  };
185
224
  };
225
+
226
+ export { buildQueries, type QueryAllBy, type BuiltQueries };
@@ -1,14 +1,27 @@
1
- export type CleanupFunction = () => void | Promise<void>;
1
+ type CleanupFunction = () => void | Promise<void>;
2
2
 
3
- const cleanupQueue = new Set<CleanupFunction>();
3
+ const cleanupQueue: Set<CleanupFunction> = new Set();
4
4
 
5
- export const addToCleanupQueue = (fn: CleanupFunction): void => {
5
+ const addToCleanupQueue = (fn: CleanupFunction): void => {
6
6
  cleanupQueue.add(fn);
7
7
  };
8
8
 
9
- export const runCleanup = async (): Promise<void> => {
9
+ const runCleanup = async (): Promise<void> => {
10
+ const failures: unknown[] = [];
11
+
10
12
  for (const fn of cleanupQueue) {
11
- await fn();
13
+ try {
14
+ await fn();
15
+ } catch (error) {
16
+ failures.push(error);
17
+ }
12
18
  }
19
+
13
20
  cleanupQueue.clear();
21
+
22
+ if (failures.length > 0) {
23
+ throw new AggregateError(failures, "Cleanup failed");
24
+ }
14
25
  };
26
+
27
+ export { addToCleanupQueue, runCleanup, type CleanupFunction };
package/src/config.ts CHANGED
@@ -5,7 +5,7 @@ import type { Container } from "./traversal.js";
5
5
  * error formatting, and the timeouts used by async utilities and actionability
6
6
  * checks.
7
7
  */
8
- export type Config = {
8
+ type Config = {
9
9
  throwSuggestions: boolean;
10
10
 
11
11
  /** Builds the error thrown when a query fails, given a message and optional container. */
@@ -22,13 +22,7 @@ export type Config = {
22
22
  * Function form of a configuration update: receives the current config and
23
23
  * returns the fields to override.
24
24
  */
25
- export type ConfigFn = (existingConfig: Config) => Partial<Config>;
26
-
27
- const defaultGetElementError = (message: string, _container?: Container): Error => {
28
- const error = new Error(message);
29
- error.name = "GtkxElementError";
30
- return error;
31
- };
25
+ type ConfigFn = (existingConfig: Config) => Partial<Config>;
32
26
 
33
27
  const defaultConfig: Config = {
34
28
  throwSuggestions: false,
@@ -37,12 +31,16 @@ const defaultConfig: Config = {
37
31
  actionabilityTimeout: 500,
38
32
  };
39
33
 
40
- let currentConfig: Config = { ...defaultConfig };
34
+ const currentConfig: Config = { ...defaultConfig };
35
+
36
+ function defaultGetElementError(message: string): Error {
37
+ return new GtkxElementError(message);
38
+ }
41
39
 
42
40
  /**
43
41
  * Returns the current global testing configuration.
44
42
  */
45
- export const getConfig = (): Config => {
43
+ const getConfig = (): Config => {
46
44
  return currentConfig;
47
45
  };
48
46
 
@@ -52,7 +50,13 @@ export const getConfig = (): Config => {
52
50
  * @param newConfig Either a partial config object or a function that receives
53
51
  * the current config and returns the fields to override.
54
52
  */
55
- export const configure = (newConfig: Partial<Config> | ConfigFn): void => {
53
+ const configure = (newConfig: Partial<Config> | ConfigFn): void => {
56
54
  const updates = typeof newConfig === "function" ? newConfig(currentConfig) : newConfig;
57
- currentConfig = { ...currentConfig, ...updates };
55
+ Object.assign(currentConfig, updates);
58
56
  };
57
+
58
+ class GtkxElementError extends Error {
59
+ override name = "GtkxElementError";
60
+ }
61
+
62
+ export { getConfig, configure, type Config, type ConfigFn };
package/src/editable.ts CHANGED
@@ -1,30 +1,37 @@
1
1
  import * as Gtk from "@gtkx/gi/gtk";
2
2
 
3
- export type EditableTarget = Gtk.Editable | Gtk.TextView;
3
+ type EditableTarget = Gtk.Editable | Gtk.TextView;
4
4
 
5
- export const EDITABLE_ROLES: Set<Gtk.AccessibleRole> = new Set<Gtk.AccessibleRole>([
5
+ const EDITABLE_ROLES: Set<Gtk.AccessibleRole> = new Set<Gtk.AccessibleRole>([
6
6
  Gtk.AccessibleRole.TEXT_BOX,
7
7
  Gtk.AccessibleRole.SEARCH_BOX,
8
8
  Gtk.AccessibleRole.SPIN_BUTTON,
9
9
  ]);
10
10
 
11
- export const isEditable = (widget: unknown): widget is EditableTarget =>
11
+ const isEditable = (widget: unknown): widget is EditableTarget =>
12
12
  widget instanceof Gtk.Editable || widget instanceof Gtk.TextView;
13
13
 
14
- export const getEditableDelegate = (widget: Gtk.Widget): Gtk.Widget | null => {
15
- if (!(widget instanceof Gtk.Editable)) return null;
14
+ const getEditableDelegate = (widget: Gtk.Widget): Gtk.Widget | null => {
15
+ if (!(widget instanceof Gtk.Editable)) {
16
+ return null;
17
+ }
18
+
16
19
  return widget.getDelegate();
17
20
  };
18
21
 
19
22
  const readTextViewBufferText = (widget: Gtk.TextView): string => {
20
23
  const buffer = widget.getBuffer();
21
24
  const [start, end] = buffer.getBounds();
25
+
22
26
  return buffer.getText(start, end, true);
23
27
  };
24
28
 
25
- export const readEditableText = (widget: EditableTarget): string => {
29
+ const readEditableText = (widget: EditableTarget): string => {
26
30
  if (widget instanceof Gtk.TextView) {
27
31
  return readTextViewBufferText(widget);
28
32
  }
33
+
29
34
  return widget.getText();
30
35
  };
36
+
37
+ export { EDITABLE_ROLES, isEditable, getEditableDelegate, readEditableText, type EditableTarget };
package/src/errors.ts CHANGED
@@ -1,94 +1,140 @@
1
1
  import type * as Gtk from "@gtkx/gi/gtk";
2
+ import type { Container } from "./traversal.js";
3
+ import type { ByRoleOptions, ByRoleValue, Matcher } from "./types.js";
2
4
  import { getConfig } from "./config.js";
3
5
  import { prettyWidget } from "./pretty-widget.js";
4
6
  import { formatRole, prettyRoles } from "./role-helpers.js";
5
- import type { Container } from "./traversal.js";
6
- import type { ByRoleOptions, ByRoleValue, Matcher } from "./types.js";
7
+
8
+ type QueryDescriptor =
9
+ | { queryType: "role"; role: Gtk.AccessibleRole; options?: ByRoleOptions | undefined } |
10
+ { queryType: "text"; text: Matcher } |
11
+ { queryType: "labelText"; text: Matcher } |
12
+ { queryType: "name"; name: Matcher } |
13
+ { queryType: "placeholderText"; text: Matcher } |
14
+ { queryType: "displayValue"; value: Matcher };
15
+
16
+ const roleOptionFormatters: ((options: ByRoleOptions) => string | null)[] = [
17
+ (o) => (o.name ? `name ${formatTextMatcher(o.name)}` : null),
18
+ (o) => (o.checked === undefined ? null : `checked=${String(o.checked)}`),
19
+ (o) => (o.pressed === undefined ? null : `pressed=${String(o.pressed)}`),
20
+ (o) => (o.selected === undefined ? null : `selected=${String(o.selected)}`),
21
+ (o) => (o.expanded === undefined ? null : `expanded=${String(o.expanded)}`),
22
+ (o) => (o.level === undefined ? null : `level=${String(o.level)}`),
23
+ (o) => (o.busy === undefined ? null : `busy=${String(o.busy)}`),
24
+ (o) => (o.description ? `description ${formatTextMatcher(o.description)}` : null),
25
+ (o) => (o.value ? `value ${formatByRoleValue(o.value)}` : null),
26
+ (o) => (o.hidden === undefined ? null : `hidden=${String(o.hidden)}`),
27
+ ];
28
+
29
+ const expensiveErrorDiagnostics = { isDisabled: false };
7
30
 
8
31
  const formatTextMatcher = (text: Matcher): string => {
9
32
  if (typeof text === "function") {
10
33
  return "custom function";
11
34
  }
35
+
12
36
  if (text instanceof RegExp) {
13
37
  return text.toString();
14
38
  }
15
- return `'${text}'`;
39
+
40
+ return `'${String(text)}'`;
16
41
  };
17
42
 
18
43
  const formatByRoleValue = (value: ByRoleValue): string => {
19
44
  const parts: string[] = [];
20
- if (value.now !== undefined) parts.push(`now=${value.now}`);
21
- if (value.min !== undefined) parts.push(`min=${value.min}`);
22
- if (value.max !== undefined) parts.push(`max=${value.max}`);
23
- if (value.text !== undefined) parts.push(`text ${formatTextMatcher(value.text)}`);
45
+
46
+ if (value.now !== undefined) {
47
+ parts.push(`now=${String(value.now)}`);
48
+ }
49
+
50
+ if (value.min !== undefined) {
51
+ parts.push(`min=${String(value.min)}`);
52
+ }
53
+
54
+ if (value.max !== undefined) {
55
+ parts.push(`max=${String(value.max)}`);
56
+ }
57
+
58
+ if (value.text !== undefined) {
59
+ parts.push(`text ${formatTextMatcher(value.text)}`);
60
+ }
61
+
24
62
  return parts.join(", ");
25
63
  };
26
64
 
65
+ const roleOptionParts = (options: ByRoleOptions): string[] => {
66
+ const parts: string[] = [];
67
+
68
+ for (const formatter of roleOptionFormatters) {
69
+ const part = formatter(options);
70
+
71
+ if (part !== null) {
72
+ parts.push(part);
73
+ }
74
+ }
75
+
76
+ return parts;
77
+ };
78
+
27
79
  const formatByRoleDescription = (role: Gtk.AccessibleRole, options?: ByRoleOptions): string => {
28
80
  const parts = [`role '${formatRole(role).toUpperCase()}'`];
29
- if (options?.name) parts.push(`name ${formatTextMatcher(options.name)}`);
30
- if (options?.checked !== undefined) parts.push(`checked=${options.checked}`);
31
- if (options?.pressed !== undefined) parts.push(`pressed=${options.pressed}`);
32
- if (options?.selected !== undefined) parts.push(`selected=${options.selected}`);
33
- if (options?.expanded !== undefined) parts.push(`expanded=${options.expanded}`);
34
- if (options?.level !== undefined) parts.push(`level=${options.level}`);
35
- if (options?.busy !== undefined) parts.push(`busy=${options.busy}`);
36
- if (options?.description) parts.push(`description ${formatTextMatcher(options.description)}`);
37
- if (options?.value) parts.push(`value ${formatByRoleValue(options.value)}`);
38
- if (options?.hidden !== undefined) parts.push(`hidden=${options.hidden}`);
81
+
82
+ if (options) {
83
+ parts.push(...roleOptionParts(options));
84
+ }
85
+
39
86
  return parts.join(" and ");
40
87
  };
41
88
 
42
- export type QueryDescriptor =
43
- | { queryType: "role"; role: Gtk.AccessibleRole; options?: ByRoleOptions | undefined }
44
- | { queryType: "text"; text: Matcher }
45
- | { queryType: "labelText"; text: Matcher }
46
- | { queryType: "name"; name: Matcher }
47
- | { queryType: "placeholderText"; text: Matcher }
48
- | { queryType: "displayValue"; value: Matcher };
49
-
50
89
  const formatQueryDescription = (descriptor: QueryDescriptor): string => {
51
90
  switch (descriptor.queryType) {
52
- case "role":
91
+ case "role": {
53
92
  return formatByRoleDescription(descriptor.role, descriptor.options);
54
- case "text":
93
+ }
94
+ case "text": {
55
95
  return `text ${formatTextMatcher(descriptor.text)}`;
56
- case "labelText":
96
+ }
97
+ case "labelText": {
57
98
  return `label text ${formatTextMatcher(descriptor.text)}`;
58
- case "name":
99
+ }
100
+ case "name": {
59
101
  return `name ${formatTextMatcher(descriptor.name)}`;
60
- case "placeholderText":
102
+ }
103
+ case "placeholderText": {
61
104
  return `placeholder text ${formatTextMatcher(descriptor.text)}`;
62
- case "displayValue":
105
+ }
106
+ case "displayValue": {
63
107
  return `display value ${formatTextMatcher(descriptor.value)}`;
108
+ }
64
109
  }
65
110
  };
66
111
 
67
- let expensiveErrorDiagnosticsDisabled = false;
112
+ const runWithExpensiveErrorDiagnosticsDisabled = <T>(callback: () => T): T => {
113
+ const isPrevious = expensiveErrorDiagnostics.isDisabled;
114
+ expensiveErrorDiagnostics.isDisabled = true;
68
115
 
69
- export const runWithExpensiveErrorDiagnosticsDisabled = <T>(callback: () => T): T => {
70
- const previous = expensiveErrorDiagnosticsDisabled;
71
- expensiveErrorDiagnosticsDisabled = true;
72
116
  try {
73
117
  return callback();
74
118
  } finally {
75
- expensiveErrorDiagnosticsDisabled = previous;
119
+ expensiveErrorDiagnostics.isDisabled = isPrevious;
76
120
  }
77
121
  };
78
122
 
79
123
  const buildElementError = (container: Container, headLines: string[]): Error => {
80
124
  const config = getConfig();
81
- const lines = expensiveErrorDiagnosticsDisabled
125
+
126
+ const lines = expensiveErrorDiagnostics.isDisabled
82
127
  ? headLines
83
128
  : [...headLines, "", prettyWidget(container, { highlight: false })];
129
+
84
130
  return config.getElementError(lines.join("\n"), container);
85
131
  };
86
132
 
87
- export const notFoundError = (container: Container, descriptor: QueryDescriptor): Error => {
133
+ const notFoundError = (container: Container, descriptor: QueryDescriptor): Error => {
88
134
  const description = formatQueryDescription(descriptor);
89
135
  const headLines = [`Unable to find an element with ${description}`];
90
136
 
91
- if (!expensiveErrorDiagnosticsDisabled && descriptor.queryType === "role") {
137
+ if (!expensiveErrorDiagnostics.isDisabled && descriptor.queryType === "role") {
92
138
  headLines.push("", "Here are the accessible roles:", "", prettyRoles(container));
93
139
  }
94
140
 
@@ -97,34 +143,49 @@ export const notFoundError = (container: Container, descriptor: QueryDescriptor)
97
143
 
98
144
  const allByVariantHint = (descriptor: QueryDescriptor): string => {
99
145
  const variant = descriptor.queryType === "role" ? "getAllByRole" : "getAllBy*";
146
+
100
147
  return (
101
148
  "(If this is intentional, use the *AllBy* variant of the query, " +
102
149
  `e.g. queryAllBy*/getAllBy*/findAllBy*, such as ${variant}.)`
103
150
  );
104
151
  };
105
152
 
106
- export const multipleFoundError = (container: Container, descriptor: QueryDescriptor, matches: Gtk.Widget[]): Error => {
153
+ const multipleFoundError = (container: Container, descriptor: QueryDescriptor, matches: Gtk.Widget[]): Error => {
107
154
  const description = formatQueryDescription(descriptor);
155
+
108
156
  const headLines = [
109
- `Found ${matches.length} elements with ${description}, but expected only one`,
157
+ `Found ${String(matches.length)} elements with ${description}, but expected only one`,
110
158
  "",
111
159
  allByVariantHint(descriptor),
112
160
  ];
113
- if (!expensiveErrorDiagnosticsDisabled) {
161
+
162
+ if (!expensiveErrorDiagnostics.isDisabled) {
114
163
  const renderedMatches = matches.map((widget) => prettyWidget(widget, { highlight: false }));
115
164
  headLines.push("", "Here are the matching elements:", "", ...renderedMatches);
116
165
  }
166
+
117
167
  return buildElementError(container, headLines);
118
168
  };
119
169
 
120
- export const suggestionError = (suggestion: string, container: Container): Error => {
170
+ const suggestionError = (suggestion: string, container: Container): Error => {
121
171
  const config = getConfig();
122
172
  const message = `A better query is available, try this:\n${suggestion}\n`;
173
+
123
174
  return config.getElementError(message, container);
124
175
  };
125
176
 
126
- export const timeoutError = (timeout: number, lastError: Error | null): Error => {
127
- const baseMessage = `Timed out after ${timeout}ms`;
177
+ const timeoutError = (timeout: number, lastError: Error | null): Error => {
178
+ const baseMessage = `Timed out after ${String(timeout)}ms`;
128
179
  const message = lastError ? `${baseMessage}.\n\n${lastError.message}` : baseMessage;
180
+
129
181
  return getConfig().getElementError(message);
130
182
  };
183
+
184
+ export {
185
+ runWithExpensiveErrorDiagnosticsDisabled,
186
+ notFoundError,
187
+ multipleFoundError,
188
+ suggestionError,
189
+ timeoutError,
190
+ type QueryDescriptor,
191
+ };
package/src/fire-event.ts CHANGED
@@ -9,8 +9,10 @@ import { runInAct } from "./act.js";
9
9
  * @param signalName Name of the signal to emit.
10
10
  * @param args Arguments passed to the signal handlers.
11
11
  */
12
- export const fireEvent = async (element: GObject.Object, signalName: string, ...args: unknown[]): Promise<void> => {
12
+ const fireEvent = async (element: GObject.Object, signalName: string, ...args: unknown[]): Promise<void> => {
13
13
  await runInAct(() => {
14
14
  element.emit(signalName, ...args);
15
15
  });
16
16
  };
17
+
18
+ export { fireEvent };