@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/suggestions.ts
CHANGED
|
@@ -11,13 +11,15 @@ import {
|
|
|
11
11
|
} from "./widget-accessible-properties.js";
|
|
12
12
|
|
|
13
13
|
/** The query variant a suggestion targets. */
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
type Variant = "get" | "getAll" | "query" | "queryAll" | "find" | "findAll";
|
|
16
15
|
/** The query family a suggestion targets. */
|
|
17
|
-
|
|
16
|
+
type Method = "Role" | "LabelText" | "PlaceholderText" | "Text" | "DisplayValue" | "Name";
|
|
18
17
|
|
|
19
|
-
/**
|
|
20
|
-
|
|
18
|
+
/**
|
|
19
|
+
* A suggested query for reaching a widget, including its family, variant, method name, and a
|
|
20
|
+
* `toString` that renders the full call.
|
|
21
|
+
*/
|
|
22
|
+
type Suggestion = {
|
|
21
23
|
queryName: Method;
|
|
22
24
|
/** The full query function name, such as `getByRole`. */
|
|
23
25
|
queryMethod: string;
|
|
@@ -27,6 +29,7 @@ export type Suggestion = {
|
|
|
27
29
|
|
|
28
30
|
const makeSuggestion = (queryName: Method, variant: Variant, argsText: string): Suggestion => {
|
|
29
31
|
const queryMethod = `${variant}By${queryName}`;
|
|
32
|
+
|
|
30
33
|
return {
|
|
31
34
|
queryName,
|
|
32
35
|
queryMethod,
|
|
@@ -37,29 +40,38 @@ const makeSuggestion = (queryName: Method, variant: Variant, argsText: string):
|
|
|
37
40
|
|
|
38
41
|
const roleSuggestion = (widget: Gtk.Widget, variant: Variant): Suggestion | undefined => {
|
|
39
42
|
const role = widget.getAccessibleRole();
|
|
40
|
-
|
|
43
|
+
|
|
44
|
+
if (role === Gtk.AccessibleRole.NONE || role === Gtk.AccessibleRole.GENERIC) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
41
47
|
|
|
42
48
|
const roleText = `Gtk.AccessibleRole.${formatRole(role).toUpperCase()}`;
|
|
43
49
|
const name = getWidgetAccessibleName(widget);
|
|
50
|
+
|
|
44
51
|
if (name === null) {
|
|
45
52
|
return makeSuggestion("Role", variant, roleText);
|
|
46
53
|
}
|
|
54
|
+
|
|
47
55
|
return makeSuggestion("Role", variant, `${roleText}, { name: '${name}' }`);
|
|
48
56
|
};
|
|
49
57
|
|
|
50
58
|
const textSuggestion = (queryName: Method, variant: Variant, value: string | null): Suggestion | undefined => {
|
|
51
|
-
if (value === null)
|
|
59
|
+
if (value === null) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
52
63
|
return makeSuggestion(queryName, variant, `'${value}'`);
|
|
53
64
|
};
|
|
54
65
|
|
|
55
66
|
/**
|
|
56
|
-
* Computes the recommended query for reaching the given widget, preferring role, then label text,
|
|
67
|
+
* Computes the recommended query for reaching the given widget, preferring role, then label text,
|
|
68
|
+
* placeholder text, text, display value, and name.
|
|
57
69
|
* @param widget Widget to build a suggestion for.
|
|
58
70
|
* @param variant Query variant the suggestion should use.
|
|
59
71
|
* @param method Restrict the suggestion to a specific query family instead of choosing by priority.
|
|
60
72
|
* @returns The best available suggestion, or undefined when no query applies.
|
|
61
73
|
*/
|
|
62
|
-
|
|
74
|
+
const getSuggestedQuery = (
|
|
63
75
|
widget: Gtk.Widget,
|
|
64
76
|
variant: Variant = "get",
|
|
65
77
|
method?: Method,
|
|
@@ -79,9 +91,16 @@ export const getSuggestedQuery = (
|
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
const priority: Method[] = ["Role", "LabelText", "PlaceholderText", "Text", "DisplayValue", "Name"];
|
|
94
|
+
|
|
82
95
|
for (const candidate of priority) {
|
|
83
96
|
const suggestion = builders[candidate]();
|
|
84
|
-
|
|
97
|
+
|
|
98
|
+
if (suggestion) {
|
|
99
|
+
return suggestion;
|
|
100
|
+
}
|
|
85
101
|
}
|
|
102
|
+
|
|
86
103
|
return undefined;
|
|
87
104
|
};
|
|
105
|
+
|
|
106
|
+
export { getSuggestedQuery, type Variant, type Method, type Suggestion };
|
package/src/traversal.ts
CHANGED
|
@@ -1,63 +1,83 @@
|
|
|
1
1
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
2
2
|
|
|
3
|
-
export const TOPLEVELS: unique symbol = Symbol("gtkx.toplevels");
|
|
4
|
-
|
|
5
3
|
type QueryContainer = Gtk.Widget | Gtk.EventController | Gtk.LayoutManager | Gtk.ListItem | Gtk.ListHeader;
|
|
6
|
-
|
|
7
4
|
/**
|
|
8
5
|
* A scope that queries and traversal can run against: a widget, an event
|
|
9
6
|
* controller, a layout manager, a list item or header, an application, or the
|
|
10
7
|
* sentinel representing all current toplevel windows.
|
|
11
8
|
*/
|
|
12
|
-
|
|
9
|
+
type Container = QueryContainer | Gtk.Application | typeof TOPLEVELS;
|
|
10
|
+
|
|
11
|
+
const TOPLEVELS: unique symbol = Symbol("gtkx.toplevels");
|
|
13
12
|
|
|
14
13
|
const isApplication = (container: Container): container is Gtk.Application => container instanceof Gtk.Application;
|
|
15
14
|
|
|
16
15
|
const traverseWidgetTree = function* (root: Gtk.Widget): Generator<Gtk.Widget> {
|
|
17
16
|
yield root;
|
|
18
|
-
|
|
19
17
|
let child = root.getFirstChild();
|
|
18
|
+
|
|
20
19
|
while (child) {
|
|
21
20
|
yield* traverseWidgetTree(child);
|
|
22
21
|
child = child.getNextSibling();
|
|
23
22
|
}
|
|
24
23
|
};
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
const descendants = function* (widget: Gtk.Widget): Generator<Gtk.Widget> {
|
|
27
26
|
const tree = traverseWidgetTree(widget);
|
|
28
27
|
tree.next();
|
|
29
28
|
yield* tree;
|
|
30
29
|
};
|
|
31
30
|
|
|
32
31
|
const resolveRoot = (container: QueryContainer): Gtk.Widget | null => {
|
|
33
|
-
if (container instanceof Gtk.Widget)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
if (container instanceof Gtk.Widget) {
|
|
33
|
+
return container;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (container instanceof Gtk.EventController) {
|
|
37
|
+
return container.getWidget();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (container instanceof Gtk.LayoutManager) {
|
|
41
|
+
return container.getWidget();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (container instanceof Gtk.ListItem || container instanceof Gtk.ListHeader) {
|
|
45
|
+
return container.getChild();
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
return null;
|
|
38
49
|
};
|
|
39
50
|
|
|
40
|
-
|
|
51
|
+
const roots = function* (container: Container): Generator<Gtk.Widget> {
|
|
41
52
|
if (container === TOPLEVELS || isApplication(container)) {
|
|
42
53
|
yield* Gtk.Window.listToplevels();
|
|
54
|
+
|
|
43
55
|
return;
|
|
44
56
|
}
|
|
57
|
+
|
|
45
58
|
const root = resolveRoot(container);
|
|
46
|
-
|
|
59
|
+
|
|
60
|
+
if (root) {
|
|
61
|
+
yield root;
|
|
62
|
+
}
|
|
47
63
|
};
|
|
48
64
|
|
|
49
|
-
|
|
65
|
+
const traverse = function* (container: Container): Generator<Gtk.Widget> {
|
|
50
66
|
for (const root of roots(container)) {
|
|
51
67
|
yield* traverseWidgetTree(root);
|
|
52
68
|
}
|
|
53
69
|
};
|
|
54
70
|
|
|
55
|
-
|
|
71
|
+
const findAll = (container: Container, isMatch: (node: Gtk.Widget) => boolean): Gtk.Widget[] => {
|
|
56
72
|
const results: Gtk.Widget[] = [];
|
|
73
|
+
|
|
57
74
|
for (const node of traverse(container)) {
|
|
58
|
-
if (
|
|
75
|
+
if (isMatch(node)) {
|
|
59
76
|
results.push(node);
|
|
60
77
|
}
|
|
61
78
|
}
|
|
79
|
+
|
|
62
80
|
return results;
|
|
63
81
|
};
|
|
82
|
+
|
|
83
|
+
export { TOPLEVELS, descendants, roots, traverse, findAll, type Container };
|
package/src/types.ts
CHANGED
|
@@ -4,17 +4,18 @@ import type { ComponentType, ErrorInfo, ReactNode } from "react";
|
|
|
4
4
|
import type { PrettyWidgetOptions } from "./pretty-widget.js";
|
|
5
5
|
import type { Container } from "./traversal.js";
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
/**
|
|
8
|
+
* A custom matcher predicate: given a candidate widget's normalized text content and the widget
|
|
9
|
+
* itself, returns whether it matches.
|
|
10
|
+
*/
|
|
11
|
+
type MatcherFunction = (content: string, widget: Gtk.Widget) => boolean;
|
|
10
12
|
/** A value used to match widget text: a string or number, a regular expression, or a custom {@link MatcherFunction}. */
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
type Matcher = string | number | RegExp | MatcherFunction;
|
|
13
14
|
/** Normalizes a widget's text before it is compared against a matcher. */
|
|
14
|
-
|
|
15
|
+
type NormalizerFn = (text: string) => string;
|
|
15
16
|
|
|
16
17
|
/** Options controlling the default text normalizer. */
|
|
17
|
-
|
|
18
|
+
type NormalizerOptions = {
|
|
18
19
|
/** Trim leading and trailing whitespace. */
|
|
19
20
|
trim?: boolean | undefined;
|
|
20
21
|
/** Collapse runs of whitespace into a single space. */
|
|
@@ -22,7 +23,7 @@ export type NormalizerOptions = {
|
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
/** Options controlling how asynchronous queries and waits poll for a condition. */
|
|
25
|
-
|
|
26
|
+
type WaitForOptions = {
|
|
26
27
|
/** Maximum time in milliseconds to keep retrying before failing. */
|
|
27
28
|
timeout?: number | undefined;
|
|
28
29
|
/** Delay in milliseconds between retries. */
|
|
@@ -34,7 +35,7 @@ export type WaitForOptions = {
|
|
|
34
35
|
};
|
|
35
36
|
|
|
36
37
|
/** Options controlling text matching and, for asynchronous queries, polling behavior. */
|
|
37
|
-
|
|
38
|
+
type MatcherOptions = {
|
|
38
39
|
/** When true (the default), require an exact match; when false, match case-insensitively as a substring. */
|
|
39
40
|
exact?: boolean | undefined;
|
|
40
41
|
/** Custom normalizer replacing the default; cannot be combined with `trim` or `collapseWhitespace`. */
|
|
@@ -48,7 +49,7 @@ export type MatcherOptions = {
|
|
|
48
49
|
} & WaitForOptions;
|
|
49
50
|
|
|
50
51
|
/** Constraints on a widget's numeric range value used by role queries. */
|
|
51
|
-
|
|
52
|
+
type ByRoleValue = {
|
|
52
53
|
/** The current value. */
|
|
53
54
|
now?: number | undefined;
|
|
54
55
|
min?: number | undefined;
|
|
@@ -58,7 +59,7 @@ export type ByRoleValue = {
|
|
|
58
59
|
};
|
|
59
60
|
|
|
60
61
|
/** Options for role queries: an accessible name matcher plus accessible state and value constraints. */
|
|
61
|
-
|
|
62
|
+
type ByRoleOptions = MatcherOptions & {
|
|
62
63
|
name?: Matcher | undefined;
|
|
63
64
|
checked?: boolean | undefined;
|
|
64
65
|
pressed?: boolean | undefined;
|
|
@@ -74,22 +75,24 @@ export type ByRoleOptions = MatcherOptions & {
|
|
|
74
75
|
};
|
|
75
76
|
|
|
76
77
|
/** A React component that wraps rendered content, receiving it as its children. */
|
|
77
|
-
|
|
78
|
+
type WrapperComponent = ComponentType<{
|
|
78
79
|
children: ReactNode;
|
|
79
80
|
}>;
|
|
80
81
|
|
|
81
82
|
type Query = (container: Container, ...args: never[]) => unknown;
|
|
82
|
-
|
|
83
|
-
export type QueryMap = Record<string, Query>;
|
|
83
|
+
type QueryMap = Record<string, Query>;
|
|
84
84
|
|
|
85
85
|
type BoundQuery<Q extends Query> = Q extends (container: Container, ...args: infer A) => infer R
|
|
86
86
|
? (...args: A) => R
|
|
87
87
|
: never;
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
type BoundCustomQueries<Q extends QueryMap> = { [K in keyof Q]: BoundQuery<Q[K]> };
|
|
90
90
|
|
|
91
|
-
/**
|
|
92
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Options for {@link render}: the container and base element to mount into, an optional wrapper,
|
|
93
|
+
* React behavior toggles, error callbacks, and custom queries to bind.
|
|
94
|
+
*/
|
|
95
|
+
type RenderOptions<Q extends QueryMap = Record<never, never>> = {
|
|
93
96
|
container?: Gtk.Widget | RootElement | undefined;
|
|
94
97
|
/** Root of the subtree that bound queries search. */
|
|
95
98
|
baseElement?: Container | undefined;
|
|
@@ -106,14 +109,14 @@ export type RenderOptions<Q extends QueryMap = Record<never, never>> = {
|
|
|
106
109
|
queries?: Q | undefined;
|
|
107
110
|
};
|
|
108
111
|
|
|
109
|
-
|
|
112
|
+
type DebugUtilities = {
|
|
110
113
|
debug: (element?: Container | Container[], options?: PrettyWidgetOptions) => void;
|
|
111
114
|
logRoles: () => void;
|
|
112
115
|
screenshot: (selector?: WindowSelector, options?: ScreenshotOptions) => Promise<ScreenshotResult>;
|
|
113
116
|
};
|
|
114
117
|
|
|
115
118
|
/** A captured screenshot: base64-encoded image data, its MIME type, and pixel dimensions. */
|
|
116
|
-
|
|
119
|
+
type ScreenshotResult = {
|
|
117
120
|
data: string;
|
|
118
121
|
mimeType: string;
|
|
119
122
|
width: number;
|
|
@@ -121,29 +124,59 @@ export type ScreenshotResult = {
|
|
|
121
124
|
};
|
|
122
125
|
|
|
123
126
|
/** Options for capturing a screenshot: the poll timeout and interval, plus a rendering scale factor. */
|
|
124
|
-
|
|
127
|
+
type ScreenshotOptions = Pick<WaitForOptions, "timeout" | "interval"> & {
|
|
125
128
|
/** Device scale factor applied when rendering. */
|
|
126
129
|
scale?: number;
|
|
127
130
|
};
|
|
128
131
|
|
|
129
|
-
/**
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Selects the window to screenshot by index, or by title (exact string or regular expression);
|
|
134
|
+
* undefined targets the default window.
|
|
135
|
+
*/
|
|
136
|
+
type WindowSelector = number | string | RegExp | undefined;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Options for {@link renderHook}: an optional wrapper and the initial props (required unless the
|
|
140
|
+
* props type permits undefined).
|
|
141
|
+
*/
|
|
142
|
+
type RenderHookOptions<Props> = {
|
|
134
143
|
wrapper?: WrapperComponent;
|
|
135
144
|
} & (undefined extends Props
|
|
136
145
|
? {
|
|
137
|
-
|
|
138
|
-
|
|
146
|
+
initialProps?: Props;
|
|
147
|
+
}
|
|
139
148
|
: {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
|
|
149
|
+
initialProps: Props;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The result of {@link renderHook}: the latest hook return value plus functions to rerender with new
|
|
154
|
+
* props and to unmount.
|
|
155
|
+
*/
|
|
156
|
+
type RenderHookResult<Result, Props> = {
|
|
145
157
|
/** Holds the most recent value returned by the hook under `current`. */
|
|
146
158
|
result: { current: Result };
|
|
147
159
|
rerender: (newProps?: Props) => Promise<void>;
|
|
148
160
|
unmount: () => Promise<void>;
|
|
149
161
|
};
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
type MatcherFunction,
|
|
165
|
+
type Matcher,
|
|
166
|
+
type NormalizerFn,
|
|
167
|
+
type NormalizerOptions,
|
|
168
|
+
type WaitForOptions,
|
|
169
|
+
type MatcherOptions,
|
|
170
|
+
type ByRoleValue,
|
|
171
|
+
type ByRoleOptions,
|
|
172
|
+
type WrapperComponent,
|
|
173
|
+
type QueryMap,
|
|
174
|
+
type BoundCustomQueries,
|
|
175
|
+
type RenderOptions,
|
|
176
|
+
type DebugUtilities,
|
|
177
|
+
type ScreenshotResult,
|
|
178
|
+
type ScreenshotOptions,
|
|
179
|
+
type WindowSelector,
|
|
180
|
+
type RenderHookOptions,
|
|
181
|
+
type RenderHookResult,
|
|
182
|
+
};
|
|
@@ -1,49 +1,62 @@
|
|
|
1
1
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
2
2
|
import { wrapEvent } from "./event-wrapper.js";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/** A scroll distance in pixels along each axis. */
|
|
5
|
+
type ScrollDelta = {
|
|
5
6
|
x?: number;
|
|
6
7
|
y?: number;
|
|
7
8
|
};
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
type ScrollAdjustments = {
|
|
11
|
+
horizontal: Gtk.Adjustment | null;
|
|
12
|
+
vertical: Gtk.Adjustment | null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const slide = (widget: Gtk.Widget, value: number): Promise<void> =>
|
|
10
16
|
wrapEvent(widget, () => {
|
|
11
17
|
if (!(widget instanceof Gtk.Range)) {
|
|
12
|
-
throw new
|
|
18
|
+
throw new TypeError(
|
|
19
|
+
`userEvent.slide requires a Gtk.Range (e.g. Gtk.Scale), got ${widget.constructor.name}`,
|
|
20
|
+
);
|
|
13
21
|
}
|
|
22
|
+
|
|
14
23
|
widget.emit("change-value", Gtk.ScrollType.JUMP, value);
|
|
15
24
|
});
|
|
16
25
|
|
|
17
|
-
type ScrollAdjustments = {
|
|
18
|
-
horizontal: Gtk.Adjustment | null;
|
|
19
|
-
vertical: Gtk.Adjustment | null;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
26
|
const resolveScrollAdjustments = (widget: Gtk.Widget): ScrollAdjustments | null => {
|
|
23
27
|
for (let current: Gtk.Widget | null = widget; current; current = current.getParent()) {
|
|
24
28
|
if (current instanceof Gtk.ScrolledWindow) {
|
|
25
29
|
return { horizontal: current.getHadjustment(), vertical: current.getVadjustment() };
|
|
26
30
|
}
|
|
31
|
+
|
|
27
32
|
if (current instanceof Gtk.Scrollable) {
|
|
28
33
|
return { horizontal: current.getHadjustment(), vertical: current.getVadjustment() };
|
|
29
34
|
}
|
|
30
35
|
}
|
|
36
|
+
|
|
31
37
|
return null;
|
|
32
38
|
};
|
|
33
39
|
|
|
34
40
|
const applyScrollDelta = (adjustment: Gtk.Adjustment | null, delta: number): void => {
|
|
35
|
-
if (!adjustment || delta === 0)
|
|
41
|
+
if (!adjustment || delta === 0) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
adjustment.setValue(adjustment.getValue() + delta);
|
|
37
46
|
};
|
|
38
47
|
|
|
39
|
-
|
|
48
|
+
const scroll = (widget: Gtk.Widget, delta: ScrollDelta): Promise<void> =>
|
|
40
49
|
wrapEvent(widget, () => {
|
|
41
50
|
const adjustments = resolveScrollAdjustments(widget);
|
|
51
|
+
|
|
42
52
|
if (!adjustments) {
|
|
43
53
|
throw new Error(
|
|
44
54
|
"userEvent.scroll: no Gtk.ScrolledWindow or Gtk.Scrollable found on the widget or its ancestors",
|
|
45
55
|
);
|
|
46
56
|
}
|
|
57
|
+
|
|
47
58
|
applyScrollDelta(adjustments.horizontal, delta.x ?? 0);
|
|
48
59
|
applyScrollDelta(adjustments.vertical, delta.y ?? 0);
|
|
49
60
|
});
|
|
61
|
+
|
|
62
|
+
export { slide, scroll, type ScrollDelta };
|
package/src/user-event/click.ts
CHANGED
|
@@ -2,25 +2,31 @@ import * as Gtk from "@gtkx/gi/gtk";
|
|
|
2
2
|
import { getOrCreateControllers, queryController } from "./controller.js";
|
|
3
3
|
import { wrapEvent } from "./event-wrapper.js";
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const getPressPoint = (widget: Gtk.Widget): { x: number; y: number } => {
|
|
6
6
|
const width = widget.getWidth();
|
|
7
7
|
const height = widget.getHeight();
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
if (width > 0 && height > 0) {
|
|
10
|
+
return { x: width / 2, y: height / 2 };
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
return { x: 0, y: 0 };
|
|
10
14
|
};
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
const { x, y } =
|
|
16
|
+
const emitGesture = (widget: Gtk.Widget, controllers: Gtk.GestureClick[], nPress: number, signal: string): void => {
|
|
17
|
+
const { x, y } = getPressPoint(widget);
|
|
18
|
+
|
|
14
19
|
for (const controller of controllers) {
|
|
15
|
-
controller.emit(
|
|
20
|
+
controller.emit(signal, nPress, x, y);
|
|
16
21
|
}
|
|
17
22
|
};
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
const emitPress = (widget: Gtk.Widget, controllers: Gtk.GestureClick[], nPress: number): void => {
|
|
25
|
+
emitGesture(widget, controllers, nPress, "pressed");
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const emitRelease = (widget: Gtk.Widget, controllers: Gtk.GestureClick[], nPress: number): void => {
|
|
29
|
+
emitGesture(widget, controllers, nPress, "released");
|
|
24
30
|
};
|
|
25
31
|
|
|
26
32
|
const emitClickSequence = (widget: Gtk.Widget, target: Gtk.Widget, nPress: number): Promise<void> =>
|
|
@@ -35,36 +41,59 @@ const emitClickSequence = (widget: Gtk.Widget, target: Gtk.Widget, nPress: numbe
|
|
|
35
41
|
|
|
36
42
|
const findClickableAncestor = (widget: Gtk.Widget): Gtk.Widget | null => {
|
|
37
43
|
let current = widget.getParent();
|
|
44
|
+
|
|
38
45
|
while (current) {
|
|
39
46
|
if (current instanceof Gtk.Button || queryController(current, Gtk.GestureClick) !== null) {
|
|
40
47
|
return current;
|
|
41
48
|
}
|
|
49
|
+
|
|
42
50
|
current = current.getParent();
|
|
43
51
|
}
|
|
52
|
+
|
|
44
53
|
return null;
|
|
45
54
|
};
|
|
46
55
|
|
|
47
|
-
|
|
56
|
+
const tryActivate = async (widget: Gtk.Widget): Promise<boolean> => {
|
|
57
|
+
if (widget.getAccessibleRole() === Gtk.AccessibleRole.LABEL) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let isActivated = false;
|
|
62
|
+
|
|
63
|
+
await wrapEvent(widget, () => {
|
|
64
|
+
isActivated = widget.activate();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return isActivated;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const click = async (widget: Gtk.Widget): Promise<void> => {
|
|
48
71
|
if (widget instanceof Gtk.Button) {
|
|
49
72
|
await emitClickSequence(widget, widget, 1);
|
|
73
|
+
|
|
50
74
|
return;
|
|
51
75
|
}
|
|
76
|
+
|
|
52
77
|
if (widget instanceof Gtk.Switch) {
|
|
53
78
|
await wrapEvent(widget, () => {
|
|
54
79
|
widget.setActive(!widget.getActive());
|
|
55
80
|
});
|
|
81
|
+
|
|
56
82
|
return;
|
|
57
83
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
activated = widget.activate();
|
|
62
|
-
});
|
|
63
|
-
if (activated) return;
|
|
84
|
+
|
|
85
|
+
if (await tryActivate(widget)) {
|
|
86
|
+
return;
|
|
64
87
|
}
|
|
88
|
+
|
|
65
89
|
const target = findClickableAncestor(widget);
|
|
66
|
-
|
|
90
|
+
|
|
91
|
+
if (target) {
|
|
92
|
+
await emitClickSequence(widget, target, 1);
|
|
93
|
+
}
|
|
67
94
|
};
|
|
68
95
|
|
|
69
|
-
|
|
70
|
-
|
|
96
|
+
const dblClick = (widget: Gtk.Widget): Promise<void> => emitClickSequence(widget, widget, 2);
|
|
97
|
+
const tripleClick = (widget: Gtk.Widget): Promise<void> => emitClickSequence(widget, widget, 3);
|
|
98
|
+
|
|
99
|
+
export { emitPress, emitRelease, click, dblClick, tripleClick };
|
|
@@ -1,44 +1,58 @@
|
|
|
1
1
|
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
type ControllerConstructor<T extends Gtk.EventController> = new () => T;
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const queryAllControllers = <T extends Gtk.EventController>(
|
|
6
6
|
widget: Gtk.Widget,
|
|
7
7
|
controllerType: ControllerConstructor<T>,
|
|
8
8
|
): T[] => {
|
|
9
9
|
const controllers = widget.observeControllers();
|
|
10
10
|
const nItems = controllers.getNItems();
|
|
11
11
|
const matches: T[] = [];
|
|
12
|
+
|
|
12
13
|
for (let i = 0; i < nItems; i++) {
|
|
13
14
|
const controller = controllers.getItem(i);
|
|
14
|
-
|
|
15
|
+
|
|
16
|
+
if (controller instanceof controllerType) {
|
|
17
|
+
matches.push(controller);
|
|
18
|
+
}
|
|
15
19
|
}
|
|
20
|
+
|
|
16
21
|
return matches;
|
|
17
22
|
};
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
const queryController = <T extends Gtk.EventController>(
|
|
20
25
|
widget: Gtk.Widget,
|
|
21
26
|
controllerType: ControllerConstructor<T>,
|
|
22
27
|
): T | null => queryAllControllers(widget, controllerType)[0] ?? null;
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
const getAllControllers = <T extends Gtk.EventController>(
|
|
25
30
|
widget: Gtk.Widget,
|
|
26
31
|
controllerType: ControllerConstructor<T>,
|
|
27
32
|
): T[] => {
|
|
28
33
|
const controllers = queryAllControllers(widget, controllerType);
|
|
34
|
+
|
|
29
35
|
if (controllers.length === 0) {
|
|
30
36
|
throw new Error(`No ${controllerType.name} controller is attached to the widget`);
|
|
31
37
|
}
|
|
38
|
+
|
|
32
39
|
return controllers;
|
|
33
40
|
};
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
const getOrCreateControllers = <T extends Gtk.EventController>(
|
|
36
43
|
widget: Gtk.Widget,
|
|
37
44
|
controllerType: ControllerConstructor<T>,
|
|
38
45
|
): T[] => {
|
|
39
46
|
const existing = queryAllControllers(widget, controllerType);
|
|
40
|
-
|
|
47
|
+
|
|
48
|
+
if (existing.length > 0) {
|
|
49
|
+
return existing;
|
|
50
|
+
}
|
|
51
|
+
|
|
41
52
|
const controller = new controllerType();
|
|
42
53
|
widget.addController(controller);
|
|
54
|
+
|
|
43
55
|
return [controller];
|
|
44
56
|
};
|
|
57
|
+
|
|
58
|
+
export { queryAllControllers, queryController, getAllControllers, getOrCreateControllers, type ControllerConstructor };
|
|
@@ -10,14 +10,16 @@ const emitOnAll = async <T extends Gtk.EventController>(controllers: T[], emit:
|
|
|
10
10
|
}
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
const dispatchOnOrCreateControllers = <T extends Gtk.EventController>(
|
|
14
14
|
widget: Gtk.Widget,
|
|
15
15
|
controllerType: ControllerConstructor<T>,
|
|
16
16
|
emit: ControllerEmit<T>,
|
|
17
17
|
): Promise<void> => wrapEvent(widget, () => emitOnAll(getOrCreateControllers(widget, controllerType), emit));
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
const dispatchOnControllers = <T extends Gtk.EventController>(
|
|
20
20
|
widget: Gtk.Widget,
|
|
21
21
|
controllerType: ControllerConstructor<T>,
|
|
22
22
|
emit: ControllerEmit<T>,
|
|
23
23
|
): Promise<void> => wrapEvent(widget, () => emitOnAll(getAllControllers(widget, controllerType), emit));
|
|
24
|
+
|
|
25
|
+
export { dispatchOnOrCreateControllers, dispatchOnControllers };
|