@gtkx/testing 2.0.0-beta.1 → 2.0.0-beta.10
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/README.md +43 -40
- package/dist/accessible-native.d.ts +1 -1
- package/dist/accessible-native.d.ts.map +1 -1
- package/dist/accessible-native.js +29 -8
- package/dist/accessible-native.js.map +1 -1
- package/dist/build-queries.d.ts +9 -9
- package/dist/build-queries.d.ts.map +1 -1
- package/dist/build-queries.js +2 -1
- package/dist/build-queries.js.map +1 -1
- package/dist/fire-event.d.ts +3 -3
- package/dist/fire-event.d.ts.map +1 -1
- package/dist/fire-event.js +14 -4
- package/dist/fire-event.js.map +1 -1
- package/dist/matchers.d.ts +52 -56
- package/dist/matchers.d.ts.map +1 -1
- package/dist/matchers.js +4 -16
- package/dist/matchers.js.map +1 -1
- package/dist/queries.d.ts +7 -7
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +18 -10
- package/dist/queries.js.map +1 -1
- package/dist/query-helpers.d.ts +2 -2
- package/dist/query-helpers.d.ts.map +1 -1
- package/dist/query-helpers.js +10 -3
- package/dist/query-helpers.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +4 -0
- package/dist/render.js.map +1 -1
- package/dist/role-helpers.d.ts +1 -1
- package/dist/role-helpers.d.ts.map +1 -1
- package/dist/role-helpers.js +2 -1
- package/dist/role-helpers.js.map +1 -1
- package/dist/screenshot.d.ts +1 -1
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +25 -5
- package/dist/screenshot.js.map +1 -1
- package/dist/suggestions.d.ts +1 -1
- package/dist/suggestions.d.ts.map +1 -1
- package/dist/suggestions.js +3 -1
- package/dist/suggestions.js.map +1 -1
- package/dist/traversal.d.ts +4 -4
- package/dist/traversal.d.ts.map +1 -1
- package/dist/traversal.js +25 -7
- package/dist/traversal.js.map +1 -1
- package/dist/types.d.ts +10 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/user-event/click.d.ts +17 -4
- package/dist/user-event/click.d.ts.map +1 -1
- package/dist/user-event/click.js +28 -9
- package/dist/user-event/click.js.map +1 -1
- package/dist/user-event/controller.d.ts +5 -5
- package/dist/user-event/controller.d.ts.map +1 -1
- package/dist/user-event/controller.js +5 -2
- package/dist/user-event/controller.js.map +1 -1
- package/dist/user-event/index.d.ts +22 -22
- package/dist/user-event/index.d.ts.map +1 -1
- package/dist/user-event/index.js +28 -25
- package/dist/user-event/index.js.map +1 -1
- package/dist/user-event/keyboard.d.ts.map +1 -1
- package/dist/user-event/keyboard.js +23 -2
- package/dist/user-event/keyboard.js.map +1 -1
- package/dist/user-event/list-row-click.d.ts +1 -1
- package/dist/user-event/list-row-click.d.ts.map +1 -1
- package/dist/user-event/list-row-click.js +2 -1
- package/dist/user-event/list-row-click.js.map +1 -1
- package/dist/user-event/pointer.d.ts.map +1 -1
- package/dist/user-event/pointer.js +9 -11
- package/dist/user-event/pointer.js.map +1 -1
- package/dist/user-event/selection.d.ts +2 -2
- package/dist/user-event/selection.d.ts.map +1 -1
- package/dist/user-event/selection.js +8 -2
- package/dist/user-event/selection.js.map +1 -1
- package/dist/wait-for.d.ts +2 -2
- package/dist/wait-for.d.ts.map +1 -1
- package/dist/wait-for.js +6 -4
- package/dist/wait-for.js.map +1 -1
- package/dist/widget-accessible-properties.d.ts +4 -3
- package/dist/widget-accessible-properties.d.ts.map +1 -1
- package/dist/widget-accessible-properties.js +24 -17
- package/dist/widget-accessible-properties.js.map +1 -1
- package/dist/widget-getters.d.ts +7 -9
- package/dist/widget-getters.d.ts.map +1 -1
- package/dist/widget-getters.js.map +1 -1
- package/dist/widget-target.d.ts +4 -0
- package/dist/widget-target.d.ts.map +1 -0
- package/dist/widget-target.js +9 -0
- package/dist/widget-target.js.map +1 -0
- package/package.json +13 -10
- package/src/accessible-native.ts +45 -9
- package/src/build-queries.ts +60 -30
- package/src/fire-event.ts +32 -8
- package/src/matchers.ts +63 -85
- package/src/queries.ts +64 -30
- package/src/query-helpers.ts +17 -9
- package/src/render.tsx +7 -2
- package/src/role-helpers.ts +3 -1
- package/src/screenshot.ts +48 -5
- package/src/suggestions.ts +3 -1
- package/src/traversal.ts +35 -11
- package/src/types.ts +14 -10
- package/src/user-event/click.ts +37 -17
- package/src/user-event/controller.ts +8 -5
- package/src/user-event/index.ts +133 -47
- package/src/user-event/keyboard.ts +32 -3
- package/src/user-event/list-row-click.ts +2 -2
- package/src/user-event/pointer.ts +11 -15
- package/src/user-event/selection.ts +12 -4
- package/src/wait-for.ts +9 -6
- package/src/widget-accessible-properties.ts +39 -25
- package/src/widget-getters.ts +11 -13
- package/src/widget-target.ts +11 -0
package/src/role-helpers.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { sortStringsBy } from "@gtkx/utils";
|
|
|
3
3
|
import { type Container, traverse } from "./traversal.js";
|
|
4
4
|
import { getWidgetAccessibleName, getWidgetLevel } from "./widget-accessible-properties.js";
|
|
5
5
|
import { getTypeTag } from "./widget-getters.js";
|
|
6
|
+
import { requireWidget } from "./widget-target.js";
|
|
6
7
|
|
|
7
8
|
const ROLE_NAMES_BY_VALUE = enumNamesByValue(Gtk.AccessibleRole);
|
|
8
9
|
|
|
@@ -125,6 +126,7 @@ const logRoles = (container: Container): void => {
|
|
|
125
126
|
*
|
|
126
127
|
* @param widget The widget to read the level from.
|
|
127
128
|
*/
|
|
128
|
-
const computeHeadingLevel = (widget: Gtk.
|
|
129
|
+
const computeHeadingLevel = (widget: Gtk.Accessible): number | undefined =>
|
|
130
|
+
getWidgetLevel(requireWidget(widget)) ?? undefined;
|
|
129
131
|
|
|
130
132
|
export { computeHeadingLevel, formatRole, formatRoleList, getRoles, prettyRoles, logRoles };
|
package/src/screenshot.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { getConfig } from "./config.js";
|
|
|
9
9
|
import { now } from "./timers.js";
|
|
10
10
|
import { descendants } from "./traversal.js";
|
|
11
11
|
import { waitFor } from "./wait-for.js";
|
|
12
|
+
import { requireWidget } from "./widget-target.js";
|
|
12
13
|
import { activeToplevel, mappedToplevels } from "./window-state.js";
|
|
13
14
|
|
|
14
15
|
type FrameProbe = {
|
|
@@ -31,6 +32,20 @@ type CaptureOutcome =
|
|
|
31
32
|
{ status: "captured"; result: ScreenshotResult } |
|
|
32
33
|
{ status: "stalled"; failure: CaptureFailure };
|
|
33
34
|
|
|
35
|
+
type BackgroundSnapshot = {
|
|
36
|
+
renderBackground(...args: [context: object, x: number, y: number, width: number, height: number]): void;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type StyledWidget = {
|
|
40
|
+
getStyleContext(): object;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type RenderSize = {
|
|
44
|
+
scale: number;
|
|
45
|
+
width: number;
|
|
46
|
+
height: number;
|
|
47
|
+
};
|
|
48
|
+
|
|
34
49
|
const DEFAULT_SCREENSHOT_INTERVAL = 10;
|
|
35
50
|
const PRESENTATION_PROBE_MS = 250;
|
|
36
51
|
|
|
@@ -179,16 +194,43 @@ const getChildrenNode = (widget: Gtk.Widget, scale: number): Gsk.RenderNode | nu
|
|
|
179
194
|
return snapshot.toNode();
|
|
180
195
|
};
|
|
181
196
|
|
|
197
|
+
const addWindowBackground = (
|
|
198
|
+
widget: Gtk.Widget,
|
|
199
|
+
node: Gsk.RenderNode,
|
|
200
|
+
{ scale, width, height }: RenderSize,
|
|
201
|
+
): Gsk.RenderNode => {
|
|
202
|
+
if (!(widget instanceof Gtk.Window)) {
|
|
203
|
+
return node;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const snapshot = new Gtk.Snapshot();
|
|
207
|
+
const backgroundSnapshot: BackgroundSnapshot = snapshot;
|
|
208
|
+
const styledWidget: StyledWidget = widget;
|
|
209
|
+
snapshot.save();
|
|
210
|
+
snapshot.scale(scale, scale);
|
|
211
|
+
backgroundSnapshot.renderBackground(styledWidget.getStyleContext(), 0, 0, width, height);
|
|
212
|
+
snapshot.restore();
|
|
213
|
+
snapshot.appendNode(node);
|
|
214
|
+
|
|
215
|
+
return snapshot.toNode() ?? node;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const getFallbackNode = (widget: Gtk.Widget, size: RenderSize): Gsk.RenderNode | null => {
|
|
219
|
+
const content = getChildrenNode(widget, size.scale);
|
|
220
|
+
|
|
221
|
+
return content === null ? null : addWindowBackground(widget, content, size);
|
|
222
|
+
};
|
|
223
|
+
|
|
182
224
|
const getRenderNode = (widget: Gtk.Widget, scale: number, width: number, height: number): Gsk.RenderNode | null => {
|
|
183
|
-
const
|
|
225
|
+
const content = getPaintableNode(widget, scale, width, height);
|
|
184
226
|
|
|
185
|
-
if (
|
|
186
|
-
return
|
|
227
|
+
if (content) {
|
|
228
|
+
return content;
|
|
187
229
|
}
|
|
188
230
|
|
|
189
231
|
allocateRoot(widget);
|
|
190
232
|
|
|
191
|
-
return getPaintableNode(widget, scale, width, height) ??
|
|
233
|
+
return getPaintableNode(widget, scale, width, height) ?? getFallbackNode(widget, { scale, width, height });
|
|
192
234
|
};
|
|
193
235
|
|
|
194
236
|
const renderToPng = (widget: Gtk.Widget, node: Gsk.RenderNode, width: number, height: number): ScreenshotResult => {
|
|
@@ -301,7 +343,8 @@ const captureUntilPaintable = (
|
|
|
301
343
|
* @param options Optional scale, timeout, retry interval, and output path.
|
|
302
344
|
* @returns The base64-encoded PNG data along with its mime type and dimensions.
|
|
303
345
|
*/
|
|
304
|
-
const screenshot = async (
|
|
346
|
+
const screenshot = async (target: Gtk.Accessible, options?: ScreenshotOptions): Promise<ScreenshotResult> => {
|
|
347
|
+
const widget = requireWidget(target);
|
|
305
348
|
const scale = options?.scale ?? 1;
|
|
306
349
|
|
|
307
350
|
if (!Number.isFinite(scale) || scale <= 0) {
|
package/src/suggestions.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getWidgetOwnLabel,
|
|
10
10
|
getWidgetPlaceholderText,
|
|
11
11
|
} from "./widget-accessible-properties.js";
|
|
12
|
+
import { requireWidget } from "./widget-target.js";
|
|
12
13
|
|
|
13
14
|
/** The query variant a suggestion targets. */
|
|
14
15
|
type Variant = "get" | "getAll" | "query" | "queryAll" | "find" | "findAll";
|
|
@@ -75,10 +76,11 @@ const textSuggestion = (queryName: Method, variant: Variant, value: string | nul
|
|
|
75
76
|
* @returns The best available suggestion, or undefined when no query applies.
|
|
76
77
|
*/
|
|
77
78
|
const getSuggestedQuery = (
|
|
78
|
-
|
|
79
|
+
target: Gtk.Accessible,
|
|
79
80
|
variant: Variant = "get",
|
|
80
81
|
method?: Method,
|
|
81
82
|
): Suggestion | undefined => {
|
|
83
|
+
const widget = requireWidget(target);
|
|
82
84
|
const builders: Record<Method, () => Suggestion | undefined> = {
|
|
83
85
|
Role: () => roleSuggestion(widget, variant),
|
|
84
86
|
LabelText: () =>
|
package/src/traversal.ts
CHANGED
|
@@ -1,25 +1,41 @@
|
|
|
1
1
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import { getClassType, getInstanceType, typeIsA } from "@gtkx/runtime";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* A scope that resolves to a single root widget: the widget itself, the widget a controller or
|
|
5
6
|
* layout manager is attached to, or a list item's or header's child.
|
|
6
7
|
*/
|
|
7
|
-
type QueryContainer = Gtk.
|
|
8
|
+
type QueryContainer = Gtk.Accessible | Gtk.EventController | Gtk.LayoutManager | Gtk.ListItem | Gtk.ListHeader;
|
|
8
9
|
/**
|
|
9
10
|
* A scope that queries and traversal can run against: a widget, an event
|
|
10
11
|
* controller, a layout manager, a list item or header, an application, or the
|
|
11
12
|
* sentinel representing all current toplevel windows.
|
|
12
13
|
*/
|
|
13
14
|
type Container = QueryContainer | Gtk.Application | typeof TOPLEVELS;
|
|
14
|
-
type WidgetClass<T extends
|
|
15
|
+
type WidgetClass<T extends object> = abstract new (...args: never[]) => T;
|
|
15
16
|
|
|
16
17
|
/** Container sentinel that widens a query to every toplevel window currently open. */
|
|
17
18
|
const TOPLEVELS: unique symbol = Symbol("gtkx.toplevels");
|
|
19
|
+
const nativeWidgetTypes: Map<bigint, boolean> = new Map();
|
|
18
20
|
|
|
19
21
|
const isApplication = (container: Container): container is Gtk.Application => container instanceof Gtk.Application;
|
|
20
22
|
const isAnyWidget = (): boolean => true;
|
|
21
23
|
const isOnScreen = (widget: Gtk.Widget): boolean => widget.getMapped();
|
|
22
24
|
|
|
25
|
+
const isNativeWidget = (widget: Gtk.Widget): boolean => {
|
|
26
|
+
const type = getInstanceType(widget);
|
|
27
|
+
const cached = nativeWidgetTypes.get(type);
|
|
28
|
+
|
|
29
|
+
if (cached !== undefined) {
|
|
30
|
+
return cached;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const isNative = typeIsA(type, getClassType(Gtk.Native));
|
|
34
|
+
nativeWidgetTypes.set(type, isNative);
|
|
35
|
+
|
|
36
|
+
return isNative;
|
|
37
|
+
};
|
|
38
|
+
|
|
23
39
|
const traverseWidgetTree = function* (
|
|
24
40
|
root: Gtk.Widget,
|
|
25
41
|
isIncluded: (widget: Gtk.Widget) => boolean,
|
|
@@ -60,7 +76,7 @@ const ancestors = function* (widget: Gtk.Widget): Generator<Gtk.Widget> {
|
|
|
60
76
|
}
|
|
61
77
|
};
|
|
62
78
|
|
|
63
|
-
const ancestorFor = <T extends
|
|
79
|
+
const ancestorFor = <T extends object>(widget: Gtk.Widget, type: WidgetClass<T>): T | null => {
|
|
64
80
|
for (const ancestor of ancestors(widget)) {
|
|
65
81
|
if (ancestor instanceof type) {
|
|
66
82
|
return ancestor;
|
|
@@ -70,15 +86,23 @@ const ancestorFor = <T extends Gtk.Widget>(widget: Gtk.Widget, type: WidgetClass
|
|
|
70
86
|
return null;
|
|
71
87
|
};
|
|
72
88
|
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const root = widget.getRoot();
|
|
89
|
+
const mappedWidgets = function* (widget: Gtk.Widget, isParentMapped: boolean): Generator<Gtk.Widget> {
|
|
90
|
+
const isMapped = (isParentMapped || isNativeWidget(widget)) && isOnScreen(widget);
|
|
76
91
|
|
|
77
|
-
if (
|
|
78
|
-
|
|
92
|
+
if (isMapped) {
|
|
93
|
+
yield widget;
|
|
79
94
|
}
|
|
80
95
|
|
|
81
|
-
|
|
96
|
+
for (const child of children(widget)) {
|
|
97
|
+
yield* mappedWidgets(child, isMapped);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const relationCandidates = (widget: Gtk.Widget): Iterable<Gtk.Accessible>[] => {
|
|
102
|
+
const root = widget.getRoot();
|
|
103
|
+
const tree: Iterable<Gtk.Accessible> = root instanceof Gtk.Widget ? traverseWidgetTree(root, isAnyWidget) : [];
|
|
104
|
+
|
|
105
|
+
return [widget.listMnemonicLabels(), descendants(widget), tree];
|
|
82
106
|
};
|
|
83
107
|
|
|
84
108
|
const resolveRoot = (container: QueryContainer): Gtk.Widget | null => {
|
|
@@ -98,7 +122,7 @@ const resolveRoot = (container: QueryContainer): Gtk.Widget | null => {
|
|
|
98
122
|
return container.getChild();
|
|
99
123
|
}
|
|
100
124
|
|
|
101
|
-
|
|
125
|
+
throw new TypeError("Query container must resolve to a Gtk.Widget");
|
|
102
126
|
};
|
|
103
127
|
|
|
104
128
|
const roots = function* (container: Container): Generator<Gtk.Widget> {
|
|
@@ -117,7 +141,7 @@ const roots = function* (container: Container): Generator<Gtk.Widget> {
|
|
|
117
141
|
|
|
118
142
|
const traverse = function* (container: Container): Generator<Gtk.Widget> {
|
|
119
143
|
for (const root of roots(container)) {
|
|
120
|
-
yield*
|
|
144
|
+
yield* mappedWidgets(root, true);
|
|
121
145
|
}
|
|
122
146
|
};
|
|
123
147
|
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
-
import type { RootElement } from "@gtkx/react";
|
|
2
|
+
import type { CaughtErrorInfo, RootElement } from "@gtkx/react";
|
|
3
3
|
import type { ComponentType, ErrorInfo, ReactNode } from "react";
|
|
4
4
|
import type { PrettyWidgetOptions } from "./pretty-widget.js";
|
|
5
5
|
import type { Container } from "./traversal.js";
|
|
@@ -15,9 +15,11 @@ type Matcher = string | number | RegExp | MatcherFunction;
|
|
|
15
15
|
type NormalizerFn = (text: string) => string;
|
|
16
16
|
/**
|
|
17
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.
|
|
18
|
+
* generated GInterface pseudo-classes that extend `Gtk.Accessible` are accepted.
|
|
19
19
|
*/
|
|
20
|
-
type WidgetType<T extends Gtk.
|
|
20
|
+
type WidgetType<T extends Gtk.Accessible = Gtk.Accessible> = abstract new (
|
|
21
|
+
...args: never[]
|
|
22
|
+
) => T;
|
|
21
23
|
|
|
22
24
|
/** Options controlling the default text normalizer. */
|
|
23
25
|
type NormalizerOptions = {
|
|
@@ -40,7 +42,7 @@ type WaitForOptions = {
|
|
|
40
42
|
};
|
|
41
43
|
|
|
42
44
|
/** Options controlling text matching and, for asynchronous queries, polling behavior. */
|
|
43
|
-
type MatcherOptions<T extends Gtk.
|
|
45
|
+
type MatcherOptions<T extends Gtk.Accessible = Gtk.Accessible> = {
|
|
44
46
|
/** When true (the default), require an exact match; when false, match case-insensitively as a substring. */
|
|
45
47
|
exact?: boolean | undefined;
|
|
46
48
|
/** Custom normalizer replacing the default; cannot be combined with `trim` or `collapseWhitespace`. */
|
|
@@ -74,7 +76,7 @@ type ByRoleValue = {
|
|
|
74
76
|
};
|
|
75
77
|
|
|
76
78
|
/** Options for role queries: an accessible name matcher plus accessible state and value constraints. */
|
|
77
|
-
type ByRoleOptions<T extends Gtk.
|
|
79
|
+
type ByRoleOptions<T extends Gtk.Accessible = Gtk.Accessible> = MatcherOptions<T> & {
|
|
78
80
|
/** Matcher for the widget's accessible name. */
|
|
79
81
|
name?: Matcher | undefined;
|
|
80
82
|
/** Required checked state; a mixed check button reads as neither, so it matches neither value. */
|
|
@@ -116,7 +118,7 @@ type BoundQuery<Q extends Query> = Q extends (container: Container, ...args: inf
|
|
|
116
118
|
type BoundCustomQueries<Q extends QueryMap> = { [K in keyof Q]: BoundQuery<Q[K]> };
|
|
117
119
|
|
|
118
120
|
/** What each variant of a query family yields for the widget type it matched. */
|
|
119
|
-
type QueryFamilyReturns<T extends Gtk.
|
|
121
|
+
type QueryFamilyReturns<T extends Gtk.Accessible> = {
|
|
120
122
|
/** The single match, or null when nothing matched; throws when more than one matched. */
|
|
121
123
|
queryBy: T | null;
|
|
122
124
|
/** Every match, empty when nothing matched. */
|
|
@@ -135,7 +137,7 @@ type QueryFamilyReturns<T extends Gtk.Widget> = {
|
|
|
135
137
|
type QueryKind = "role" | "text" | "name" | "value";
|
|
136
138
|
|
|
137
139
|
/** The arguments a query of the given kind takes after the family's leading ones. */
|
|
138
|
-
type QueryArgs<Kind extends QueryKind, T extends Gtk.
|
|
140
|
+
type QueryArgs<Kind extends QueryKind, T extends Gtk.Accessible> = Kind extends "role"
|
|
139
141
|
? [role: Gtk.AccessibleRole, options?: ByRoleOptions<T>]
|
|
140
142
|
: Kind extends "name"
|
|
141
143
|
? [name: Matcher, options?: MatcherOptions<T>]
|
|
@@ -148,7 +150,9 @@ type QueryArgs<Kind extends QueryKind, T extends Gtk.Widget> = Kind extends "rol
|
|
|
148
150
|
* ahead of the family's own arguments.
|
|
149
151
|
*/
|
|
150
152
|
type QueryFamily<Suffix extends string, Kind extends QueryKind, Head extends unknown[]> = {
|
|
151
|
-
[K in keyof QueryFamilyReturns<Gtk.Widget> as `${K & string}${Suffix}`]: <
|
|
153
|
+
[K in keyof QueryFamilyReturns<Gtk.Widget> as `${K & string}${Suffix}`]: <
|
|
154
|
+
T extends Gtk.Accessible = Gtk.Widget,
|
|
155
|
+
>(
|
|
152
156
|
...args: [...Head, ...QueryArgs<Kind, T>]
|
|
153
157
|
) => QueryFamilyReturns<T>[K];
|
|
154
158
|
};
|
|
@@ -175,7 +179,7 @@ type RenderOptions<Q extends QueryMap = Record<never, never>> = {
|
|
|
175
179
|
* out of reach of a pointer and a keyboard, so every `userEvent` helper aimed at it rejects
|
|
176
180
|
* after `actionabilityTimeout`; `fireEvent` drives such a tree instead.
|
|
177
181
|
*/
|
|
178
|
-
container?: Gtk.
|
|
182
|
+
container?: Gtk.Accessible | RootElement | undefined;
|
|
179
183
|
/** Root of the subtree that bound queries search. */
|
|
180
184
|
baseElement?: Container | undefined;
|
|
181
185
|
/** Component wrapped around the rendered element, such as a context provider. */
|
|
@@ -185,7 +189,7 @@ type RenderOptions<Q extends QueryMap = Record<never, never>> = {
|
|
|
185
189
|
/** Enable widget animations during the test. */
|
|
186
190
|
areAnimationsEnabled?: boolean | undefined;
|
|
187
191
|
/** Called for errors caught by React error boundaries. */
|
|
188
|
-
onCaughtError?: ((error: unknown, errorInfo:
|
|
192
|
+
onCaughtError?: ((error: unknown, errorInfo: CaughtErrorInfo) => void) | undefined;
|
|
189
193
|
/** Called for errors React recovered from automatically. */
|
|
190
194
|
onRecoverableError?: ((error: unknown, errorInfo: ErrorInfo) => void) | undefined;
|
|
191
195
|
/** Custom queries to bind to the rendered result. */
|
package/src/user-event/click.ts
CHANGED
|
@@ -11,6 +11,7 @@ type PressPoint = { x: number; y: number };
|
|
|
11
11
|
type ClickPhase = "pressed" | "released";
|
|
12
12
|
type ClickSite = { gestures: Gtk.GestureClick[]; point: PressPoint };
|
|
13
13
|
type ClickScope = { isClicked: boolean; isInternalAllowed: boolean };
|
|
14
|
+
type DirectClick = { sites: ClickSite[]; outcome: NativeClick | null };
|
|
14
15
|
|
|
15
16
|
type ClickTarget = {
|
|
16
17
|
widget: Gtk.Widget;
|
|
@@ -59,15 +60,6 @@ const emitGesture = (
|
|
|
59
60
|
}
|
|
60
61
|
};
|
|
61
62
|
|
|
62
|
-
const emitClickPhase = (
|
|
63
|
-
widget: Gtk.Widget,
|
|
64
|
-
controllers: Gtk.GestureClick[],
|
|
65
|
-
nPress: number,
|
|
66
|
-
phase: ClickPhase,
|
|
67
|
-
): void => {
|
|
68
|
-
emitGesture(getCenterPoint(widget), controllers, nPress, phase);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
63
|
const emitSitePhase = (sites: ClickSite[], nPress: number, phase: ClickPhase): void => {
|
|
72
64
|
for (const site of sites) {
|
|
73
65
|
emitGesture(site.point, site.gestures, nPress, phase);
|
|
@@ -97,14 +89,23 @@ const clickGestures = (widget: Gtk.Widget): Gtk.GestureClick[] =>
|
|
|
97
89
|
const getAuthoredClickGestures = (widget: Gtk.Widget): Gtk.GestureClick[] =>
|
|
98
90
|
clickGestures(widget).filter((gesture) => hasClickHandlers(gesture));
|
|
99
91
|
|
|
100
|
-
const isClaimingTarget = (widget: Gtk.Widget): boolean =>
|
|
101
|
-
widget instanceof Gtk.Button || containerFor(widget) !== null;
|
|
102
|
-
|
|
103
92
|
const gestureTargetFor = (widget: Gtk.Widget, isInternalAllowed: boolean): ClickTarget | null => {
|
|
104
93
|
const gestures = clickGestures(widget);
|
|
105
94
|
|
|
106
|
-
if (
|
|
107
|
-
return { widget, container:
|
|
95
|
+
if (widget instanceof Gtk.Button) {
|
|
96
|
+
return { widget, container: null, gestures, isClaiming: true, native: null };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const container = containerFor(widget);
|
|
100
|
+
|
|
101
|
+
if (container !== null) {
|
|
102
|
+
return {
|
|
103
|
+
widget,
|
|
104
|
+
container,
|
|
105
|
+
gestures: gestures.filter((gesture) => hasClickHandlers(gesture)),
|
|
106
|
+
isClaiming: true,
|
|
107
|
+
native: null,
|
|
108
|
+
};
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
const authored = gestures.filter((gesture) => hasClickHandlers(gesture));
|
|
@@ -205,7 +206,7 @@ const applyContainerOutcome = (target: ClickTarget, nPress: number): void => {
|
|
|
205
206
|
}
|
|
206
207
|
|
|
207
208
|
if (isActivatedByClick(container, nPress)) {
|
|
208
|
-
|
|
209
|
+
Gtk.Widget.prototype.activate.call(widget);
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
if (isSelectionReplacedByClick(container, nPress)) {
|
|
@@ -223,6 +224,24 @@ const applyClickOutcome = (target: ClickTarget, nPress: number): void => {
|
|
|
223
224
|
target.native(target.widget, nPress);
|
|
224
225
|
};
|
|
225
226
|
|
|
227
|
+
const applyContainerClick: NativeClick = (widget, nPress) => {
|
|
228
|
+
const container = containerFor(widget);
|
|
229
|
+
|
|
230
|
+
if (container !== null) {
|
|
231
|
+
applyContainerOutcome({ widget, container, gestures: [], isClaiming: true, native: null }, nPress);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const directClickFor = (widget: Gtk.Widget): DirectClick => {
|
|
236
|
+
const native = nativeClickFor(widget, true);
|
|
237
|
+
const container = native === null ? containerFor(widget) : null;
|
|
238
|
+
const gestures =
|
|
239
|
+
native !== null || container !== null ? getAuthoredClickGestures(widget) : clickGestures(widget);
|
|
240
|
+
const target: ClickTarget = { widget, container, gestures, isClaiming: true, native };
|
|
241
|
+
|
|
242
|
+
return { sites: targetSites(widget, target), outcome: native ?? (container === null ? null : applyContainerClick) };
|
|
243
|
+
};
|
|
244
|
+
|
|
226
245
|
const applyClickOutcomes = (targets: ClickTarget[], nPress: number): void => {
|
|
227
246
|
for (const target of targets) {
|
|
228
247
|
applyClickOutcome(target, nPress);
|
|
@@ -246,7 +265,7 @@ const tryActivate = async (widget: Gtk.Widget): Promise<boolean> => {
|
|
|
246
265
|
let isActivated = false;
|
|
247
266
|
|
|
248
267
|
await wrapEvent(widget, () => {
|
|
249
|
-
isActivated =
|
|
268
|
+
isActivated = Gtk.Widget.prototype.activate.call(widget);
|
|
250
269
|
});
|
|
251
270
|
|
|
252
271
|
return isActivated;
|
|
@@ -266,4 +285,5 @@ const dblClick = (widget: Gtk.Widget): Promise<void> => deliverClick(widget, 2);
|
|
|
266
285
|
/** Delivers a triple-click gesture. */
|
|
267
286
|
const tripleClick = (widget: Gtk.Widget): Promise<void> => deliverClick(widget, 3);
|
|
268
287
|
|
|
269
|
-
export {
|
|
288
|
+
export { directClickFor, emitSitePhase, click, dblClick, tripleClick };
|
|
289
|
+
export type { DirectClick };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import { requireWidget } from "../widget-target.js";
|
|
2
3
|
|
|
3
4
|
/** An event controller class that can be constructed without arguments, such as `Gtk.GestureClick`. */
|
|
4
5
|
type ControllerConstructor<T extends Gtk.EventController> = new () => T;
|
|
@@ -11,9 +12,10 @@ type ControllerConstructor<T extends Gtk.EventController> = new () => T;
|
|
|
11
12
|
* @returns The matching controllers, or an empty array when the widget has none.
|
|
12
13
|
*/
|
|
13
14
|
const queryAllControllers = <T extends Gtk.EventController>(
|
|
14
|
-
|
|
15
|
+
target: Gtk.Accessible,
|
|
15
16
|
controllerType: ControllerConstructor<T>,
|
|
16
17
|
): T[] => {
|
|
18
|
+
const widget = requireWidget(target);
|
|
17
19
|
const controllers = widget.observeControllers();
|
|
18
20
|
const nItems = controllers.getNItems();
|
|
19
21
|
const matches: T[] = [];
|
|
@@ -37,7 +39,7 @@ const queryAllControllers = <T extends Gtk.EventController>(
|
|
|
37
39
|
* @returns The first matching controller, or null when the widget has none.
|
|
38
40
|
*/
|
|
39
41
|
const queryController = <T extends Gtk.EventController>(
|
|
40
|
-
widget: Gtk.
|
|
42
|
+
widget: Gtk.Accessible,
|
|
41
43
|
controllerType: ControllerConstructor<T>,
|
|
42
44
|
): T | null => queryAllControllers(widget, controllerType)[0] ?? null;
|
|
43
45
|
|
|
@@ -49,7 +51,7 @@ const queryController = <T extends Gtk.EventController>(
|
|
|
49
51
|
* @throws When the widget has no controller of that type.
|
|
50
52
|
*/
|
|
51
53
|
const getAllControllers = <T extends Gtk.EventController>(
|
|
52
|
-
widget: Gtk.
|
|
54
|
+
widget: Gtk.Accessible,
|
|
53
55
|
controllerType: ControllerConstructor<T>,
|
|
54
56
|
): T[] => {
|
|
55
57
|
const controllers = queryAllControllers(widget, controllerType);
|
|
@@ -69,7 +71,7 @@ const getAllControllers = <T extends Gtk.EventController>(
|
|
|
69
71
|
* @throws When the widget has no controller of that type.
|
|
70
72
|
*/
|
|
71
73
|
const getController = <T extends Gtk.EventController>(
|
|
72
|
-
widget: Gtk.
|
|
74
|
+
widget: Gtk.Accessible,
|
|
73
75
|
controllerType: ControllerConstructor<T>,
|
|
74
76
|
): T => {
|
|
75
77
|
const [controller] = getAllControllers(widget, controllerType);
|
|
@@ -82,9 +84,10 @@ const getController = <T extends Gtk.EventController>(
|
|
|
82
84
|
};
|
|
83
85
|
|
|
84
86
|
const getOrCreateControllers = <T extends Gtk.EventController>(
|
|
85
|
-
|
|
87
|
+
target: Gtk.Accessible,
|
|
86
88
|
controllerType: ControllerConstructor<T>,
|
|
87
89
|
): T[] => {
|
|
90
|
+
const widget = requireWidget(target);
|
|
88
91
|
const existing = queryAllControllers(widget, controllerType);
|
|
89
92
|
|
|
90
93
|
if (existing.length > 0) {
|