@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/fire-event.ts
CHANGED
|
@@ -7,13 +7,21 @@ import { runInAct } from "./act.js";
|
|
|
7
7
|
*/
|
|
8
8
|
type WidgetEvent = {
|
|
9
9
|
/** The GObject instance the signal is emitted on. */
|
|
10
|
-
target: GObject.Object
|
|
10
|
+
target: Pick<GObject.Object, "__type__">;
|
|
11
11
|
/** Name of the signal to emit. */
|
|
12
12
|
signalName: string;
|
|
13
13
|
/** Arguments passed to the signal handlers. */
|
|
14
14
|
args: unknown[];
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
const requireObject = (target: object): GObject.Object => {
|
|
18
|
+
if (!(target instanceof GObject.Object)) {
|
|
19
|
+
throw new TypeError("Expected a GObject instance");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return target;
|
|
23
|
+
};
|
|
24
|
+
|
|
17
25
|
/**
|
|
18
26
|
* Records a signal emission without delivering it, so it can be inspected or held and passed to
|
|
19
27
|
* {@link fireEvent} later.
|
|
@@ -23,18 +31,28 @@ type WidgetEvent = {
|
|
|
23
31
|
* @param args Arguments to pass to the signal handlers.
|
|
24
32
|
* @returns The recorded emission.
|
|
25
33
|
*/
|
|
26
|
-
const createEvent = (
|
|
27
|
-
target,
|
|
34
|
+
const createEvent = (
|
|
35
|
+
target: Pick<GObject.Object, "__type__">,
|
|
36
|
+
signalName: string,
|
|
37
|
+
...args: unknown[]
|
|
38
|
+
): WidgetEvent => ({
|
|
39
|
+
target: requireObject(target),
|
|
28
40
|
signalName,
|
|
29
41
|
args,
|
|
30
42
|
});
|
|
31
43
|
|
|
32
44
|
const dispatchEvent = async (event: WidgetEvent): Promise<void> => {
|
|
45
|
+
const target = requireObject(event.target);
|
|
46
|
+
|
|
33
47
|
await runInAct(() => {
|
|
34
|
-
|
|
48
|
+
Reflect.apply(GObject.signalEmit, undefined, [target, event.signalName, ...event.args]);
|
|
35
49
|
});
|
|
36
50
|
};
|
|
37
51
|
|
|
52
|
+
const isWidgetEvent = (value: object): value is WidgetEvent =>
|
|
53
|
+
"target" in value && "signalName" in value && "args" in value &&
|
|
54
|
+
typeof value.signalName === "string" && Array.isArray(value.args);
|
|
55
|
+
|
|
38
56
|
/**
|
|
39
57
|
* Emits a recorded signal emission inside React's act environment, so any resulting state updates are
|
|
40
58
|
* flushed before the promise resolves.
|
|
@@ -50,14 +68,18 @@ function fireEvent(event: WidgetEvent): Promise<void>;
|
|
|
50
68
|
* @param signalName Name of the signal to emit.
|
|
51
69
|
* @param args Arguments passed to the signal handlers.
|
|
52
70
|
*/
|
|
53
|
-
function fireEvent(
|
|
71
|
+
function fireEvent(
|
|
72
|
+
target: Pick<GObject.Object, "__type__">,
|
|
73
|
+
signalName: string,
|
|
74
|
+
...args: unknown[]
|
|
75
|
+
): Promise<void>;
|
|
54
76
|
|
|
55
77
|
function fireEvent(
|
|
56
|
-
eventOrTarget: WidgetEvent | GObject.Object,
|
|
78
|
+
eventOrTarget: WidgetEvent | Pick<GObject.Object, "__type__">,
|
|
57
79
|
signalName?: string,
|
|
58
80
|
...args: unknown[]
|
|
59
81
|
): Promise<void> {
|
|
60
|
-
if (
|
|
82
|
+
if (signalName === undefined && isWidgetEvent(eventOrTarget)) {
|
|
61
83
|
return dispatchEvent(eventOrTarget);
|
|
62
84
|
}
|
|
63
85
|
|
|
@@ -65,7 +87,9 @@ function fireEvent(
|
|
|
65
87
|
throw new Error("Unable to fire an event: pass a signal name, or an event built by createEvent.");
|
|
66
88
|
}
|
|
67
89
|
|
|
68
|
-
|
|
90
|
+
const target = requireObject(eventOrTarget);
|
|
91
|
+
|
|
92
|
+
return dispatchEvent({ target, signalName, args });
|
|
69
93
|
}
|
|
70
94
|
|
|
71
95
|
export { createEvent, fireEvent, type WidgetEvent };
|
package/src/matchers.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SyncMatcherResult } from "vitest";
|
|
2
2
|
import * as GObject from "@gtkx/gi/gobject";
|
|
3
3
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
4
4
|
import type { ByRoleOptions, Matcher, MatcherOptions } from "./types.js";
|
|
@@ -57,7 +57,7 @@ type TextContentOptions = {
|
|
|
57
57
|
/** The expected value for a style class: an exact class name or a regular expression. */
|
|
58
58
|
type ClassExpectation = string | RegExp;
|
|
59
59
|
/** The outcome of a matcher: whether it passed, and the failure text built on demand. */
|
|
60
|
-
type MatcherResult = Pick<
|
|
60
|
+
type MatcherResult = Pick<SyncMatcherResult, "message" | "pass">;
|
|
61
61
|
|
|
62
62
|
/** The matcher state bound as `this`, supplying the test runner's deep equality check. */
|
|
63
63
|
type MatcherContext = {
|
|
@@ -77,7 +77,7 @@ type TextQueryArgs = [text: Matcher, options?: MatcherOptions];
|
|
|
77
77
|
type ContainmentMatcher<Args extends unknown[]> = (received: unknown, ...args: Args) => MatcherResult;
|
|
78
78
|
type TextMatcherContext = { matcherName: string; widget: Gtk.Widget; actual: string | null };
|
|
79
79
|
type ClassArguments = { expected: ClassExpectation[]; isExact: boolean };
|
|
80
|
-
type QueryAll<Args extends unknown[]> = (container: Container, ...args: Args) =>
|
|
80
|
+
type QueryAll<Args extends unknown[]> = (container: Container, ...args: Args) => object[];
|
|
81
81
|
|
|
82
82
|
type BooleanAccessibleState =
|
|
83
83
|
Gtk.AccessibleState.BUSY |
|
|
@@ -195,11 +195,11 @@ type MatcherImplementations = {
|
|
|
195
195
|
/** Asserts the widget's accessible role. */
|
|
196
196
|
toHaveRole: (received: unknown, expected: Gtk.AccessibleRole) => MatcherResult;
|
|
197
197
|
/** Asserts the widget is, or is an ancestor of, the given widget. */
|
|
198
|
-
toContainElement: (received: unknown, descendant: Gtk.
|
|
198
|
+
toContainElement: (received: unknown, descendant: Gtk.Accessible | null) => MatcherResult;
|
|
199
199
|
/** Asserts the widget comes before the given widget in the widget tree, neither containing the other. */
|
|
200
|
-
toAppearBefore: (received: unknown, other: Gtk.
|
|
200
|
+
toAppearBefore: (received: unknown, other: Gtk.Accessible) => MatcherResult;
|
|
201
201
|
/** Asserts the widget comes after the given widget in the widget tree, neither containing the other. */
|
|
202
|
-
toAppearAfter: (received: unknown, other: Gtk.
|
|
202
|
+
toAppearAfter: (received: unknown, other: Gtk.Accessible) => MatcherResult;
|
|
203
203
|
/** Asserts at least one widget under the received one matches the `ByRole` query. */
|
|
204
204
|
toContainAnyByRole: ContainmentMatcher<RoleQueryArgs>;
|
|
205
205
|
/** Asserts exactly one widget under the received one matches the `ByRole` query. */
|
|
@@ -525,9 +525,6 @@ const isAppearingBefore = (first: Gtk.Widget, second: Gtk.Widget): boolean => {
|
|
|
525
525
|
return isSeparateWidget(first, second) && root === getTreeRoot(second) && isPrecedingWidget(root, first, second);
|
|
526
526
|
};
|
|
527
527
|
|
|
528
|
-
const camelCase = (name: string): string =>
|
|
529
|
-
name.replaceAll(/-([a-z])/g, (_match, letter: string) => letter.toUpperCase());
|
|
530
|
-
|
|
531
528
|
const isAsymmetricMatcher = (value: unknown): boolean =>
|
|
532
529
|
typeof value === "object" && value !== null && typeof Reflect.get(value, "asymmetricMatch") === "function";
|
|
533
530
|
|
|
@@ -588,26 +585,8 @@ function readErrorMessageText(widget: Gtk.Widget): string | null {
|
|
|
588
585
|
return texts.length > 0 ? texts.join(" ") : null;
|
|
589
586
|
}
|
|
590
587
|
|
|
591
|
-
function assertReadableProperty(object: GObject.Object, name: string, property: string, value: unknown): void {
|
|
592
|
-
if (typeof value === "function") {
|
|
593
|
-
throw new TypeError(
|
|
594
|
-
`toHaveObjectProperty: "${name}" is shadowed by a method of the same name; ` +
|
|
595
|
-
`call ${property}() and assert on its result instead`,
|
|
596
|
-
);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
if (value === undefined && !Reflect.has(object, property)) {
|
|
600
|
-
throw new TypeError(
|
|
601
|
-
`toHaveObjectProperty: no readable property "${name}" on ${describeObject(object)}; ` +
|
|
602
|
-
"construct-only and write-only properties cannot be read back",
|
|
603
|
-
);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
588
|
function readObjectProperty(object: GObject.Object, name: string): unknown {
|
|
608
|
-
const
|
|
609
|
-
const value: unknown = Reflect.get(object, property);
|
|
610
|
-
assertReadableProperty(object, name, property, value);
|
|
589
|
+
const value: unknown = Reflect.apply(GObject.getProperty, undefined, [object, name]);
|
|
611
590
|
|
|
612
591
|
return value;
|
|
613
592
|
}
|
|
@@ -804,14 +783,14 @@ function toBePartiallyPressed(received: unknown): MatcherResult {
|
|
|
804
783
|
return stateResult(widget, "partially pressed", state === MIXED_TRISTATE);
|
|
805
784
|
}
|
|
806
785
|
|
|
807
|
-
function toAppearBefore(received: unknown, other: Gtk.
|
|
786
|
+
function toAppearBefore(received: unknown, other: Gtk.Accessible): MatcherResult {
|
|
808
787
|
const widget = asWidget(received, "toAppearBefore");
|
|
809
788
|
const target = asWidget(other, "toAppearBefore");
|
|
810
789
|
|
|
811
790
|
return orderResult(widget, target, "before", isAppearingBefore(widget, target));
|
|
812
791
|
}
|
|
813
792
|
|
|
814
|
-
function toAppearAfter(received: unknown, other: Gtk.
|
|
793
|
+
function toAppearAfter(received: unknown, other: Gtk.Accessible): MatcherResult {
|
|
815
794
|
const widget = asWidget(received, "toAppearAfter");
|
|
816
795
|
const target = asWidget(other, "toAppearAfter");
|
|
817
796
|
|
|
@@ -943,15 +922,16 @@ function toHaveRole(received: unknown, expected: Gtk.AccessibleRole): MatcherRes
|
|
|
943
922
|
};
|
|
944
923
|
}
|
|
945
924
|
|
|
946
|
-
function toContainElement(received: unknown, descendant: Gtk.
|
|
925
|
+
function toContainElement(received: unknown, descendant: Gtk.Accessible | null): MatcherResult {
|
|
947
926
|
const widget = asWidget(received, "toContainElement");
|
|
948
|
-
const
|
|
927
|
+
const target = descendant === null ? null : asWidget(descendant, "toContainElement");
|
|
928
|
+
const isPass = target !== null && (target === widget || target.isAncestor(widget));
|
|
949
929
|
|
|
950
930
|
return {
|
|
951
931
|
pass: isPass,
|
|
952
932
|
message: () =>
|
|
953
933
|
`expected widget ${negationPrefix(isPass)}to contain ` +
|
|
954
|
-
`${
|
|
934
|
+
`${target === null ? "null" : describeWidget(target)}\n${describeWidget(widget)}`,
|
|
955
935
|
};
|
|
956
936
|
}
|
|
957
937
|
|
|
@@ -1020,64 +1000,62 @@ const registerMatchers = (): void => {
|
|
|
1020
1000
|
registration.isRegistered = true;
|
|
1021
1001
|
};
|
|
1022
1002
|
|
|
1023
|
-
declare module "
|
|
1024
|
-
/* eslint-disable @typescript-eslint/consistent-type-definitions --
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1003
|
+
declare module "vitest" {
|
|
1004
|
+
/* eslint-disable @typescript-eslint/consistent-type-definitions, @typescript-eslint/no-unused-vars --
|
|
1005
|
+
declaration merging requires an interface with vitest's exact type parameters */
|
|
1006
|
+
interface Matchers<R extends void | Promise<void> = void | Promise<void>, T = unknown> {
|
|
1007
|
+
toHaveDisplayValue(expected?: TextExpectation): R;
|
|
1008
|
+
toHaveTextContent(expected?: TextExpectation, options?: TextContentOptions): R;
|
|
1009
|
+
toHaveAccessibleName(expected?: TextExpectation): R;
|
|
1010
|
+
toHaveAccessibleDescription(expected?: TextExpectation): R;
|
|
1011
|
+
toHaveAccessibleErrorMessage(expected?: TextExpectation): R;
|
|
1012
|
+
toHaveSelection(expected?: TextExpectation): R;
|
|
1032
1013
|
/* eslint-disable-next-line unicorn/consistent-boolean-name -- expected is the jest-dom matcher argument name */
|
|
1033
|
-
toHaveAccessibleState(state: BooleanAccessibleState, expected?: boolean):
|
|
1034
|
-
toHaveAccessibleState(state: TristateAccessibleState, expected?: Gtk.AccessibleTristate):
|
|
1035
|
-
toHaveAccessibleState(state: Gtk.AccessibleState.INVALID, expected?: Gtk.AccessibleInvalidState):
|
|
1036
|
-
toHaveAccessibleProperty(property: StringAccessibleProperty, expected?: string):
|
|
1014
|
+
toHaveAccessibleState(state: BooleanAccessibleState, expected?: boolean): R;
|
|
1015
|
+
toHaveAccessibleState(state: TristateAccessibleState, expected?: Gtk.AccessibleTristate): R;
|
|
1016
|
+
toHaveAccessibleState(state: Gtk.AccessibleState.INVALID, expected?: Gtk.AccessibleInvalidState): R;
|
|
1017
|
+
toHaveAccessibleProperty(property: StringAccessibleProperty, expected?: string): R;
|
|
1037
1018
|
/* eslint-disable-next-line unicorn/consistent-boolean-name -- expected is the jest-dom matcher argument name */
|
|
1038
|
-
toHaveAccessibleProperty(property: BooleanAccessibleProperty, expected?: boolean):
|
|
1039
|
-
toHaveAccessibleProperty(property: NumberAccessibleProperty, expected?: number):
|
|
1019
|
+
toHaveAccessibleProperty(property: BooleanAccessibleProperty, expected?: boolean): R;
|
|
1020
|
+
toHaveAccessibleProperty(property: NumberAccessibleProperty, expected?: number): R;
|
|
1040
1021
|
toHaveAccessibleProperty(
|
|
1041
1022
|
property: Gtk.AccessibleProperty.AUTOCOMPLETE,
|
|
1042
1023
|
expected?: Gtk.AccessibleAutocomplete,
|
|
1043
|
-
):
|
|
1044
|
-
toHaveAccessibleProperty(property: Gtk.AccessibleProperty.ORIENTATION, expected?: Gtk.Orientation):
|
|
1045
|
-
toHaveAccessibleProperty(property: Gtk.AccessibleProperty.SORT, expected?: Gtk.AccessibleSort):
|
|
1046
|
-
toBeChecked():
|
|
1047
|
-
toBePartiallyChecked():
|
|
1048
|
-
toBePressed():
|
|
1049
|
-
toBePartiallyPressed():
|
|
1050
|
-
toBeDisabled():
|
|
1051
|
-
toBeEnabled():
|
|
1052
|
-
toBeVisible():
|
|
1053
|
-
toBeRooted():
|
|
1054
|
-
toBeEmptyWidget():
|
|
1055
|
-
toBeInvalid():
|
|
1056
|
-
toBeValid():
|
|
1057
|
-
toBeRequired():
|
|
1058
|
-
toHaveFocus():
|
|
1059
|
-
toHaveValue(expected?: number | string):
|
|
1060
|
-
toHaveRole(expected: Gtk.AccessibleRole):
|
|
1061
|
-
toContainElement(descendant: Gtk.
|
|
1062
|
-
toAppearBefore(other: Gtk.
|
|
1063
|
-
toAppearAfter(other: Gtk.
|
|
1064
|
-
toContainAnyByRole(...args: RoleQueryArgs):
|
|
1065
|
-
toContainOneByRole(...args: RoleQueryArgs):
|
|
1066
|
-
toContainAnyByText(...args: TextQueryArgs):
|
|
1067
|
-
toContainOneByText(...args: TextQueryArgs):
|
|
1068
|
-
toContainAnyByLabelText(...args: TextQueryArgs):
|
|
1069
|
-
toContainOneByLabelText(...args: TextQueryArgs):
|
|
1070
|
-
toContainAnyByPlaceholderText(...args: TextQueryArgs):
|
|
1071
|
-
toContainOneByPlaceholderText(...args: TextQueryArgs):
|
|
1072
|
-
toContainAnyByDisplayValue(...args: TextQueryArgs):
|
|
1073
|
-
toContainOneByDisplayValue(...args: TextQueryArgs):
|
|
1074
|
-
toHaveClass(...args: (ClassExpectation | { exact: boolean })[]):
|
|
1075
|
-
toHaveObjectProperty(name: string, expected?: unknown):
|
|
1024
|
+
): R;
|
|
1025
|
+
toHaveAccessibleProperty(property: Gtk.AccessibleProperty.ORIENTATION, expected?: Gtk.Orientation): R;
|
|
1026
|
+
toHaveAccessibleProperty(property: Gtk.AccessibleProperty.SORT, expected?: Gtk.AccessibleSort): R;
|
|
1027
|
+
toBeChecked(): R;
|
|
1028
|
+
toBePartiallyChecked(): R;
|
|
1029
|
+
toBePressed(): R;
|
|
1030
|
+
toBePartiallyPressed(): R;
|
|
1031
|
+
toBeDisabled(): R;
|
|
1032
|
+
toBeEnabled(): R;
|
|
1033
|
+
toBeVisible(): R;
|
|
1034
|
+
toBeRooted(): R;
|
|
1035
|
+
toBeEmptyWidget(): R;
|
|
1036
|
+
toBeInvalid(): R;
|
|
1037
|
+
toBeValid(): R;
|
|
1038
|
+
toBeRequired(): R;
|
|
1039
|
+
toHaveFocus(): R;
|
|
1040
|
+
toHaveValue(expected?: number | string): R;
|
|
1041
|
+
toHaveRole(expected: Gtk.AccessibleRole): R;
|
|
1042
|
+
toContainElement(descendant: Gtk.Accessible | null): R;
|
|
1043
|
+
toAppearBefore(other: Gtk.Accessible): R;
|
|
1044
|
+
toAppearAfter(other: Gtk.Accessible): R;
|
|
1045
|
+
toContainAnyByRole(...args: RoleQueryArgs): R;
|
|
1046
|
+
toContainOneByRole(...args: RoleQueryArgs): R;
|
|
1047
|
+
toContainAnyByText(...args: TextQueryArgs): R;
|
|
1048
|
+
toContainOneByText(...args: TextQueryArgs): R;
|
|
1049
|
+
toContainAnyByLabelText(...args: TextQueryArgs): R;
|
|
1050
|
+
toContainOneByLabelText(...args: TextQueryArgs): R;
|
|
1051
|
+
toContainAnyByPlaceholderText(...args: TextQueryArgs): R;
|
|
1052
|
+
toContainOneByPlaceholderText(...args: TextQueryArgs): R;
|
|
1053
|
+
toContainAnyByDisplayValue(...args: TextQueryArgs): R;
|
|
1054
|
+
toContainOneByDisplayValue(...args: TextQueryArgs): R;
|
|
1055
|
+
toHaveClass(...args: (ClassExpectation | { exact: boolean })[]): R;
|
|
1056
|
+
toHaveObjectProperty(name: string, expected?: unknown): R;
|
|
1076
1057
|
}
|
|
1077
|
-
|
|
1078
|
-
interface Assertion extends WidgetMatchers {}
|
|
1079
|
-
interface AsymmetricMatchersContaining extends WidgetMatchers {}
|
|
1080
|
-
/* eslint-enable @typescript-eslint/consistent-type-definitions */
|
|
1058
|
+
/* eslint-enable @typescript-eslint/consistent-type-definitions, @typescript-eslint/no-unused-vars */
|
|
1081
1059
|
}
|
|
1082
1060
|
|
|
1083
1061
|
export { matchers, registerMatchers, type ClassExpectation, type TextContentOptions, type TextExpectation };
|
package/src/queries.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { multipleFoundError, notFoundError } from "./errors.js";
|
|
|
12
12
|
import { getDefaultNormalizer } from "./normalize.js";
|
|
13
13
|
import { type Container, findAll, traverse } from "./traversal.js";
|
|
14
14
|
import {
|
|
15
|
+
buildInaccessibilityCheck,
|
|
15
16
|
getWidgetAccessibleName,
|
|
16
17
|
getWidgetBusyState,
|
|
17
18
|
getWidgetDescription,
|
|
@@ -26,7 +27,6 @@ import {
|
|
|
26
27
|
getWidgetPressedState,
|
|
27
28
|
getWidgetSelectedState,
|
|
28
29
|
getWidgetValueText,
|
|
29
|
-
isInaccessible,
|
|
30
30
|
isWidgetChecked,
|
|
31
31
|
isWidgetValueMatch,
|
|
32
32
|
namingLabelText,
|
|
@@ -39,7 +39,7 @@ type BuiltinQueries = QueryFamilies<[container: Container]>;
|
|
|
39
39
|
const roleQueries = nameQueryFamily(
|
|
40
40
|
"Role",
|
|
41
41
|
queryAllByRole,
|
|
42
|
-
buildQueries<[role: Gtk.AccessibleRole, options?: ByRoleOptions]>(
|
|
42
|
+
buildQueries<[role: Gtk.AccessibleRole, options?: ByRoleOptions<Gtk.Widget>], Gtk.Widget>(
|
|
43
43
|
queryAllByRole,
|
|
44
44
|
(container, matches, role, options) =>
|
|
45
45
|
multipleFoundError(container, { queryType: "role", role, options }, matches),
|
|
@@ -50,7 +50,7 @@ const roleQueries = nameQueryFamily(
|
|
|
50
50
|
const labelTextQueries = nameQueryFamily(
|
|
51
51
|
"LabelText",
|
|
52
52
|
queryAllByLabelText,
|
|
53
|
-
buildQueries<[text: Matcher, options?: MatcherOptions]>(
|
|
53
|
+
buildQueries<[text: Matcher, options?: MatcherOptions<Gtk.Widget>], Gtk.Widget>(
|
|
54
54
|
queryAllByLabelText,
|
|
55
55
|
(container, matches, text) => multipleFoundError(container, { queryType: "labelText", text }, matches),
|
|
56
56
|
(container, text) => notFoundError(container, { queryType: "labelText", text }),
|
|
@@ -60,7 +60,7 @@ const labelTextQueries = nameQueryFamily(
|
|
|
60
60
|
const textQueries = nameQueryFamily(
|
|
61
61
|
"Text",
|
|
62
62
|
queryAllByText,
|
|
63
|
-
buildQueries<[text: Matcher, options?: MatcherOptions]>(
|
|
63
|
+
buildQueries<[text: Matcher, options?: MatcherOptions<Gtk.Widget>], Gtk.Widget>(
|
|
64
64
|
queryAllByText,
|
|
65
65
|
(container, matches, text) => multipleFoundError(container, { queryType: "text", text }, matches),
|
|
66
66
|
(container, text) => notFoundError(container, { queryType: "text", text }),
|
|
@@ -70,7 +70,7 @@ const textQueries = nameQueryFamily(
|
|
|
70
70
|
const nameQueries = nameQueryFamily(
|
|
71
71
|
"Name",
|
|
72
72
|
queryAllByName,
|
|
73
|
-
buildQueries<[name: Matcher, options?: MatcherOptions]>(
|
|
73
|
+
buildQueries<[name: Matcher, options?: MatcherOptions<Gtk.Widget>], Gtk.Widget>(
|
|
74
74
|
queryAllByName,
|
|
75
75
|
(container, matches, name) => multipleFoundError(container, { queryType: "name", name }, matches),
|
|
76
76
|
(container, name) => notFoundError(container, { queryType: "name", name }),
|
|
@@ -80,7 +80,7 @@ const nameQueries = nameQueryFamily(
|
|
|
80
80
|
const placeholderTextQueries = nameQueryFamily(
|
|
81
81
|
"PlaceholderText",
|
|
82
82
|
queryAllByPlaceholderText,
|
|
83
|
-
buildQueries<[text: Matcher, options?: MatcherOptions]>(
|
|
83
|
+
buildQueries<[text: Matcher, options?: MatcherOptions<Gtk.Widget>], Gtk.Widget>(
|
|
84
84
|
queryAllByPlaceholderText,
|
|
85
85
|
(container, matches, text) => multipleFoundError(container, { queryType: "placeholderText", text }, matches),
|
|
86
86
|
(container, text) => notFoundError(container, { queryType: "placeholderText", text }),
|
|
@@ -90,7 +90,7 @@ const placeholderTextQueries = nameQueryFamily(
|
|
|
90
90
|
const displayValueQueries = nameQueryFamily(
|
|
91
91
|
"DisplayValue",
|
|
92
92
|
queryAllByDisplayValue,
|
|
93
|
-
buildQueries<[value: Matcher, options?: MatcherOptions]>(
|
|
93
|
+
buildQueries<[value: Matcher, options?: MatcherOptions<Gtk.Widget>], Gtk.Widget>(
|
|
94
94
|
queryAllByDisplayValue,
|
|
95
95
|
(container, matches, value) => multipleFoundError(container, { queryType: "displayValue", value }, matches),
|
|
96
96
|
(container, value) => notFoundError(container, { queryType: "displayValue", value }),
|
|
@@ -284,25 +284,27 @@ const hasMatchingAccessibleStates = (widget: Gtk.Widget, options: ByRoleOptions)
|
|
|
284
284
|
hasMatchingDescriptionState(widget, options) &&
|
|
285
285
|
hasMatchingValueState(widget, options);
|
|
286
286
|
|
|
287
|
-
const isMatchingWidgetType =
|
|
287
|
+
const isMatchingWidgetType = <T extends Gtk.Accessible>(
|
|
288
|
+
widget: Gtk.Widget,
|
|
289
|
+
options?: MatcherOptions<T>,
|
|
290
|
+
): widget is Gtk.Widget & T =>
|
|
288
291
|
options?.as === undefined || widget instanceof options.as;
|
|
289
292
|
|
|
290
|
-
const
|
|
293
|
+
const hasMatchingAccessibleOptions = (widget: Gtk.Widget, options?: ByRoleOptions): boolean => {
|
|
291
294
|
if (!options) {
|
|
292
295
|
return true;
|
|
293
296
|
}
|
|
294
297
|
|
|
295
298
|
return (
|
|
296
|
-
isMatchingWidgetType(widget, options) &&
|
|
297
299
|
hasMatchingAccessibleName(widget, options) &&
|
|
298
300
|
hasMatchingAccessibleStates(widget, options)
|
|
299
301
|
);
|
|
300
302
|
};
|
|
301
303
|
|
|
302
|
-
function nameQueryFamily<Args extends unknown[]>(
|
|
304
|
+
function nameQueryFamily<Args extends unknown[], Element extends Gtk.Accessible>(
|
|
303
305
|
suffix: string,
|
|
304
|
-
queryAllBy: QueryAllBy<Args>,
|
|
305
|
-
built: BuiltQueries<Args>,
|
|
306
|
+
queryAllBy: QueryAllBy<Args, Element>,
|
|
307
|
+
built: BuiltQueries<Args, Element>,
|
|
306
308
|
): Record<string, unknown> {
|
|
307
309
|
const [queryBy, getAllBy, getBy, findAllBy, findBy] = built;
|
|
308
310
|
|
|
@@ -325,22 +327,30 @@ function nameQueryFamily<Args extends unknown[]>(
|
|
|
325
327
|
* @param options Additional accessible name, state, and value constraints.
|
|
326
328
|
* @returns Every matching widget, or an empty array when none match.
|
|
327
329
|
*/
|
|
328
|
-
function queryAllByRole(
|
|
330
|
+
function queryAllByRole<T extends Gtk.Accessible = Gtk.Widget>(
|
|
329
331
|
container: Container,
|
|
330
332
|
role: Gtk.AccessibleRole,
|
|
331
|
-
options?: ByRoleOptions
|
|
332
|
-
):
|
|
333
|
-
|
|
333
|
+
options?: ByRoleOptions<T>,
|
|
334
|
+
): T[] {
|
|
335
|
+
const isInaccessible = buildInaccessibilityCheck();
|
|
336
|
+
|
|
337
|
+
const matches = findAll(container, (widget) => {
|
|
334
338
|
if (widget.getAccessibleRole() !== role) {
|
|
335
339
|
return false;
|
|
336
340
|
}
|
|
337
341
|
|
|
342
|
+
if (!isMatchingWidgetType(widget, options)) {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
|
|
338
346
|
if (!options?.hidden && isInaccessible(widget)) {
|
|
339
347
|
return false;
|
|
340
348
|
}
|
|
341
349
|
|
|
342
|
-
return
|
|
350
|
+
return hasMatchingAccessibleOptions(widget, options);
|
|
343
351
|
});
|
|
352
|
+
|
|
353
|
+
return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
|
|
344
354
|
}
|
|
345
355
|
|
|
346
356
|
const collectMnemonicMatch = (
|
|
@@ -394,14 +404,18 @@ const collectLabelMatches = (
|
|
|
394
404
|
* @param options Text matching options.
|
|
395
405
|
* @returns Every matching widget, or an empty array when none match.
|
|
396
406
|
*/
|
|
397
|
-
function queryAllByLabelText
|
|
407
|
+
function queryAllByLabelText<T extends Gtk.Accessible = Gtk.Widget>(
|
|
408
|
+
container: Container,
|
|
409
|
+
text: Matcher,
|
|
410
|
+
options?: MatcherOptions<T>,
|
|
411
|
+
): T[] {
|
|
398
412
|
const results: Set<Gtk.Widget> = new Set();
|
|
399
413
|
|
|
400
414
|
for (const widget of traverse(container)) {
|
|
401
415
|
collectLabelMatches(results, widget, text, options);
|
|
402
416
|
}
|
|
403
417
|
|
|
404
|
-
return [...results].filter((widget) => isMatchingWidgetType(widget, options));
|
|
418
|
+
return [...results].filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
|
|
405
419
|
}
|
|
406
420
|
|
|
407
421
|
/**
|
|
@@ -411,12 +425,18 @@ function queryAllByLabelText(container: Container, text: Matcher, options?: Matc
|
|
|
411
425
|
* @param options Text matching options.
|
|
412
426
|
* @returns Every matching widget, or an empty array when none match.
|
|
413
427
|
*/
|
|
414
|
-
function queryAllByText
|
|
415
|
-
|
|
428
|
+
function queryAllByText<T extends Gtk.Accessible = Gtk.Widget>(
|
|
429
|
+
container: Container,
|
|
430
|
+
text: Matcher,
|
|
431
|
+
options?: MatcherOptions<T>,
|
|
432
|
+
): T[] {
|
|
433
|
+
const matches = findAll(
|
|
416
434
|
container,
|
|
417
435
|
(widget) =>
|
|
418
436
|
isMatchingWidgetType(widget, options) && isTextMatch(getWidgetLabelText(widget), text, widget, options),
|
|
419
437
|
);
|
|
438
|
+
|
|
439
|
+
return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
|
|
420
440
|
}
|
|
421
441
|
|
|
422
442
|
/**
|
|
@@ -426,11 +446,17 @@ function queryAllByText(container: Container, text: Matcher, options?: MatcherOp
|
|
|
426
446
|
* @param options Text matching options.
|
|
427
447
|
* @returns Every matching widget, or an empty array when none match.
|
|
428
448
|
*/
|
|
429
|
-
function queryAllByName
|
|
430
|
-
|
|
449
|
+
function queryAllByName<T extends Gtk.Accessible = Gtk.Widget>(
|
|
450
|
+
container: Container,
|
|
451
|
+
name: Matcher,
|
|
452
|
+
options?: MatcherOptions<T>,
|
|
453
|
+
): T[] {
|
|
454
|
+
const matches = findAll(
|
|
431
455
|
container,
|
|
432
456
|
(widget) => isMatchingWidgetType(widget, options) && isTextMatch(getWidgetName(widget), name, widget, options),
|
|
433
457
|
);
|
|
458
|
+
|
|
459
|
+
return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
|
|
434
460
|
}
|
|
435
461
|
|
|
436
462
|
/**
|
|
@@ -440,17 +466,19 @@ function queryAllByName(container: Container, name: Matcher, options?: MatcherOp
|
|
|
440
466
|
* @param options Text matching options.
|
|
441
467
|
* @returns Every matching widget, or an empty array when none match.
|
|
442
468
|
*/
|
|
443
|
-
function queryAllByPlaceholderText(
|
|
469
|
+
function queryAllByPlaceholderText<T extends Gtk.Accessible = Gtk.Widget>(
|
|
444
470
|
container: Container,
|
|
445
471
|
text: Matcher,
|
|
446
|
-
options?: MatcherOptions
|
|
447
|
-
):
|
|
448
|
-
|
|
472
|
+
options?: MatcherOptions<T>,
|
|
473
|
+
): T[] {
|
|
474
|
+
const matches = findAll(
|
|
449
475
|
container,
|
|
450
476
|
(widget) =>
|
|
451
477
|
isMatchingWidgetType(widget, options) &&
|
|
452
478
|
isTextMatch(getWidgetPlaceholderText(widget), text, widget, options),
|
|
453
479
|
);
|
|
480
|
+
|
|
481
|
+
return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
|
|
454
482
|
}
|
|
455
483
|
|
|
456
484
|
/**
|
|
@@ -460,13 +488,19 @@ function queryAllByPlaceholderText(
|
|
|
460
488
|
* @param options Text matching options.
|
|
461
489
|
* @returns Every matching widget, or an empty array when none match.
|
|
462
490
|
*/
|
|
463
|
-
function queryAllByDisplayValue
|
|
464
|
-
|
|
491
|
+
function queryAllByDisplayValue<T extends Gtk.Accessible = Gtk.Widget>(
|
|
492
|
+
container: Container,
|
|
493
|
+
value: Matcher,
|
|
494
|
+
options?: MatcherOptions<T>,
|
|
495
|
+
): T[] {
|
|
496
|
+
const matches = findAll(
|
|
465
497
|
container,
|
|
466
498
|
(widget) =>
|
|
467
499
|
isMatchingWidgetType(widget, options) &&
|
|
468
500
|
isTextMatch(getWidgetDisplayValue(widget), value, widget, options),
|
|
469
501
|
);
|
|
502
|
+
|
|
503
|
+
return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
|
|
470
504
|
}
|
|
471
505
|
|
|
472
506
|
export {
|
package/src/query-helpers.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
-
import {
|
|
2
|
+
import { getProperty } from "@gtkx/gi/gobject";
|
|
3
3
|
import type { Matcher, MatcherOptions } from "./types.js";
|
|
4
4
|
import { buildQueries } from "./build-queries.js";
|
|
5
5
|
import { getElementError } from "./errors.js";
|
|
@@ -27,7 +27,13 @@ const queryHelpers: QueryHelpers = {
|
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
const readObjectProperty = (widget: Gtk.Widget, property: string): string | null => {
|
|
30
|
-
|
|
30
|
+
let value: unknown;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
value = Reflect.apply(getProperty, undefined, [widget, property]);
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
31
37
|
|
|
32
38
|
if (typeof value === "string") {
|
|
33
39
|
return value;
|
|
@@ -51,18 +57,20 @@ const readObjectProperty = (widget: Gtk.Widget, property: string): string | null
|
|
|
51
57
|
* @param options Text matching options.
|
|
52
58
|
* @returns Every matching widget, or an empty array when none match.
|
|
53
59
|
*/
|
|
54
|
-
function queryAllByObjectProperty(
|
|
60
|
+
function queryAllByObjectProperty<T extends Gtk.Accessible = Gtk.Widget>(
|
|
55
61
|
property: string,
|
|
56
62
|
container: Container,
|
|
57
63
|
text: Matcher,
|
|
58
|
-
options?: MatcherOptions
|
|
59
|
-
):
|
|
60
|
-
|
|
64
|
+
options?: MatcherOptions<T>,
|
|
65
|
+
): T[] {
|
|
66
|
+
const matches = findAll(
|
|
61
67
|
container,
|
|
62
68
|
(widget) =>
|
|
63
69
|
isMatchingWidgetType(widget, options) &&
|
|
64
70
|
isTextMatch(readObjectProperty(widget, property), text, widget, options),
|
|
65
71
|
);
|
|
72
|
+
|
|
73
|
+
return matches.filter((widget): widget is Gtk.Widget & T => isMatchingWidgetType(widget, options));
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
/**
|
|
@@ -76,12 +84,12 @@ function queryAllByObjectProperty(
|
|
|
76
84
|
* @returns The matching widget, or null when none match.
|
|
77
85
|
* @throws When more than one widget matches.
|
|
78
86
|
*/
|
|
79
|
-
function queryByObjectProperty(
|
|
87
|
+
function queryByObjectProperty<T extends Gtk.Accessible = Gtk.Widget>(
|
|
80
88
|
property: string,
|
|
81
89
|
container: Container,
|
|
82
90
|
text: Matcher,
|
|
83
|
-
options?: MatcherOptions
|
|
84
|
-
):
|
|
91
|
+
options?: MatcherOptions<T>,
|
|
92
|
+
): T | null {
|
|
85
93
|
const matches = queryAllByObjectProperty(property, container, text, options);
|
|
86
94
|
|
|
87
95
|
if (matches.length > 1) {
|
package/src/render.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RootElement } from "@gtkx/react";
|
|
1
|
+
import type { CaughtErrorInfo, RootElement } from "@gtkx/react";
|
|
2
2
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
3
3
|
import {
|
|
4
4
|
createReconcilerRoot,
|
|
@@ -21,6 +21,7 @@ import { clearScreen, setScreen } from "./screen.js";
|
|
|
21
21
|
import { captureScreen } from "./screenshot.js";
|
|
22
22
|
import { type Container, roots, TOPLEVELS } from "./traversal.js";
|
|
23
23
|
import { resetClipboard } from "./user-event/index.js";
|
|
24
|
+
import { requireWidget } from "./widget-target.js";
|
|
24
25
|
import { findPresentedWindowFailure, findRenderedWindowFailure, mappedToplevels } from "./window-state.js";
|
|
25
26
|
import { within } from "./within.js";
|
|
26
27
|
|
|
@@ -125,6 +126,10 @@ const resolveContainer = (container: RenderOptions["container"]): ResolvedContai
|
|
|
125
126
|
return { containerInfo: container, window: null };
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
if (container !== undefined) {
|
|
130
|
+
requireWidget(container);
|
|
131
|
+
}
|
|
132
|
+
|
|
128
133
|
const window = createHarnessWindow();
|
|
129
134
|
window.setDecorated(false);
|
|
130
135
|
|
|
@@ -188,7 +193,7 @@ const settleRender = async (
|
|
|
188
193
|
|
|
189
194
|
const renderErrorHandlers = <Q extends QueryMap>(options: RenderOptions<Q> | undefined) => ({
|
|
190
195
|
onUncaughtError: handleError,
|
|
191
|
-
onCaughtError: (error: unknown, errorInfo:
|
|
196
|
+
onCaughtError: (error: unknown, errorInfo: CaughtErrorInfo): void => {
|
|
192
197
|
handleError(error);
|
|
193
198
|
options?.onCaughtError?.(error, errorInfo);
|
|
194
199
|
},
|