@gtkx/testing 1.0.0-rc.1 → 1.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/act.d.ts +6 -6
- package/dist/act.d.ts.map +1 -1
- package/dist/act.js +34 -26
- package/dist/act.js.map +1 -1
- package/dist/bound-queries.d.ts +4 -3
- package/dist/bound-queries.d.ts.map +1 -1
- package/dist/bound-queries.js.map +1 -1
- package/dist/build-queries.d.ts +5 -5
- package/dist/build-queries.d.ts.map +1 -1
- package/dist/build-queries.js +21 -9
- package/dist/build-queries.js.map +1 -1
- package/dist/cleanup-registry.d.ts +4 -3
- package/dist/cleanup-registry.d.ts.map +1 -1
- package/dist/cleanup-registry.js +13 -3
- package/dist/cleanup-registry.js.map +1 -1
- package/dist/config.d.ts +5 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -9
- package/dist/config.js.map +1 -1
- package/dist/editable.d.ts +6 -5
- package/dist/editable.d.ts.map +1 -1
- package/dist/editable.js +7 -5
- package/dist/editable.js.map +1 -1
- package/dist/errors.d.ts +7 -6
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +64 -48
- package/dist/errors.js.map +1 -1
- package/dist/fire-event.d.ts +2 -1
- package/dist/fire-event.d.ts.map +1 -1
- package/dist/fire-event.js +2 -1
- package/dist/fire-event.js.map +1 -1
- package/dist/frame-sync.d.ts +2 -1
- package/dist/frame-sync.d.ts.map +1 -1
- package/dist/frame-sync.js +28 -21
- package/dist/frame-sync.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/matchers.d.ts +38 -34
- package/dist/matchers.d.ts.map +1 -1
- package/dist/matchers.js +72 -58
- package/dist/matchers.js.map +1 -1
- package/dist/pretty-widget.d.ts +6 -4
- package/dist/pretty-widget.d.ts.map +1 -1
- package/dist/pretty-widget.js +64 -24
- package/dist/pretty-widget.js.map +1 -1
- package/dist/queries.d.ts +143 -86
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +231 -147
- package/dist/queries.js.map +1 -1
- package/dist/register-test-hooks.js +2 -1
- package/dist/register-test-hooks.js.map +1 -1
- package/dist/render-hook.d.ts +3 -2
- package/dist/render-hook.d.ts.map +1 -1
- package/dist/render-hook.js +2 -1
- package/dist/render-hook.js.map +1 -1
- package/dist/render.d.ts +3 -2
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +24 -18
- package/dist/render.js.map +1 -1
- package/dist/role-helpers.d.ts +6 -5
- package/dist/role-helpers.d.ts.map +1 -1
- package/dist/role-helpers.js +16 -12
- package/dist/role-helpers.js.map +1 -1
- package/dist/screen.d.ts +4 -3
- package/dist/screen.d.ts.map +1 -1
- package/dist/screen.js +13 -11
- package/dist/screen.js.map +1 -1
- package/dist/screenshot.d.ts +4 -3
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +43 -36
- package/dist/screenshot.js.map +1 -1
- package/dist/suggestions.d.ts +11 -6
- package/dist/suggestions.d.ts.map +1 -1
- package/dist/suggestions.js +10 -5
- package/dist/suggestions.js.map +1 -1
- package/dist/traversal.d.ts +7 -7
- package/dist/traversal.d.ts.map +1 -1
- package/dist/traversal.js +17 -11
- package/dist/traversal.js.map +1 -1
- package/dist/types.d.ts +39 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/user-event/adjustment.d.ts +5 -3
- package/dist/user-event/adjustment.d.ts.map +1 -1
- package/dist/user-event/adjustment.js +6 -4
- package/dist/user-event/adjustment.js.map +1 -1
- package/dist/user-event/click.d.ts +6 -5
- package/dist/user-event/click.d.ts.map +1 -1
- package/dist/user-event/click.js +29 -21
- package/dist/user-event/click.js.map +1 -1
- package/dist/user-event/controller.d.ts +6 -5
- package/dist/user-event/controller.d.ts.map +1 -1
- package/dist/user-event/controller.js +9 -6
- package/dist/user-event/controller.js.map +1 -1
- package/dist/user-event/dispatch.d.ts +3 -3
- package/dist/user-event/dispatch.d.ts.map +1 -1
- package/dist/user-event/dispatch.js +3 -2
- package/dist/user-event/dispatch.js.map +1 -1
- package/dist/user-event/event-wrapper.d.ts +2 -1
- package/dist/user-event/event-wrapper.d.ts.map +1 -1
- package/dist/user-event/event-wrapper.js +27 -16
- package/dist/user-event/event-wrapper.js.map +1 -1
- package/dist/user-event/gesture.d.ts +18 -14
- package/dist/user-event/gesture.d.ts.map +1 -1
- package/dist/user-event/gesture.js +70 -37
- package/dist/user-event/gesture.js.map +1 -1
- package/dist/user-event/index.d.ts +17 -10
- package/dist/user-event/index.d.ts.map +1 -1
- package/dist/user-event/index.js +7 -3
- package/dist/user-event/index.js.map +1 -1
- package/dist/user-event/keyboard.d.ts +4 -3
- package/dist/user-event/keyboard.d.ts.map +1 -1
- package/dist/user-event/keyboard.js +89 -58
- package/dist/user-event/keyboard.js.map +1 -1
- package/dist/user-event/pointer.d.ts +7 -3
- package/dist/user-event/pointer.d.ts.map +1 -1
- package/dist/user-event/pointer.js +4 -1
- package/dist/user-event/pointer.js.map +1 -1
- package/dist/user-event/selection.d.ts +3 -2
- package/dist/user-event/selection.d.ts.map +1 -1
- package/dist/user-event/selection.js +26 -27
- package/dist/user-event/selection.js.map +1 -1
- package/dist/user-event/state.d.ts +3 -2
- package/dist/user-event/state.d.ts.map +1 -1
- package/dist/user-event/state.js +2 -1
- package/dist/user-event/state.js.map +1 -1
- package/dist/user-event/text.d.ts +8 -7
- package/dist/user-event/text.d.ts.map +1 -1
- package/dist/user-event/text.js +39 -33
- package/dist/user-event/text.js.map +1 -1
- package/dist/wait-for.d.ts +5 -5
- package/dist/wait-for.d.ts.map +1 -1
- package/dist/wait-for.js +45 -30
- package/dist/wait-for.js.map +1 -1
- package/dist/widget-accessible-properties.d.ts +26 -25
- package/dist/widget-accessible-properties.d.ts.map +1 -1
- package/dist/widget-accessible-properties.js +106 -61
- package/dist/widget-accessible-properties.js.map +1 -1
- package/dist/within.d.ts +2 -1
- package/dist/within.d.ts.map +1 -1
- package/dist/within.js +2 -1
- package/dist/within.js.map +1 -1
- package/package.json +11 -9
- package/src/act.ts +44 -35
- package/src/bound-queries.ts +5 -4
- package/src/build-queries.ts +84 -43
- package/src/cleanup-registry.ts +18 -5
- package/src/config.ts +16 -12
- package/src/editable.ts +13 -6
- package/src/errors.ts +106 -45
- package/src/fire-event.ts +3 -1
- package/src/frame-sync.ts +38 -21
- package/src/index.ts +2 -1
- package/src/matchers.ts +158 -107
- package/src/pretty-widget.ts +114 -42
- package/src/queries.ts +422 -233
- package/src/register-test-hooks.ts +4 -1
- package/src/render-hook.tsx +10 -4
- package/src/render.tsx +58 -33
- package/src/role-helpers.ts +29 -16
- package/src/screen.ts +17 -12
- package/src/screenshot.ts +61 -38
- package/src/suggestions.ts +29 -10
- package/src/traversal.ts +35 -15
- package/src/types.ts +65 -32
- package/src/user-event/adjustment.ts +23 -10
- package/src/user-event/click.ts +49 -20
- package/src/user-event/controller.ts +21 -7
- package/src/user-event/dispatch.ts +4 -2
- package/src/user-event/event-wrapper.ts +42 -16
- package/src/user-event/gesture.ts +135 -50
- package/src/user-event/index.ts +19 -12
- package/src/user-event/keyboard.ts +147 -61
- package/src/user-event/pointer.ts +20 -8
- package/src/user-event/selection.ts +69 -32
- package/src/user-event/state.ts +4 -2
- package/src/user-event/text.ts +73 -38
- package/src/wait-for.ts +77 -36
- package/src/widget-accessible-properties.ts +218 -74
- package/src/within.ts +7 -4
package/src/frame-sync.ts
CHANGED
|
@@ -1,34 +1,51 @@
|
|
|
1
1
|
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
|
+
import * as GLib from "@gtkx/gi/glib";
|
|
2
3
|
|
|
3
4
|
const CLOCK_STALL_FALLBACK_MS = 500;
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
let
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
removeTick?.();
|
|
15
|
-
removeTick = null;
|
|
16
|
-
fallback = null;
|
|
6
|
+
const once = (callback: () => void): (() => void) => {
|
|
7
|
+
let isCalled = false;
|
|
8
|
+
|
|
9
|
+
return () => {
|
|
10
|
+
if (isCalled) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
isCalled = true;
|
|
17
15
|
callback();
|
|
18
16
|
};
|
|
17
|
+
};
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
const runWhenSized = (widget: Gtk.Widget, finish: () => void): void => {
|
|
20
|
+
let tickId = 0;
|
|
21
|
+
|
|
22
|
+
const fallback = setTimeout(() => {
|
|
23
|
+
widget.removeTickCallback(tickId);
|
|
24
|
+
finish();
|
|
25
|
+
}, CLOCK_STALL_FALLBACK_MS);
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
tickId = widget.addTickCallback(() => {
|
|
28
|
+
if (widget.getWidth() === 0) {
|
|
29
|
+
return GLib.SOURCE_CONTINUE;
|
|
30
|
+
}
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
if (widget.getWidth() === 0) return true;
|
|
32
|
+
clearTimeout(fallback);
|
|
29
33
|
finish();
|
|
30
|
-
|
|
34
|
+
|
|
35
|
+
return GLib.SOURCE_REMOVE;
|
|
31
36
|
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const scheduleAfterLayout = (widget: Gtk.Widget | null, callback: () => void): void => {
|
|
40
|
+
const finish = once(callback);
|
|
41
|
+
|
|
42
|
+
if (widget?.getFrameClock() == null || widget.getWidth() > 0) {
|
|
43
|
+
queueMicrotask(finish);
|
|
32
44
|
|
|
33
|
-
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
runWhenSized(widget, finish);
|
|
34
49
|
};
|
|
50
|
+
|
|
51
|
+
export { scheduleAfterLayout };
|
package/src/index.ts
CHANGED
|
@@ -48,8 +48,8 @@ export {
|
|
|
48
48
|
queryByRole,
|
|
49
49
|
queryByText,
|
|
50
50
|
} from "./queries.js";
|
|
51
|
-
export { cleanup, render } from "./render.js";
|
|
52
51
|
export { renderHook } from "./render-hook.js";
|
|
52
|
+
export { cleanup, render } from "./render.js";
|
|
53
53
|
export { formatRole, getRoles, logRoles, prettyRoles } from "./role-helpers.js";
|
|
54
54
|
export { screen } from "./screen.js";
|
|
55
55
|
export { captureAndSaveScreenshot, logScreenshotPath, screenshot } from "./screenshot.js";
|
|
@@ -79,6 +79,7 @@ export type {
|
|
|
79
79
|
DropContent,
|
|
80
80
|
DropOptions,
|
|
81
81
|
PointerInput,
|
|
82
|
+
ScrollDelta,
|
|
82
83
|
TabOptions,
|
|
83
84
|
TypeOptions,
|
|
84
85
|
UserEvent,
|
package/src/matchers.ts
CHANGED
|
@@ -13,188 +13,239 @@ import {
|
|
|
13
13
|
} from "./widget-accessible-properties.js";
|
|
14
14
|
|
|
15
15
|
/** The expected value for a text matcher: an exact string or a regular expression. */
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
type TextExpectation = string | RegExp;
|
|
18
17
|
type MatcherResult = { pass: boolean; message: () => string };
|
|
18
|
+
type TextMatcher = (received: unknown, expected?: TextExpectation) => MatcherResult;
|
|
19
|
+
type StateMatcher = (received: unknown) => MatcherResult;
|
|
20
|
+
type ValueMatcher = (received: unknown, expected: number) => MatcherResult;
|
|
21
|
+
type TextMatcherContext = { matcherName: string; widget: Gtk.Widget; actual: string | null };
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
type MatcherImplementations = {
|
|
24
|
+
toHaveDisplayValue: TextMatcher;
|
|
25
|
+
toHaveTextContent: TextMatcher;
|
|
26
|
+
toHaveAccessibleName: TextMatcher;
|
|
27
|
+
toHavePlaceholderText: TextMatcher;
|
|
28
|
+
toBeChecked: StateMatcher;
|
|
29
|
+
toBePressed: StateMatcher;
|
|
30
|
+
toBeExpanded: StateMatcher;
|
|
31
|
+
toBeSelected: StateMatcher;
|
|
32
|
+
toHaveValue: ValueMatcher;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type ExpectExtend = { extend: (m: MatcherImplementations) => void };
|
|
36
|
+
|
|
37
|
+
const toHaveDisplayValue: TextMatcher = textMatcher("toHaveDisplayValue", getWidgetDisplayValue, "exact");
|
|
38
|
+
|
|
39
|
+
const toHaveTextContent: TextMatcher = textMatcher(
|
|
40
|
+
"toHaveTextContent",
|
|
41
|
+
(widget) => getWidgetNodeText(widget) ?? getWidgetAccessibleName(widget),
|
|
42
|
+
"substring",
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const toHaveAccessibleName: TextMatcher = textMatcher("toHaveAccessibleName", getWidgetAccessibleName, "exact");
|
|
46
|
+
|
|
47
|
+
const toHavePlaceholderText: TextMatcher = textMatcher(
|
|
48
|
+
"toHavePlaceholderText",
|
|
49
|
+
getWidgetPlaceholderText,
|
|
50
|
+
"exact",
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const toBeChecked: StateMatcher = booleanStateMatcher("toBeChecked", "checked", getWidgetCheckedState);
|
|
54
|
+
const toBePressed: StateMatcher = booleanStateMatcher("toBePressed", "pressed", getWidgetPressedState);
|
|
55
|
+
const toBeExpanded: StateMatcher = booleanStateMatcher("toBeExpanded", "expanded", getWidgetExpandedState);
|
|
56
|
+
const toBeSelected: StateMatcher = booleanStateMatcher("toBeSelected", "selected", getWidgetSelectedState);
|
|
57
|
+
|
|
58
|
+
/** The widget assertion matchers keyed by name, suitable for passing to `expect.extend`. */
|
|
59
|
+
const matchers: MatcherImplementations = {
|
|
60
|
+
toHaveDisplayValue,
|
|
61
|
+
toHaveTextContent,
|
|
62
|
+
toHaveAccessibleName,
|
|
63
|
+
toHavePlaceholderText,
|
|
64
|
+
toBeChecked,
|
|
65
|
+
toBePressed,
|
|
66
|
+
toBeExpanded,
|
|
67
|
+
toBeSelected,
|
|
68
|
+
toHaveValue,
|
|
69
|
+
};
|
|
23
70
|
|
|
24
|
-
|
|
25
|
-
type StateMatcher = (this: MatcherContext, received: unknown) => MatcherResult;
|
|
26
|
-
type ValueMatcher = (this: MatcherContext, received: unknown, expected: number) => MatcherResult;
|
|
71
|
+
const registration = { isRegistered: false };
|
|
27
72
|
|
|
28
73
|
const asWidget = (received: unknown, matcherName: string): Gtk.Widget => {
|
|
29
74
|
if (!(received instanceof Gtk.Widget)) {
|
|
30
75
|
throw new TypeError(`${matcherName}: received value must be a Gtk.Widget, got ${typeof received}`);
|
|
31
76
|
}
|
|
77
|
+
|
|
32
78
|
return received;
|
|
33
79
|
};
|
|
34
80
|
|
|
35
81
|
const describeWidget = (widget: Gtk.Widget): string => {
|
|
36
82
|
const role = Gtk.AccessibleRole[widget.getAccessibleRole()];
|
|
37
83
|
const name = getWidgetAccessibleName(widget);
|
|
38
|
-
|
|
84
|
+
|
|
85
|
+
return name === null ? `<${role}>` : `<${role} name=${JSON.stringify(name)}>`;
|
|
39
86
|
};
|
|
40
87
|
|
|
41
|
-
const
|
|
88
|
+
const isTextMatch = (actual: string, expected: TextExpectation, mode: "exact" | "substring"): boolean => {
|
|
42
89
|
if (expected instanceof RegExp) {
|
|
43
90
|
expected.lastIndex = 0;
|
|
91
|
+
|
|
44
92
|
return expected.test(actual);
|
|
45
93
|
}
|
|
94
|
+
|
|
46
95
|
return mode === "exact" ? actual === expected : actual.includes(expected);
|
|
47
96
|
};
|
|
48
97
|
|
|
49
98
|
const describeExpected = (expected: TextExpectation): string =>
|
|
50
99
|
expected instanceof RegExp ? String(expected) : JSON.stringify(expected);
|
|
51
100
|
|
|
52
|
-
const
|
|
101
|
+
const negationPrefix = (isPass: boolean): string => (isPass ? "not " : "");
|
|
102
|
+
|
|
103
|
+
const nonEmptyResult = ({ matcherName, widget, actual }: TextMatcherContext): MatcherResult => {
|
|
104
|
+
const isPass = actual !== null && actual !== "";
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
pass: isPass,
|
|
108
|
+
message: () =>
|
|
109
|
+
`expected widget ${negationPrefix(isPass)}to have a non-empty value for ${matcherName}, ` +
|
|
110
|
+
`but got ${JSON.stringify(actual)}\n${describeWidget(widget)}`,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const matchedResult = (context: TextMatcherContext, expected: TextExpectation, isPass: boolean): MatcherResult => ({
|
|
115
|
+
pass: isPass,
|
|
116
|
+
message: () =>
|
|
117
|
+
`expected widget ${negationPrefix(isPass)}${context.matcherName} ${describeExpected(expected)}, ` +
|
|
118
|
+
`but received ${JSON.stringify(context.actual)}\n${describeWidget(context.widget)}`,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
function textMatcher(
|
|
53
122
|
matcherName: string,
|
|
54
123
|
read: (widget: Gtk.Widget) => string | null,
|
|
55
124
|
mode: "exact" | "substring",
|
|
56
|
-
): TextMatcher
|
|
57
|
-
|
|
125
|
+
): TextMatcher {
|
|
126
|
+
return (received: unknown, expected?: TextExpectation): MatcherResult => {
|
|
58
127
|
const widget = asWidget(received, matcherName);
|
|
59
128
|
const actual = read(widget);
|
|
129
|
+
const context: TextMatcherContext = { matcherName, widget, actual };
|
|
60
130
|
|
|
61
131
|
if (expected === undefined) {
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
pass,
|
|
65
|
-
message: () =>
|
|
66
|
-
`expected widget ${this.isNot ? "not " : ""}to have a non-empty value for ${matcherName}, ` +
|
|
67
|
-
`but got ${JSON.stringify(actual)}\n${describeWidget(widget)}`,
|
|
68
|
-
};
|
|
132
|
+
return nonEmptyResult(context);
|
|
69
133
|
}
|
|
70
134
|
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
pass,
|
|
74
|
-
message: () =>
|
|
75
|
-
`expected widget ${this.isNot ? "not " : ""}${matcherName} ${describeExpected(expected)}, ` +
|
|
76
|
-
`but received ${JSON.stringify(actual)}\n${describeWidget(widget)}`,
|
|
77
|
-
};
|
|
135
|
+
return matchedResult(context, expected, actual !== null && isTextMatch(actual, expected, mode));
|
|
78
136
|
};
|
|
137
|
+
}
|
|
79
138
|
|
|
80
|
-
|
|
139
|
+
function booleanStateMatcher(
|
|
81
140
|
matcherName: string,
|
|
82
141
|
stateName: string,
|
|
83
142
|
read: (widget: Gtk.Widget) => boolean | null,
|
|
84
|
-
): StateMatcher
|
|
85
|
-
|
|
143
|
+
): StateMatcher {
|
|
144
|
+
return (received: unknown): MatcherResult => {
|
|
86
145
|
const widget = asWidget(received, matcherName);
|
|
87
146
|
const state = read(widget);
|
|
147
|
+
|
|
88
148
|
if (state === null) {
|
|
89
149
|
throw new Error(
|
|
90
150
|
`${matcherName}: widget does not expose a ${stateName} state ` +
|
|
91
|
-
|
|
151
|
+
`(role ${Gtk.AccessibleRole[widget.getAccessibleRole()]})\n${describeWidget(widget)}`,
|
|
92
152
|
);
|
|
93
153
|
}
|
|
154
|
+
|
|
94
155
|
return {
|
|
95
156
|
pass: state,
|
|
96
|
-
message: () =>
|
|
157
|
+
message: () =>
|
|
158
|
+
`expected widget ${negationPrefix(state)}to be ${stateName}\n${describeWidget(widget)}`,
|
|
97
159
|
};
|
|
98
160
|
};
|
|
161
|
+
}
|
|
99
162
|
|
|
100
|
-
|
|
101
|
-
export const toHaveTextContent: TextMatcher = textMatcher(
|
|
102
|
-
"toHaveTextContent",
|
|
103
|
-
(widget) => getWidgetNodeText(widget) ?? getWidgetAccessibleName(widget),
|
|
104
|
-
"substring",
|
|
105
|
-
);
|
|
106
|
-
export const toHaveAccessibleName: TextMatcher = textMatcher("toHaveAccessibleName", getWidgetAccessibleName, "exact");
|
|
107
|
-
export const toHavePlaceholderText: TextMatcher = textMatcher(
|
|
108
|
-
"toHavePlaceholderText",
|
|
109
|
-
getWidgetPlaceholderText,
|
|
110
|
-
"exact",
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
export const toBeChecked: StateMatcher = booleanStateMatcher("toBeChecked", "checked", getWidgetCheckedState);
|
|
114
|
-
export const toBePressed: StateMatcher = booleanStateMatcher("toBePressed", "pressed", getWidgetPressedState);
|
|
115
|
-
export const toBeExpanded: StateMatcher = booleanStateMatcher("toBeExpanded", "expanded", getWidgetExpandedState);
|
|
116
|
-
export const toBeSelected: StateMatcher = booleanStateMatcher("toBeSelected", "selected", getWidgetSelectedState);
|
|
117
|
-
|
|
118
|
-
export const toHaveValue: ValueMatcher = function (
|
|
119
|
-
this: MatcherContext,
|
|
120
|
-
received: unknown,
|
|
121
|
-
expected: number,
|
|
122
|
-
): MatcherResult {
|
|
163
|
+
function toHaveValue(received: unknown, expected: number): MatcherResult {
|
|
123
164
|
const widget = asWidget(received, "toHaveValue");
|
|
124
165
|
const actual = getWidgetValue(widget).now;
|
|
166
|
+
|
|
125
167
|
if (actual === null) {
|
|
126
168
|
throw new Error(
|
|
127
|
-
|
|
128
|
-
|
|
169
|
+
"toHaveValue: widget does not expose a numeric value " +
|
|
170
|
+
`(role ${Gtk.AccessibleRole[widget.getAccessibleRole()]})\n${describeWidget(widget)}`,
|
|
129
171
|
);
|
|
130
172
|
}
|
|
173
|
+
|
|
174
|
+
const isPass = actual === expected;
|
|
175
|
+
|
|
131
176
|
return {
|
|
132
|
-
pass:
|
|
177
|
+
pass: isPass,
|
|
133
178
|
message: () =>
|
|
134
|
-
`expected widget ${
|
|
179
|
+
`expected widget ${negationPrefix(isPass)}to have value ${String(expected)}, ` +
|
|
180
|
+
`but received ${String(actual)}\n${describeWidget(widget)}`,
|
|
135
181
|
};
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
interface MatcherImplementations {
|
|
139
|
-
toHaveDisplayValue: TextMatcher;
|
|
140
|
-
toHaveTextContent: TextMatcher;
|
|
141
|
-
toHaveAccessibleName: TextMatcher;
|
|
142
|
-
toHavePlaceholderText: TextMatcher;
|
|
143
|
-
toBeChecked: StateMatcher;
|
|
144
|
-
toBePressed: StateMatcher;
|
|
145
|
-
toBeExpanded: StateMatcher;
|
|
146
|
-
toBeSelected: StateMatcher;
|
|
147
|
-
toHaveValue: ValueMatcher;
|
|
148
182
|
}
|
|
149
183
|
|
|
150
|
-
/** The widget assertion matchers keyed by name, suitable for passing to `expect.extend`. */
|
|
151
|
-
export const matchers: MatcherImplementations = {
|
|
152
|
-
toHaveDisplayValue,
|
|
153
|
-
toHaveTextContent,
|
|
154
|
-
toHaveAccessibleName,
|
|
155
|
-
toHavePlaceholderText,
|
|
156
|
-
toBeChecked,
|
|
157
|
-
toBePressed,
|
|
158
|
-
toBeExpanded,
|
|
159
|
-
toBeSelected,
|
|
160
|
-
toHaveValue,
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
type ExpectExtend = { extend: (m: MatcherImplementations) => void };
|
|
164
|
-
|
|
165
184
|
const globalExpect = (): ExpectExtend | null => {
|
|
166
185
|
const candidate: unknown = Reflect.get(globalThis, "expect");
|
|
186
|
+
|
|
167
187
|
if (candidate && typeof (candidate as ExpectExtend).extend === "function") {
|
|
168
188
|
return candidate as ExpectExtend;
|
|
169
189
|
}
|
|
190
|
+
|
|
170
191
|
return null;
|
|
171
192
|
};
|
|
172
193
|
|
|
173
|
-
let registered = false;
|
|
174
|
-
|
|
175
194
|
/** Registers the widget matchers on the global `expect`, when one is available. Safe to call more than once. */
|
|
176
|
-
|
|
177
|
-
if (
|
|
195
|
+
const registerMatchers = (): void => {
|
|
196
|
+
if (registration.isRegistered) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
178
200
|
const expect = globalExpect();
|
|
179
|
-
|
|
201
|
+
|
|
202
|
+
if (!expect) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
180
206
|
expect.extend(matchers);
|
|
181
|
-
|
|
207
|
+
registration.isRegistered = true;
|
|
182
208
|
};
|
|
183
209
|
|
|
184
|
-
interface WidgetMatchers<R = void> {
|
|
185
|
-
toHaveDisplayValue(expected?: TextExpectation): R;
|
|
186
|
-
toHaveTextContent(expected?: TextExpectation): R;
|
|
187
|
-
toHaveAccessibleName(expected?: TextExpectation): R;
|
|
188
|
-
toHavePlaceholderText(expected?: TextExpectation): R;
|
|
189
|
-
toBeChecked(): R;
|
|
190
|
-
toBePressed(): R;
|
|
191
|
-
toBeExpanded(): R;
|
|
192
|
-
toBeSelected(): R;
|
|
193
|
-
toHaveValue(expected: number): R;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
210
|
declare module "@vitest/expect" {
|
|
197
|
-
|
|
198
|
-
interface Assertion
|
|
199
|
-
|
|
211
|
+
/* eslint-disable @typescript-eslint/consistent-type-definitions -- declaration merging requires interfaces */
|
|
212
|
+
interface Assertion {
|
|
213
|
+
toHaveDisplayValue(expected?: TextExpectation): void;
|
|
214
|
+
toHaveTextContent(expected?: TextExpectation): void;
|
|
215
|
+
toHaveAccessibleName(expected?: TextExpectation): void;
|
|
216
|
+
toHavePlaceholderText(expected?: TextExpectation): void;
|
|
217
|
+
toBeChecked(): void;
|
|
218
|
+
toBePressed(): void;
|
|
219
|
+
toBeExpanded(): void;
|
|
220
|
+
toBeSelected(): void;
|
|
221
|
+
toHaveValue(expected: number): void;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
interface AsymmetricMatchersContaining {
|
|
225
|
+
toHaveDisplayValue(expected?: TextExpectation): void;
|
|
226
|
+
toHaveTextContent(expected?: TextExpectation): void;
|
|
227
|
+
toHaveAccessibleName(expected?: TextExpectation): void;
|
|
228
|
+
toHavePlaceholderText(expected?: TextExpectation): void;
|
|
229
|
+
toBeChecked(): void;
|
|
230
|
+
toBePressed(): void;
|
|
231
|
+
toBeExpanded(): void;
|
|
232
|
+
toBeSelected(): void;
|
|
233
|
+
toHaveValue(expected: number): void;
|
|
234
|
+
}
|
|
235
|
+
/* eslint-enable @typescript-eslint/consistent-type-definitions */
|
|
200
236
|
}
|
|
237
|
+
|
|
238
|
+
export {
|
|
239
|
+
toHaveDisplayValue,
|
|
240
|
+
toHaveTextContent,
|
|
241
|
+
toHaveAccessibleName,
|
|
242
|
+
toHavePlaceholderText,
|
|
243
|
+
toBeChecked,
|
|
244
|
+
toBePressed,
|
|
245
|
+
toBeExpanded,
|
|
246
|
+
toBeSelected,
|
|
247
|
+
matchers,
|
|
248
|
+
toHaveValue,
|
|
249
|
+
registerMatchers,
|
|
250
|
+
type TextExpectation,
|
|
251
|
+
};
|