@gtkx/testing 1.0.0-rc.1 → 1.0.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +5 -5
- 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 +9 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/matchers.d.ts +99 -35
- package/dist/matchers.d.ts.map +1 -1
- package/dist/matchers.js +314 -63
- package/dist/matchers.js.map +1 -1
- package/dist/normalize.d.ts +10 -0
- package/dist/normalize.d.ts.map +1 -0
- package/dist/normalize.js +20 -0
- package/dist/normalize.js.map +1 -0
- 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 +129 -90
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +235 -164
- 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 +25 -19
- 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 +65 -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 +7 -5
- package/dist/user-event/controller.d.ts.map +1 -1
- package/dist/user-event/controller.js +16 -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 +36 -25
- package/dist/widget-accessible-properties.d.ts.map +1 -1
- package/dist/widget-accessible-properties.js +215 -68
- 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 +6 -6
- 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 +13 -3
- package/src/matchers.ts +568 -114
- package/src/normalize.ts +28 -0
- package/src/pretty-widget.ts +114 -42
- package/src/queries.ts +418 -256
- package/src/register-test-hooks.ts +4 -1
- package/src/render-hook.tsx +10 -4
- package/src/render.tsx +59 -34
- 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 +111 -32
- package/src/user-event/adjustment.ts +23 -10
- package/src/user-event/click.ts +49 -20
- package/src/user-event/controller.ts +41 -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 +385 -79
- package/src/within.ts +8 -5
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,23 @@ 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;
|
|
16
|
+
/**
|
|
17
|
+
* A widget class usable as a query's `as` constraint, such as `Gtk.Button`. Abstract classes and
|
|
18
|
+
* generated GInterface pseudo-classes are accepted.
|
|
19
|
+
*/
|
|
20
|
+
type WidgetType<T extends Gtk.Widget = Gtk.Widget> = abstract new (...args: never[]) => T;
|
|
15
21
|
|
|
16
22
|
/** Options controlling the default text normalizer. */
|
|
17
|
-
|
|
23
|
+
type NormalizerOptions = {
|
|
18
24
|
/** Trim leading and trailing whitespace. */
|
|
19
25
|
trim?: boolean | undefined;
|
|
20
26
|
/** Collapse runs of whitespace into a single space. */
|
|
@@ -22,7 +28,7 @@ export type NormalizerOptions = {
|
|
|
22
28
|
};
|
|
23
29
|
|
|
24
30
|
/** Options controlling how asynchronous queries and waits poll for a condition. */
|
|
25
|
-
|
|
31
|
+
type WaitForOptions = {
|
|
26
32
|
/** Maximum time in milliseconds to keep retrying before failing. */
|
|
27
33
|
timeout?: number | undefined;
|
|
28
34
|
/** Delay in milliseconds between retries. */
|
|
@@ -34,7 +40,7 @@ export type WaitForOptions = {
|
|
|
34
40
|
};
|
|
35
41
|
|
|
36
42
|
/** Options controlling text matching and, for asynchronous queries, polling behavior. */
|
|
37
|
-
|
|
43
|
+
type MatcherOptions<T extends Gtk.Widget = Gtk.Widget> = {
|
|
38
44
|
/** When true (the default), require an exact match; when false, match case-insensitively as a substring. */
|
|
39
45
|
exact?: boolean | undefined;
|
|
40
46
|
/** Custom normalizer replacing the default; cannot be combined with `trim` or `collapseWhitespace`. */
|
|
@@ -45,10 +51,12 @@ export type MatcherOptions = {
|
|
|
45
51
|
collapseWhitespace?: boolean | undefined;
|
|
46
52
|
/** Whether to include a suggested better query in error messages. */
|
|
47
53
|
suggest?: boolean | undefined;
|
|
54
|
+
/** Restricts matches to instances of this widget class, and narrows the query's return type to it. */
|
|
55
|
+
as?: WidgetType<T> | undefined;
|
|
48
56
|
} & WaitForOptions;
|
|
49
57
|
|
|
50
58
|
/** Constraints on a widget's numeric range value used by role queries. */
|
|
51
|
-
|
|
59
|
+
type ByRoleValue = {
|
|
52
60
|
/** The current value. */
|
|
53
61
|
now?: number | undefined;
|
|
54
62
|
min?: number | undefined;
|
|
@@ -58,7 +66,7 @@ export type ByRoleValue = {
|
|
|
58
66
|
};
|
|
59
67
|
|
|
60
68
|
/** Options for role queries: an accessible name matcher plus accessible state and value constraints. */
|
|
61
|
-
|
|
69
|
+
type ByRoleOptions<T extends Gtk.Widget = Gtk.Widget> = MatcherOptions<T> & {
|
|
62
70
|
name?: Matcher | undefined;
|
|
63
71
|
checked?: boolean | undefined;
|
|
64
72
|
pressed?: boolean | undefined;
|
|
@@ -74,22 +82,61 @@ export type ByRoleOptions = MatcherOptions & {
|
|
|
74
82
|
};
|
|
75
83
|
|
|
76
84
|
/** A React component that wraps rendered content, receiving it as its children. */
|
|
77
|
-
|
|
85
|
+
type WrapperComponent = ComponentType<{
|
|
78
86
|
children: ReactNode;
|
|
79
87
|
}>;
|
|
80
88
|
|
|
81
89
|
type Query = (container: Container, ...args: never[]) => unknown;
|
|
82
|
-
|
|
83
|
-
export type QueryMap = Record<string, Query>;
|
|
90
|
+
type QueryMap = Record<string, Query>;
|
|
84
91
|
|
|
85
92
|
type BoundQuery<Q extends Query> = Q extends (container: Container, ...args: infer A) => infer R
|
|
86
93
|
? (...args: A) => R
|
|
87
94
|
: never;
|
|
88
95
|
|
|
89
|
-
|
|
96
|
+
type BoundCustomQueries<Q extends QueryMap> = { [K in keyof Q]: BoundQuery<Q[K]> };
|
|
90
97
|
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
type QueryFamilyReturns<T extends Gtk.Widget> = {
|
|
99
|
+
queryBy: T | null;
|
|
100
|
+
queryAllBy: T[];
|
|
101
|
+
getBy: T;
|
|
102
|
+
getAllBy: T[];
|
|
103
|
+
findBy: Promise<T>;
|
|
104
|
+
findAllBy: Promise<T[]>;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
type QueryKind = "role" | "text" | "name" | "value";
|
|
108
|
+
|
|
109
|
+
type QueryArgs<Kind extends QueryKind, T extends Gtk.Widget> = Kind extends "role"
|
|
110
|
+
? [role: Gtk.AccessibleRole, options?: ByRoleOptions<T>]
|
|
111
|
+
: Kind extends "name"
|
|
112
|
+
? [name: Matcher, options?: MatcherOptions<T>]
|
|
113
|
+
: Kind extends "value"
|
|
114
|
+
? [value: Matcher, options?: MatcherOptions<T>]
|
|
115
|
+
: [text: Matcher, options?: MatcherOptions<T>];
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* One query family (`queryBy`, `getBy`, `findBy` and their `All` variants) for a single suffix.
|
|
119
|
+
* Each member takes an explicit widget type, as Testing Library's queries do, and also infers it
|
|
120
|
+
* from an `as` option.
|
|
121
|
+
*/
|
|
122
|
+
type QueryFamily<Suffix extends string, Kind extends QueryKind, Head extends unknown[]> = {
|
|
123
|
+
[K in keyof QueryFamilyReturns<Gtk.Widget> as `${K & string}${Suffix}`]: <T extends Gtk.Widget = Gtk.Widget>(
|
|
124
|
+
...args: [...Head, ...QueryArgs<Kind, T>]
|
|
125
|
+
) => QueryFamilyReturns<T>[K];
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
type QueryFamilies<Head extends unknown[]> = QueryFamily<"Role", "role", Head> &
|
|
129
|
+
QueryFamily<"LabelText", "text", Head> &
|
|
130
|
+
QueryFamily<"Text", "text", Head> &
|
|
131
|
+
QueryFamily<"Name", "name", Head> &
|
|
132
|
+
QueryFamily<"PlaceholderText", "text", Head> &
|
|
133
|
+
QueryFamily<"DisplayValue", "value", Head>;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Options for {@link render}: the container and base element to mount into, an optional wrapper,
|
|
137
|
+
* React behavior toggles, error callbacks, and custom queries to bind.
|
|
138
|
+
*/
|
|
139
|
+
type RenderOptions<Q extends QueryMap = Record<never, never>> = {
|
|
93
140
|
container?: Gtk.Widget | RootElement | undefined;
|
|
94
141
|
/** Root of the subtree that bound queries search. */
|
|
95
142
|
baseElement?: Container | undefined;
|
|
@@ -106,14 +153,14 @@ export type RenderOptions<Q extends QueryMap = Record<never, never>> = {
|
|
|
106
153
|
queries?: Q | undefined;
|
|
107
154
|
};
|
|
108
155
|
|
|
109
|
-
|
|
156
|
+
type DebugUtilities = {
|
|
110
157
|
debug: (element?: Container | Container[], options?: PrettyWidgetOptions) => void;
|
|
111
158
|
logRoles: () => void;
|
|
112
159
|
screenshot: (selector?: WindowSelector, options?: ScreenshotOptions) => Promise<ScreenshotResult>;
|
|
113
160
|
};
|
|
114
161
|
|
|
115
162
|
/** A captured screenshot: base64-encoded image data, its MIME type, and pixel dimensions. */
|
|
116
|
-
|
|
163
|
+
type ScreenshotResult = {
|
|
117
164
|
data: string;
|
|
118
165
|
mimeType: string;
|
|
119
166
|
width: number;
|
|
@@ -121,29 +168,61 @@ export type ScreenshotResult = {
|
|
|
121
168
|
};
|
|
122
169
|
|
|
123
170
|
/** Options for capturing a screenshot: the poll timeout and interval, plus a rendering scale factor. */
|
|
124
|
-
|
|
171
|
+
type ScreenshotOptions = Pick<WaitForOptions, "timeout" | "interval"> & {
|
|
125
172
|
/** Device scale factor applied when rendering. */
|
|
126
173
|
scale?: number;
|
|
127
174
|
};
|
|
128
175
|
|
|
129
|
-
/**
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Selects the window to screenshot by index, or by title (exact string or regular expression);
|
|
178
|
+
* undefined targets the default window.
|
|
179
|
+
*/
|
|
180
|
+
type WindowSelector = number | string | RegExp | undefined;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Options for {@link renderHook}: an optional wrapper and the initial props (required unless the
|
|
184
|
+
* props type permits undefined).
|
|
185
|
+
*/
|
|
186
|
+
type RenderHookOptions<Props> = {
|
|
134
187
|
wrapper?: WrapperComponent;
|
|
135
188
|
} & (undefined extends Props
|
|
136
189
|
? {
|
|
137
|
-
|
|
138
|
-
|
|
190
|
+
initialProps?: Props;
|
|
191
|
+
}
|
|
139
192
|
: {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
|
|
193
|
+
initialProps: Props;
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* The result of {@link renderHook}: the latest hook return value plus functions to rerender with new
|
|
198
|
+
* props and to unmount.
|
|
199
|
+
*/
|
|
200
|
+
type RenderHookResult<Result, Props> = {
|
|
145
201
|
/** Holds the most recent value returned by the hook under `current`. */
|
|
146
202
|
result: { current: Result };
|
|
147
203
|
rerender: (newProps?: Props) => Promise<void>;
|
|
148
204
|
unmount: () => Promise<void>;
|
|
149
205
|
};
|
|
206
|
+
|
|
207
|
+
export {
|
|
208
|
+
type MatcherFunction,
|
|
209
|
+
type Matcher,
|
|
210
|
+
type NormalizerFn,
|
|
211
|
+
type NormalizerOptions,
|
|
212
|
+
type WaitForOptions,
|
|
213
|
+
type MatcherOptions,
|
|
214
|
+
type ByRoleValue,
|
|
215
|
+
type ByRoleOptions,
|
|
216
|
+
type WidgetType,
|
|
217
|
+
type WrapperComponent,
|
|
218
|
+
type QueryMap,
|
|
219
|
+
type BoundCustomQueries,
|
|
220
|
+
type QueryFamilies,
|
|
221
|
+
type RenderOptions,
|
|
222
|
+
type DebugUtilities,
|
|
223
|
+
type ScreenshotResult,
|
|
224
|
+
type ScreenshotOptions,
|
|
225
|
+
type WindowSelector,
|
|
226
|
+
type RenderHookOptions,
|
|
227
|
+
type RenderHookResult,
|
|
228
|
+
};
|
|
@@ -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 };
|