@gtkx/testing 0.15.0 → 0.17.1
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 +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/pretty-widget.js +2 -1
- package/dist/queries.js +3 -2
- package/dist/render-hook.d.ts +40 -0
- package/dist/render-hook.js +64 -0
- package/dist/render.js +2 -3
- package/dist/screen.d.ts +1 -1
- package/dist/screen.js +2 -2
- package/dist/traversal.d.ts +1 -1
- package/dist/traversal.js +4 -3
- package/dist/types.d.ts +33 -0
- package/dist/user-event.d.ts +2 -2
- package/dist/user-event.js +27 -16
- package/dist/wait-for.js +5 -3
- package/dist/widget-text.js +2 -2
- package/dist/widget.d.ts +1 -1
- package/dist/widget.js +3 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -71,6 +71,7 @@ render(<App />, "com.example.counter");
|
|
|
71
71
|
- **React 19** — Hooks, concurrent features, and the component model you know
|
|
72
72
|
- **Native GTK4 widgets** — Real native controls, not web components in a webview
|
|
73
73
|
- **Adwaita support** — Modern GNOME styling with Libadwaita components
|
|
74
|
+
- **Declarative animations** — Framer Motion-like API using native Adwaita animations
|
|
74
75
|
- **Hot Module Replacement** — Fast refresh during development
|
|
75
76
|
- **TypeScript first** — Full type safety with auto-generated bindings
|
|
76
77
|
- **CSS-in-JS styling** — Familiar styling patterns adapted for GTK
|
package/dist/config.d.ts
CHANGED
|
@@ -13,6 +13,11 @@ export type Config = {
|
|
|
13
13
|
* Allows customizing how errors are constructed.
|
|
14
14
|
*/
|
|
15
15
|
getElementError: (message: string, container: Container) => Error;
|
|
16
|
+
/**
|
|
17
|
+
* Default timeout in milliseconds for async utilities (waitFor, findBy* queries).
|
|
18
|
+
* @default 1000
|
|
19
|
+
*/
|
|
20
|
+
asyncUtilTimeout: number;
|
|
16
21
|
};
|
|
17
22
|
/**
|
|
18
23
|
* Returns the current testing library configuration.
|
package/dist/config.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -5,13 +5,14 @@ export type { PrettyWidgetOptions } from "./pretty-widget.js";
|
|
|
5
5
|
export { prettyWidget } from "./pretty-widget.js";
|
|
6
6
|
export { findAllByLabelText, findAllByRole, findAllByTestId, findAllByText, findByLabelText, findByRole, findByTestId, findByText, queryAllByLabelText, queryAllByRole, queryAllByTestId, queryAllByText, queryByLabelText, queryByRole, queryByTestId, queryByText, } from "./queries.js";
|
|
7
7
|
export { cleanup, render } from "./render.js";
|
|
8
|
+
export { renderHook } from "./render-hook.js";
|
|
8
9
|
export type { RoleInfo } from "./role-helpers.js";
|
|
9
10
|
export { getRoles, logRoles, prettyRoles } from "./role-helpers.js";
|
|
10
11
|
export { screen } from "./screen.js";
|
|
11
12
|
export type { ScreenshotOptions } from "./screenshot.js";
|
|
12
13
|
export { screenshot } from "./screenshot.js";
|
|
13
14
|
export { tick } from "./timing.js";
|
|
14
|
-
export type { BoundQueries, ByRoleOptions, NormalizerOptions, RenderOptions, RenderResult, ScreenshotResult, TextMatch, TextMatchFunction, TextMatchOptions, WaitForOptions, } from "./types.js";
|
|
15
|
+
export type { BoundQueries, ByRoleOptions, NormalizerOptions, RenderHookOptions, RenderHookResult, RenderOptions, RenderResult, ScreenshotResult, TextMatch, TextMatchFunction, TextMatchOptions, WaitForOptions, } from "./types.js";
|
|
15
16
|
export type { PointerInput, TabOptions } from "./user-event.js";
|
|
16
17
|
export { userEvent } from "./user-event.js";
|
|
17
18
|
export { waitFor, waitForElementToBeRemoved } from "./wait-for.js";
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { fireEvent } from "./fire-event.js";
|
|
|
3
3
|
export { prettyWidget } from "./pretty-widget.js";
|
|
4
4
|
export { findAllByLabelText, findAllByRole, findAllByTestId, findAllByText, findByLabelText, findByRole, findByTestId, findByText, queryAllByLabelText, queryAllByRole, queryAllByTestId, queryAllByText, queryByLabelText, queryByRole, queryByTestId, queryByText, } from "./queries.js";
|
|
5
5
|
export { cleanup, render } from "./render.js";
|
|
6
|
+
export { renderHook } from "./render-hook.js";
|
|
6
7
|
export { getRoles, logRoles, prettyRoles } from "./role-helpers.js";
|
|
7
8
|
export { screen } from "./screen.js";
|
|
8
9
|
export { screenshot } from "./screenshot.js";
|
package/dist/pretty-widget.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getNativeId } from "@gtkx/ffi";
|
|
2
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
2
3
|
import { formatRole } from "./role-helpers.js";
|
|
3
4
|
import { isApplication } from "./traversal.js";
|
|
4
5
|
import { getWidgetText } from "./widget-text.js";
|
|
@@ -105,7 +106,7 @@ const printWidget = (widget, colors, depth, includeIds) => {
|
|
|
105
106
|
};
|
|
106
107
|
const printContainer = (container, colors, includeIds) => {
|
|
107
108
|
if (isApplication(container)) {
|
|
108
|
-
const windows =
|
|
109
|
+
const windows = Gtk.Window.listToplevels();
|
|
109
110
|
return windows.map((window) => printWidget(window, colors, 0, includeIds)).join("");
|
|
110
111
|
}
|
|
111
112
|
return printWidget(container, colors, 0, includeIds);
|
package/dist/queries.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { getConfig } from "./config.js";
|
|
1
2
|
import { buildMultipleFoundError, buildNotFoundError, buildTimeoutError } from "./error-builder.js";
|
|
2
3
|
import { findAll } from "./traversal.js";
|
|
3
4
|
import { getWidgetCheckedState, getWidgetExpandedState, getWidgetTestId, getWidgetText } from "./widget-text.js";
|
|
4
|
-
const DEFAULT_TIMEOUT = 1000;
|
|
5
5
|
const DEFAULT_INTERVAL = 50;
|
|
6
6
|
const buildNormalizer = (options) => {
|
|
7
7
|
if (options?.normalizer) {
|
|
@@ -60,7 +60,8 @@ const matchByRoleOptions = (widget, options) => {
|
|
|
60
60
|
return true;
|
|
61
61
|
};
|
|
62
62
|
const waitFor = async (callback, options) => {
|
|
63
|
-
const
|
|
63
|
+
const config = getConfig();
|
|
64
|
+
const { timeout = config.asyncUtilTimeout, interval = DEFAULT_INTERVAL, onTimeout } = options ?? {};
|
|
64
65
|
const startTime = Date.now();
|
|
65
66
|
let lastError = null;
|
|
66
67
|
while (Date.now() - startTime < timeout) {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { RenderHookOptions, RenderHookResult } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Renders a React hook for testing.
|
|
4
|
+
*
|
|
5
|
+
* Creates a test component that executes the hook and provides utilities
|
|
6
|
+
* for accessing the result, re-rendering with new props, and cleanup.
|
|
7
|
+
*
|
|
8
|
+
* @param callback - Function that calls the hook and returns its result
|
|
9
|
+
* @param options - Render options including initialProps and wrapper
|
|
10
|
+
* @returns A promise resolving to the hook result and utilities
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* import { renderHook } from "@gtkx/testing";
|
|
15
|
+
* import { useState } from "react";
|
|
16
|
+
*
|
|
17
|
+
* test("useState hook", async () => {
|
|
18
|
+
* const { result } = await renderHook(() => useState(0));
|
|
19
|
+
* expect(result.current[0]).toBe(0);
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* import { renderHook } from "@gtkx/testing";
|
|
26
|
+
*
|
|
27
|
+
* test("hook with props", async () => {
|
|
28
|
+
* const { result, rerender } = await renderHook(
|
|
29
|
+
* ({ multiplier }) => useMultiplier(multiplier),
|
|
30
|
+
* { initialProps: { multiplier: 2 } }
|
|
31
|
+
* );
|
|
32
|
+
*
|
|
33
|
+
* expect(result.current).toBe(2);
|
|
34
|
+
*
|
|
35
|
+
* await rerender({ multiplier: 3 });
|
|
36
|
+
* expect(result.current).toBe(3);
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare const renderHook: <Result, Props>(callback: (props: Props) => Result, options?: RenderHookOptions<Props>) => Promise<RenderHookResult<Result, Props>>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
import { render } from "./render.js";
|
|
4
|
+
/**
|
|
5
|
+
* Renders a React hook for testing.
|
|
6
|
+
*
|
|
7
|
+
* Creates a test component that executes the hook and provides utilities
|
|
8
|
+
* for accessing the result, re-rendering with new props, and cleanup.
|
|
9
|
+
*
|
|
10
|
+
* @param callback - Function that calls the hook and returns its result
|
|
11
|
+
* @param options - Render options including initialProps and wrapper
|
|
12
|
+
* @returns A promise resolving to the hook result and utilities
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* import { renderHook } from "@gtkx/testing";
|
|
17
|
+
* import { useState } from "react";
|
|
18
|
+
*
|
|
19
|
+
* test("useState hook", async () => {
|
|
20
|
+
* const { result } = await renderHook(() => useState(0));
|
|
21
|
+
* expect(result.current[0]).toBe(0);
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* import { renderHook } from "@gtkx/testing";
|
|
28
|
+
*
|
|
29
|
+
* test("hook with props", async () => {
|
|
30
|
+
* const { result, rerender } = await renderHook(
|
|
31
|
+
* ({ multiplier }) => useMultiplier(multiplier),
|
|
32
|
+
* { initialProps: { multiplier: 2 } }
|
|
33
|
+
* );
|
|
34
|
+
*
|
|
35
|
+
* expect(result.current).toBe(2);
|
|
36
|
+
*
|
|
37
|
+
* await rerender({ multiplier: 3 });
|
|
38
|
+
* expect(result.current).toBe(3);
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export const renderHook = async (callback, options) => {
|
|
43
|
+
const resultRef = { current: undefined };
|
|
44
|
+
let currentProps = options?.initialProps;
|
|
45
|
+
const TestComponent = ({ props }) => {
|
|
46
|
+
const result = callback(props);
|
|
47
|
+
const ref = useRef(resultRef);
|
|
48
|
+
ref.current.current = result;
|
|
49
|
+
return null;
|
|
50
|
+
};
|
|
51
|
+
const renderResult = await render(_jsx(TestComponent, { props: currentProps }), {
|
|
52
|
+
wrapper: options?.wrapper ?? false,
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
result: resultRef,
|
|
56
|
+
rerender: async (newProps) => {
|
|
57
|
+
if (newProps !== undefined) {
|
|
58
|
+
currentProps = newProps;
|
|
59
|
+
}
|
|
60
|
+
await renderResult.rerender(_jsx(TestComponent, { props: currentProps }));
|
|
61
|
+
},
|
|
62
|
+
unmount: renderResult.unmount,
|
|
63
|
+
};
|
|
64
|
+
};
|
package/dist/render.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { start, stop } from "@gtkx/ffi";
|
|
3
3
|
import * as Gio from "@gtkx/ffi/gio";
|
|
4
4
|
import { ApplicationContext, GtkApplicationWindow, reconciler } from "@gtkx/react";
|
|
5
5
|
import { bindQueries } from "./bind-queries.js";
|
|
@@ -20,14 +20,13 @@ const update = async (instance, element, fiberRoot) => {
|
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
22
|
const handleError = (error) => {
|
|
23
|
-
discardAllBatches();
|
|
24
23
|
lastRenderError = error;
|
|
25
24
|
};
|
|
26
25
|
const ensureInitialized = () => {
|
|
27
26
|
application = start("org.gtkx.testing", Gio.ApplicationFlags.NON_UNIQUE);
|
|
28
27
|
if (!container) {
|
|
29
28
|
const instance = reconciler.getInstance();
|
|
30
|
-
container = instance.createContainer(application, 1, null, false, null, "", handleError, handleError, () => { }, () => { }
|
|
29
|
+
container = instance.createContainer(application, 1, null, false, null, "", handleError, handleError, () => { }, () => { });
|
|
31
30
|
}
|
|
32
31
|
return { app: application, container };
|
|
33
32
|
};
|
package/dist/screen.d.ts
CHANGED
package/dist/screen.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
4
5
|
import { bindQueries } from "./bind-queries.js";
|
|
5
6
|
import { prettyWidget } from "./pretty-widget.js";
|
|
6
7
|
import { logRoles } from "./role-helpers.js";
|
|
@@ -80,8 +81,7 @@ export const screen = {
|
|
|
80
81
|
* ```
|
|
81
82
|
*/
|
|
82
83
|
screenshot: async (selector, options) => {
|
|
83
|
-
const
|
|
84
|
-
const windows = root.getWindows();
|
|
84
|
+
const windows = Gtk.Window.listToplevels();
|
|
85
85
|
if (windows.length === 0) {
|
|
86
86
|
throw new Error("No windows available for screenshot");
|
|
87
87
|
}
|
package/dist/traversal.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
2
2
|
export type Container = Gtk.Application | Gtk.Widget;
|
|
3
3
|
export declare const isApplication: (container: Container) => container is Gtk.Application;
|
|
4
4
|
export declare const traverse: (container: Container) => Generator<Gtk.Widget>;
|
package/dist/traversal.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
1
2
|
export const isApplication = (container) => "getWindows" in container && typeof container.getWindows === "function";
|
|
2
3
|
const traverseWidgetTree = function* (root) {
|
|
3
4
|
yield root;
|
|
@@ -7,15 +8,15 @@ const traverseWidgetTree = function* (root) {
|
|
|
7
8
|
child = child.getNextSibling();
|
|
8
9
|
}
|
|
9
10
|
};
|
|
10
|
-
const
|
|
11
|
-
const windows =
|
|
11
|
+
const traverseWindows = function* () {
|
|
12
|
+
const windows = Gtk.Window.listToplevels();
|
|
12
13
|
for (const window of windows) {
|
|
13
14
|
yield* traverseWidgetTree(window);
|
|
14
15
|
}
|
|
15
16
|
};
|
|
16
17
|
export const traverse = function* (container) {
|
|
17
18
|
if (isApplication(container)) {
|
|
18
|
-
yield*
|
|
19
|
+
yield* traverseWindows();
|
|
19
20
|
}
|
|
20
21
|
else {
|
|
21
22
|
yield* traverseWidgetTree(container);
|
package/dist/types.d.ts
CHANGED
|
@@ -151,3 +151,36 @@ export type ScreenshotResult = {
|
|
|
151
151
|
/** Height of the captured image in pixels */
|
|
152
152
|
height: number;
|
|
153
153
|
};
|
|
154
|
+
/**
|
|
155
|
+
* Options for {@link renderHook}.
|
|
156
|
+
*/
|
|
157
|
+
export type RenderHookOptions<Props> = {
|
|
158
|
+
/**
|
|
159
|
+
* Initial props passed to the hook callback.
|
|
160
|
+
*/
|
|
161
|
+
initialProps?: Props;
|
|
162
|
+
/**
|
|
163
|
+
* Wrapper component or boolean.
|
|
164
|
+
* - `false` (default): No wrapper
|
|
165
|
+
* - `true`: Wrap in GtkApplicationWindow
|
|
166
|
+
* - Component: Custom wrapper component
|
|
167
|
+
*/
|
|
168
|
+
wrapper?: boolean | ComponentType<{
|
|
169
|
+
children: ReactNode;
|
|
170
|
+
}>;
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* Result returned by {@link renderHook}.
|
|
174
|
+
*
|
|
175
|
+
* Provides access to the hook result and utilities for re-rendering and cleanup.
|
|
176
|
+
*/
|
|
177
|
+
export type RenderHookResult<Result, Props> = {
|
|
178
|
+
/** Object containing the current hook return value */
|
|
179
|
+
result: {
|
|
180
|
+
current: Result;
|
|
181
|
+
};
|
|
182
|
+
/** Re-render the hook with optional new props */
|
|
183
|
+
rerender: (newProps?: Props) => Promise<void>;
|
|
184
|
+
/** Unmount the component containing the hook */
|
|
185
|
+
unmount: () => Promise<void>;
|
|
186
|
+
};
|
package/dist/user-event.d.ts
CHANGED
|
@@ -46,13 +46,13 @@ export declare const userEvent: {
|
|
|
46
46
|
/**
|
|
47
47
|
* Double-clicks a widget.
|
|
48
48
|
*
|
|
49
|
-
* Emits
|
|
49
|
+
* Emits pressed/released signals with n_press=1, then n_press=2.
|
|
50
50
|
*/
|
|
51
51
|
dblClick: (element: Gtk.Widget) => Promise<void>;
|
|
52
52
|
/**
|
|
53
53
|
* Triple-clicks a widget.
|
|
54
54
|
*
|
|
55
|
-
* Emits
|
|
55
|
+
* Emits pressed/released signals with n_press=1, 2, then 3. Useful for text selection.
|
|
56
56
|
*/
|
|
57
57
|
tripleClick: (element: Gtk.Widget) => Promise<void>;
|
|
58
58
|
/**
|
package/dist/user-event.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getNativeObject } from "@gtkx/ffi";
|
|
2
1
|
import * as Gdk from "@gtkx/ffi/gdk";
|
|
3
2
|
import { signalEmitv, signalLookup, typeFromName, Value } from "@gtkx/ffi/gobject";
|
|
4
3
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
@@ -35,20 +34,31 @@ const click = async (element) => {
|
|
|
35
34
|
await fireEvent(element, "clicked");
|
|
36
35
|
}
|
|
37
36
|
};
|
|
37
|
+
const emitClickSequence = async (element, nPress) => {
|
|
38
|
+
const controller = getOrCreateController(element, Gtk.GestureClick);
|
|
39
|
+
for (let i = 1; i <= nPress; i++) {
|
|
40
|
+
const args = [
|
|
41
|
+
Value.newFromObject(controller),
|
|
42
|
+
Value.newFromInt(i),
|
|
43
|
+
Value.newFromDouble(0),
|
|
44
|
+
Value.newFromDouble(0),
|
|
45
|
+
];
|
|
46
|
+
signalEmitv(args, getSignalId(controller, "pressed"), 0, null);
|
|
47
|
+
signalEmitv(args, getSignalId(controller, "released"), 0, null);
|
|
48
|
+
}
|
|
49
|
+
await tick();
|
|
50
|
+
};
|
|
38
51
|
const dblClick = async (element) => {
|
|
39
|
-
await
|
|
40
|
-
await fireEvent(element, "clicked");
|
|
52
|
+
await emitClickSequence(element, 2);
|
|
41
53
|
};
|
|
42
54
|
const tripleClick = async (element) => {
|
|
43
|
-
await
|
|
44
|
-
await fireEvent(element, "clicked");
|
|
45
|
-
await fireEvent(element, "clicked");
|
|
55
|
+
await emitClickSequence(element, 3);
|
|
46
56
|
};
|
|
47
57
|
const tab = async (element, options) => {
|
|
48
58
|
const direction = options?.shift ? Gtk.DirectionType.TAB_BACKWARD : Gtk.DirectionType.TAB_FORWARD;
|
|
49
59
|
const root = element.getRoot();
|
|
50
|
-
if (root) {
|
|
51
|
-
|
|
60
|
+
if (root && root instanceof Gtk.Widget) {
|
|
61
|
+
root.childFocus(direction);
|
|
52
62
|
}
|
|
53
63
|
await tick();
|
|
54
64
|
};
|
|
@@ -56,16 +66,15 @@ const type = async (element, text) => {
|
|
|
56
66
|
if (!isEditable(element)) {
|
|
57
67
|
throw new Error("Cannot type into element: expected editable widget (TEXT_BOX, SEARCH_BOX, or SPIN_BUTTON)");
|
|
58
68
|
}
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
editable.setText(currentText + text);
|
|
69
|
+
const currentText = element.getText();
|
|
70
|
+
element.setText(currentText + text);
|
|
62
71
|
await tick();
|
|
63
72
|
};
|
|
64
73
|
const clear = async (element) => {
|
|
65
74
|
if (!isEditable(element)) {
|
|
66
75
|
throw new Error("Cannot clear element: expected editable widget (TEXT_BOX, SEARCH_BOX, or SPIN_BUTTON)");
|
|
67
76
|
}
|
|
68
|
-
|
|
77
|
+
element.setText("");
|
|
69
78
|
await tick();
|
|
70
79
|
};
|
|
71
80
|
const SELECTABLE_ROLES = new Set([Gtk.AccessibleRole.COMBO_BOX, Gtk.AccessibleRole.LIST]);
|
|
@@ -238,17 +247,19 @@ const parseKeyboardInput = (input) => {
|
|
|
238
247
|
}
|
|
239
248
|
return actions;
|
|
240
249
|
};
|
|
250
|
+
let gdkModifierType = null;
|
|
241
251
|
const keyboard = async (element, input) => {
|
|
252
|
+
gdkModifierType ??= typeFromName("GdkModifierType");
|
|
242
253
|
const controller = getOrCreateController(element, Gtk.EventControllerKey);
|
|
243
254
|
const actions = parseKeyboardInput(input);
|
|
244
255
|
for (const action of actions) {
|
|
245
256
|
const signalName = action.press ? "key-pressed" : "key-released";
|
|
246
|
-
const returnValue = Value.newFromBoolean(false);
|
|
257
|
+
const returnValue = action.press ? Value.newFromBoolean(false) : null;
|
|
247
258
|
signalEmitv([
|
|
248
259
|
Value.newFromObject(controller),
|
|
249
260
|
Value.newFromUint(action.keyval),
|
|
250
261
|
Value.newFromUint(0),
|
|
251
|
-
Value.
|
|
262
|
+
Value.newFromFlags(gdkModifierType, 0),
|
|
252
263
|
], getSignalId(controller, signalName), 0, returnValue);
|
|
253
264
|
if (action.press && action.keyval === Gdk.KEY_Return && isEditable(element)) {
|
|
254
265
|
await fireEvent(element, "activate");
|
|
@@ -314,13 +325,13 @@ export const userEvent = {
|
|
|
314
325
|
/**
|
|
315
326
|
* Double-clicks a widget.
|
|
316
327
|
*
|
|
317
|
-
* Emits
|
|
328
|
+
* Emits pressed/released signals with n_press=1, then n_press=2.
|
|
318
329
|
*/
|
|
319
330
|
dblClick,
|
|
320
331
|
/**
|
|
321
332
|
* Triple-clicks a widget.
|
|
322
333
|
*
|
|
323
|
-
* Emits
|
|
334
|
+
* Emits pressed/released signals with n_press=1, 2, then 3. Useful for text selection.
|
|
324
335
|
*/
|
|
325
336
|
tripleClick,
|
|
326
337
|
/**
|
package/dist/wait-for.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { getConfig } from "./config.js";
|
|
2
2
|
const DEFAULT_INTERVAL = 50;
|
|
3
3
|
/**
|
|
4
4
|
* Waits for a callback to succeed.
|
|
@@ -20,7 +20,8 @@ const DEFAULT_INTERVAL = 50;
|
|
|
20
20
|
* ```
|
|
21
21
|
*/
|
|
22
22
|
export const waitFor = async (callback, options) => {
|
|
23
|
-
const
|
|
23
|
+
const config = getConfig();
|
|
24
|
+
const { timeout = config.asyncUtilTimeout, interval = DEFAULT_INTERVAL, onTimeout } = options ?? {};
|
|
24
25
|
const startTime = Date.now();
|
|
25
26
|
let lastError = null;
|
|
26
27
|
while (Date.now() - startTime < timeout) {
|
|
@@ -73,7 +74,8 @@ const isElementRemoved = (element) => {
|
|
|
73
74
|
* ```
|
|
74
75
|
*/
|
|
75
76
|
export const waitForElementToBeRemoved = async (elementOrCallback, options) => {
|
|
76
|
-
const
|
|
77
|
+
const config = getConfig();
|
|
78
|
+
const { timeout = config.asyncUtilTimeout, interval = DEFAULT_INTERVAL, onTimeout } = options ?? {};
|
|
77
79
|
const initialElement = getElement(elementOrCallback);
|
|
78
80
|
if (initialElement === null || isElementRemoved(initialElement)) {
|
|
79
81
|
throw new Error("Element already removed: waitForElementToBeRemoved requires the element to be present initially");
|
package/dist/widget-text.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getNativeInterface } from "@gtkx/ffi";
|
|
2
2
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
3
3
|
const ROLES_WITH_INTERNAL_LABELS = new Set([
|
|
4
4
|
Gtk.AccessibleRole.BUTTON,
|
|
@@ -77,7 +77,7 @@ export const getWidgetText = (widget) => {
|
|
|
77
77
|
case Gtk.AccessibleRole.TEXT_BOX:
|
|
78
78
|
case Gtk.AccessibleRole.SEARCH_BOX:
|
|
79
79
|
case Gtk.AccessibleRole.SPIN_BUTTON:
|
|
80
|
-
return
|
|
80
|
+
return getNativeInterface(widget, Gtk.Editable)?.getText() ?? null;
|
|
81
81
|
case Gtk.AccessibleRole.GROUP:
|
|
82
82
|
return widget.getLabel?.() ?? null;
|
|
83
83
|
case Gtk.AccessibleRole.WINDOW:
|
package/dist/widget.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
2
|
-
export declare const isEditable: (widget:
|
|
2
|
+
export declare const isEditable: (widget: unknown) => widget is Gtk.Editable;
|
package/dist/widget.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1",
|
|
4
4
|
"description": "Testing utilities for GTKX applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gtkx",
|
|
@@ -36,13 +36,13 @@
|
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@gtkx/
|
|
40
|
-
"@gtkx/
|
|
39
|
+
"@gtkx/react": "0.17.1",
|
|
40
|
+
"@gtkx/ffi": "0.17.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@types/react-reconciler": "^0.
|
|
43
|
+
"@types/react-reconciler": "^0.33.0",
|
|
44
44
|
"react-reconciler": "^0.33.0",
|
|
45
|
-
"@gtkx/vitest": "0.
|
|
45
|
+
"@gtkx/vitest": "0.17.1"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsc -b && cp ../../README.md .",
|