@gtkx/testing 0.16.0 → 0.17.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/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/queries.js +3 -2
- package/dist/render-hook.d.ts +40 -0
- package/dist/render-hook.js +64 -0
- package/dist/render.js +1 -1
- package/dist/screenshot.d.ts +3 -0
- package/dist/types.d.ts +33 -0
- package/dist/user-event.js +5 -7
- package/dist/wait-for.js +5 -3
- package/dist/widget.d.ts +1 -1
- package/dist/widget.js +3 -0
- package/package.json +5 -5
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/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
|
@@ -26,7 +26,7 @@ const ensureInitialized = () => {
|
|
|
26
26
|
application = start("org.gtkx.testing", Gio.ApplicationFlags.NON_UNIQUE);
|
|
27
27
|
if (!container) {
|
|
28
28
|
const instance = reconciler.getInstance();
|
|
29
|
-
container = instance.createContainer(application, 1, null, false, null, "", handleError, handleError, () => { }, () => { }
|
|
29
|
+
container = instance.createContainer(application, 1, null, false, null, "", handleError, handleError, () => { }, () => { });
|
|
30
30
|
}
|
|
31
31
|
return { app: application, container };
|
|
32
32
|
};
|
package/dist/screenshot.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
2
2
|
import type { ScreenshotResult, WaitForOptions } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Options for capturing widget screenshots.
|
|
5
|
+
*/
|
|
3
6
|
export type ScreenshotOptions = Pick<WaitForOptions, "timeout" | "interval">;
|
|
4
7
|
/**
|
|
5
8
|
* Captures a screenshot of a GTK widget as a PNG image.
|
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.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";
|
|
@@ -58,8 +57,8 @@ const tripleClick = async (element) => {
|
|
|
58
57
|
const tab = async (element, options) => {
|
|
59
58
|
const direction = options?.shift ? Gtk.DirectionType.TAB_BACKWARD : Gtk.DirectionType.TAB_FORWARD;
|
|
60
59
|
const root = element.getRoot();
|
|
61
|
-
if (root) {
|
|
62
|
-
|
|
60
|
+
if (root && root instanceof Gtk.Widget) {
|
|
61
|
+
root.childFocus(direction);
|
|
63
62
|
}
|
|
64
63
|
await tick();
|
|
65
64
|
};
|
|
@@ -67,16 +66,15 @@ const type = async (element, text) => {
|
|
|
67
66
|
if (!isEditable(element)) {
|
|
68
67
|
throw new Error("Cannot type into element: expected editable widget (TEXT_BOX, SEARCH_BOX, or SPIN_BUTTON)");
|
|
69
68
|
}
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
editable.setText(currentText + text);
|
|
69
|
+
const currentText = element.getText();
|
|
70
|
+
element.setText(currentText + text);
|
|
73
71
|
await tick();
|
|
74
72
|
};
|
|
75
73
|
const clear = async (element) => {
|
|
76
74
|
if (!isEditable(element)) {
|
|
77
75
|
throw new Error("Cannot clear element: expected editable widget (TEXT_BOX, SEARCH_BOX, or SPIN_BUTTON)");
|
|
78
76
|
}
|
|
79
|
-
|
|
77
|
+
element.setText("");
|
|
80
78
|
await tick();
|
|
81
79
|
};
|
|
82
80
|
const SELECTABLE_ROLES = new Set([Gtk.AccessibleRole.COMBO_BOX, Gtk.AccessibleRole.LIST]);
|
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.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.2",
|
|
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.2",
|
|
40
|
+
"@gtkx/ffi": "0.17.2"
|
|
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.2"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsc -b && cp ../../README.md .",
|