@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.
- package/dist/act.d.ts +6 -6
- package/dist/act.d.ts.map +1 -1
- package/dist/act.js +34 -26
- package/dist/act.js.map +1 -1
- package/dist/bound-queries.d.ts +4 -3
- package/dist/bound-queries.d.ts.map +1 -1
- package/dist/bound-queries.js.map +1 -1
- package/dist/build-queries.d.ts +5 -5
- package/dist/build-queries.d.ts.map +1 -1
- package/dist/build-queries.js +21 -9
- package/dist/build-queries.js.map +1 -1
- package/dist/cleanup-registry.d.ts +4 -3
- package/dist/cleanup-registry.d.ts.map +1 -1
- package/dist/cleanup-registry.js +13 -3
- package/dist/cleanup-registry.js.map +1 -1
- package/dist/config.d.ts +5 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -9
- package/dist/config.js.map +1 -1
- package/dist/editable.d.ts +6 -5
- package/dist/editable.d.ts.map +1 -1
- package/dist/editable.js +7 -5
- package/dist/editable.js.map +1 -1
- package/dist/errors.d.ts +7 -6
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +64 -48
- package/dist/errors.js.map +1 -1
- package/dist/fire-event.d.ts +2 -1
- package/dist/fire-event.d.ts.map +1 -1
- package/dist/fire-event.js +2 -1
- package/dist/fire-event.js.map +1 -1
- package/dist/frame-sync.d.ts +2 -1
- package/dist/frame-sync.d.ts.map +1 -1
- package/dist/frame-sync.js +28 -21
- package/dist/frame-sync.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/matchers.d.ts +38 -34
- package/dist/matchers.d.ts.map +1 -1
- package/dist/matchers.js +72 -58
- package/dist/matchers.js.map +1 -1
- package/dist/pretty-widget.d.ts +6 -4
- package/dist/pretty-widget.d.ts.map +1 -1
- package/dist/pretty-widget.js +64 -24
- package/dist/pretty-widget.js.map +1 -1
- package/dist/queries.d.ts +143 -86
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +231 -147
- package/dist/queries.js.map +1 -1
- package/dist/register-test-hooks.js +2 -1
- package/dist/register-test-hooks.js.map +1 -1
- package/dist/render-hook.d.ts +3 -2
- package/dist/render-hook.d.ts.map +1 -1
- package/dist/render-hook.js +2 -1
- package/dist/render-hook.js.map +1 -1
- package/dist/render.d.ts +3 -2
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +24 -18
- package/dist/render.js.map +1 -1
- package/dist/role-helpers.d.ts +6 -5
- package/dist/role-helpers.d.ts.map +1 -1
- package/dist/role-helpers.js +16 -12
- package/dist/role-helpers.js.map +1 -1
- package/dist/screen.d.ts +4 -3
- package/dist/screen.d.ts.map +1 -1
- package/dist/screen.js +13 -11
- package/dist/screen.js.map +1 -1
- package/dist/screenshot.d.ts +4 -3
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +43 -36
- package/dist/screenshot.js.map +1 -1
- package/dist/suggestions.d.ts +11 -6
- package/dist/suggestions.d.ts.map +1 -1
- package/dist/suggestions.js +10 -5
- package/dist/suggestions.js.map +1 -1
- package/dist/traversal.d.ts +7 -7
- package/dist/traversal.d.ts.map +1 -1
- package/dist/traversal.js +17 -11
- package/dist/traversal.js.map +1 -1
- package/dist/types.d.ts +39 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/user-event/adjustment.d.ts +5 -3
- package/dist/user-event/adjustment.d.ts.map +1 -1
- package/dist/user-event/adjustment.js +6 -4
- package/dist/user-event/adjustment.js.map +1 -1
- package/dist/user-event/click.d.ts +6 -5
- package/dist/user-event/click.d.ts.map +1 -1
- package/dist/user-event/click.js +29 -21
- package/dist/user-event/click.js.map +1 -1
- package/dist/user-event/controller.d.ts +6 -5
- package/dist/user-event/controller.d.ts.map +1 -1
- package/dist/user-event/controller.js +9 -6
- package/dist/user-event/controller.js.map +1 -1
- package/dist/user-event/dispatch.d.ts +3 -3
- package/dist/user-event/dispatch.d.ts.map +1 -1
- package/dist/user-event/dispatch.js +3 -2
- package/dist/user-event/dispatch.js.map +1 -1
- package/dist/user-event/event-wrapper.d.ts +2 -1
- package/dist/user-event/event-wrapper.d.ts.map +1 -1
- package/dist/user-event/event-wrapper.js +27 -16
- package/dist/user-event/event-wrapper.js.map +1 -1
- package/dist/user-event/gesture.d.ts +18 -14
- package/dist/user-event/gesture.d.ts.map +1 -1
- package/dist/user-event/gesture.js +70 -37
- package/dist/user-event/gesture.js.map +1 -1
- package/dist/user-event/index.d.ts +17 -10
- package/dist/user-event/index.d.ts.map +1 -1
- package/dist/user-event/index.js +7 -3
- package/dist/user-event/index.js.map +1 -1
- package/dist/user-event/keyboard.d.ts +4 -3
- package/dist/user-event/keyboard.d.ts.map +1 -1
- package/dist/user-event/keyboard.js +89 -58
- package/dist/user-event/keyboard.js.map +1 -1
- package/dist/user-event/pointer.d.ts +7 -3
- package/dist/user-event/pointer.d.ts.map +1 -1
- package/dist/user-event/pointer.js +4 -1
- package/dist/user-event/pointer.js.map +1 -1
- package/dist/user-event/selection.d.ts +3 -2
- package/dist/user-event/selection.d.ts.map +1 -1
- package/dist/user-event/selection.js +26 -27
- package/dist/user-event/selection.js.map +1 -1
- package/dist/user-event/state.d.ts +3 -2
- package/dist/user-event/state.d.ts.map +1 -1
- package/dist/user-event/state.js +2 -1
- package/dist/user-event/state.js.map +1 -1
- package/dist/user-event/text.d.ts +8 -7
- package/dist/user-event/text.d.ts.map +1 -1
- package/dist/user-event/text.js +39 -33
- package/dist/user-event/text.js.map +1 -1
- package/dist/wait-for.d.ts +5 -5
- package/dist/wait-for.d.ts.map +1 -1
- package/dist/wait-for.js +45 -30
- package/dist/wait-for.js.map +1 -1
- package/dist/widget-accessible-properties.d.ts +26 -25
- package/dist/widget-accessible-properties.d.ts.map +1 -1
- package/dist/widget-accessible-properties.js +106 -61
- package/dist/widget-accessible-properties.js.map +1 -1
- package/dist/within.d.ts +2 -1
- package/dist/within.d.ts.map +1 -1
- package/dist/within.js +2 -1
- package/dist/within.js.map +1 -1
- package/package.json +11 -9
- package/src/act.ts +44 -35
- package/src/bound-queries.ts +5 -4
- package/src/build-queries.ts +84 -43
- package/src/cleanup-registry.ts +18 -5
- package/src/config.ts +16 -12
- package/src/editable.ts +13 -6
- package/src/errors.ts +106 -45
- package/src/fire-event.ts +3 -1
- package/src/frame-sync.ts +38 -21
- package/src/index.ts +2 -1
- package/src/matchers.ts +158 -107
- package/src/pretty-widget.ts +114 -42
- package/src/queries.ts +422 -233
- package/src/register-test-hooks.ts +4 -1
- package/src/render-hook.tsx +10 -4
- package/src/render.tsx +58 -33
- package/src/role-helpers.ts +29 -16
- package/src/screen.ts +17 -12
- package/src/screenshot.ts +61 -38
- package/src/suggestions.ts +29 -10
- package/src/traversal.ts +35 -15
- package/src/types.ts +65 -32
- package/src/user-event/adjustment.ts +23 -10
- package/src/user-event/click.ts +49 -20
- package/src/user-event/controller.ts +21 -7
- package/src/user-event/dispatch.ts +4 -2
- package/src/user-event/event-wrapper.ts +42 -16
- package/src/user-event/gesture.ts +135 -50
- package/src/user-event/index.ts +19 -12
- package/src/user-event/keyboard.ts +147 -61
- package/src/user-event/pointer.ts +20 -8
- package/src/user-event/selection.ts +69 -32
- package/src/user-event/state.ts +4 -2
- package/src/user-event/text.ts +73 -38
- package/src/wait-for.ts +77 -36
- package/src/widget-accessible-properties.ts +218 -74
- package/src/within.ts +7 -4
package/src/build-queries.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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 };
|
package/src/cleanup-registry.ts
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
|
-
|
|
1
|
+
type CleanupFunction = () => void | Promise<void>;
|
|
2
2
|
|
|
3
|
-
const cleanupQueue = new Set
|
|
3
|
+
const cleanupQueue: Set<CleanupFunction> = new Set();
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const addToCleanupQueue = (fn: CleanupFunction): void => {
|
|
6
6
|
cleanupQueue.add(fn);
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
const runCleanup = async (): Promise<void> => {
|
|
10
|
+
const failures: unknown[] = [];
|
|
11
|
+
|
|
10
12
|
for (const fn of cleanupQueue) {
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
+
const configure = (newConfig: Partial<Config> | ConfigFn): void => {
|
|
56
54
|
const updates = typeof newConfig === "function" ? newConfig(currentConfig) : newConfig;
|
|
57
|
-
currentConfig
|
|
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
|
-
|
|
3
|
+
type EditableTarget = Gtk.Editable | Gtk.TextView;
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
11
|
+
const isEditable = (widget: unknown): widget is EditableTarget =>
|
|
12
12
|
widget instanceof Gtk.Editable || widget instanceof Gtk.TextView;
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
if (!(widget instanceof Gtk.Editable))
|
|
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
|
-
|
|
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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
39
|
+
|
|
40
|
+
return `'${String(text)}'`;
|
|
16
41
|
};
|
|
17
42
|
|
|
18
43
|
const formatByRoleValue = (value: ByRoleValue): string => {
|
|
19
44
|
const parts: string[] = [];
|
|
20
|
-
|
|
21
|
-
if (value.
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
30
|
-
if (options
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
93
|
+
}
|
|
94
|
+
case "text": {
|
|
55
95
|
return `text ${formatTextMatcher(descriptor.text)}`;
|
|
56
|
-
|
|
96
|
+
}
|
|
97
|
+
case "labelText": {
|
|
57
98
|
return `label text ${formatTextMatcher(descriptor.text)}`;
|
|
58
|
-
|
|
99
|
+
}
|
|
100
|
+
case "name": {
|
|
59
101
|
return `name ${formatTextMatcher(descriptor.name)}`;
|
|
60
|
-
|
|
102
|
+
}
|
|
103
|
+
case "placeholderText": {
|
|
61
104
|
return `placeholder text ${formatTextMatcher(descriptor.text)}`;
|
|
62
|
-
|
|
105
|
+
}
|
|
106
|
+
case "displayValue": {
|
|
63
107
|
return `display value ${formatTextMatcher(descriptor.value)}`;
|
|
108
|
+
}
|
|
64
109
|
}
|
|
65
110
|
};
|
|
66
111
|
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|