@gtkx/testing 0.21.0 → 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/README.md +137 -34
- package/dist/act.d.ts +19 -0
- package/dist/act.d.ts.map +1 -0
- package/dist/act.js +56 -0
- package/dist/act.js.map +1 -0
- package/dist/bound-queries.d.ts +33 -0
- package/dist/bound-queries.d.ts.map +1 -0
- package/dist/bound-queries.js +2 -0
- package/dist/bound-queries.js.map +1 -0
- package/dist/build-queries.d.ts +16 -0
- package/dist/build-queries.d.ts.map +1 -0
- package/dist/build-queries.js +115 -0
- package/dist/build-queries.js.map +1 -0
- package/dist/cleanup-registry.d.ts +5 -0
- package/dist/cleanup-registry.d.ts.map +1 -0
- package/dist/cleanup-registry.js +21 -0
- package/dist/cleanup-registry.js.map +1 -0
- package/dist/config.d.ts +20 -52
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -46
- package/dist/config.js.map +1 -1
- package/dist/editable.d.ts +8 -0
- package/dist/editable.d.ts.map +1 -0
- package/dist/editable.js +26 -0
- package/dist/editable.js.map +1 -0
- package/dist/errors.d.ts +30 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +135 -0
- package/dist/errors.js.map +1 -0
- package/dist/fire-event.d.ts +8 -25
- package/dist/fire-event.d.ts.map +1 -1
- package/dist/fire-event.js +11 -30
- package/dist/fire-event.js.map +1 -1
- package/dist/frame-sync.d.ts +4 -0
- package/dist/frame-sync.d.ts.map +1 -0
- package/dist/frame-sync.js +37 -0
- package/dist/frame-sync.js.map +1 -0
- package/dist/index.d.ts +17 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -7
- package/dist/index.js.map +1 -1
- package/dist/matchers.d.ts +59 -0
- package/dist/matchers.d.ts.map +1 -0
- package/dist/matchers.js +117 -0
- package/dist/matchers.js.map +1 -0
- package/dist/pretty-widget.d.ts +24 -26
- package/dist/pretty-widget.d.ts.map +1 -1
- package/dist/pretty-widget.js +111 -117
- package/dist/pretty-widget.js.map +1 -1
- package/dist/queries.d.ts +190 -180
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +291 -320
- package/dist/queries.js.map +1 -1
- package/dist/register-test-hooks.d.ts +2 -0
- package/dist/register-test-hooks.d.ts.map +1 -0
- package/dist/register-test-hooks.js +16 -0
- package/dist/register-test-hooks.js.map +1 -0
- package/dist/render-hook.d.ts +15 -35
- package/dist/render-hook.d.ts.map +1 -1
- package/dist/render-hook.js +9 -45
- package/dist/render-hook.js.map +1 -1
- package/dist/render.d.ts +13 -42
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +138 -121
- package/dist/render.js.map +1 -1
- package/dist/role-helpers.d.ts +19 -53
- package/dist/role-helpers.d.ts.map +1 -1
- package/dist/role-helpers.js +48 -63
- package/dist/role-helpers.js.map +1 -1
- package/dist/screen.d.ts +7 -63
- package/dist/screen.d.ts.map +1 -1
- package/dist/screen.js +24 -110
- package/dist/screen.js.map +1 -1
- package/dist/screenshot.d.ts +21 -23
- package/dist/screenshot.d.ts.map +1 -1
- package/dist/screenshot.js +98 -50
- package/dist/screenshot.js.map +1 -1
- package/dist/suggestions.d.ts +27 -0
- package/dist/suggestions.d.ts.map +1 -0
- package/dist/suggestions.js +61 -0
- package/dist/suggestions.js.map +1 -0
- package/dist/traversal.d.ts +12 -10
- package/dist/traversal.d.ts.map +1 -1
- package/dist/traversal.js +36 -15
- package/dist/traversal.js.map +1 -1
- package/dist/types.d.ts +115 -169
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/user-event/adjustment.d.ts +10 -0
- package/dist/user-event/adjustment.d.ts.map +1 -0
- package/dist/user-event/adjustment.js +35 -0
- package/dist/user-event/adjustment.js.map +1 -0
- package/dist/user-event/click.d.ts +8 -0
- package/dist/user-event/click.d.ts.map +1 -0
- package/dist/user-event/click.js +73 -0
- package/dist/user-event/click.js.map +1 -0
- package/dist/user-event/controller.d.ts +8 -0
- package/dist/user-event/controller.d.ts.map +1 -0
- package/dist/user-event/controller.js +31 -0
- package/dist/user-event/controller.js.map +1 -0
- package/dist/user-event/dispatch.d.ts +7 -0
- package/dist/user-event/dispatch.d.ts.map +1 -0
- package/dist/user-event/dispatch.js +11 -0
- package/dist/user-event/dispatch.js.map +1 -0
- package/dist/user-event/event-wrapper.d.ts +4 -0
- package/dist/user-event/event-wrapper.d.ts.map +1 -0
- package/dist/user-event/event-wrapper.js +59 -0
- package/dist/user-event/event-wrapper.js.map +1 -0
- package/dist/user-event/gesture.d.ts +35 -0
- package/dist/user-event/gesture.d.ts.map +1 -0
- package/dist/user-event/gesture.js +138 -0
- package/dist/user-event/gesture.js.map +1 -0
- package/dist/user-event/index.d.ts +51 -0
- package/dist/user-event/index.d.ts.map +1 -0
- package/dist/user-event/index.js +42 -0
- package/dist/user-event/index.js.map +1 -0
- package/dist/user-event/keyboard.d.ts +10 -0
- package/dist/user-event/keyboard.d.ts.map +1 -0
- package/dist/user-event/keyboard.js +204 -0
- package/dist/user-event/keyboard.js.map +1 -0
- package/dist/user-event/pointer.d.ts +10 -0
- package/dist/user-event/pointer.d.ts.map +1 -0
- package/dist/user-event/pointer.js +30 -0
- package/dist/user-event/pointer.js.map +1 -0
- package/dist/user-event/selection.d.ts +5 -0
- package/dist/user-event/selection.d.ts.map +1 -0
- package/dist/user-event/selection.js +105 -0
- package/dist/user-event/selection.js.map +1 -0
- package/dist/user-event/state.d.ts +7 -0
- package/dist/user-event/state.d.ts.map +1 -0
- package/dist/user-event/state.js +3 -0
- package/dist/user-event/state.js.map +1 -0
- package/dist/user-event/text.d.ts +18 -0
- package/dist/user-event/text.d.ts.map +1 -0
- package/dist/user-event/text.js +113 -0
- package/dist/user-event/text.js.map +1 -0
- package/dist/wait-for.d.ts +16 -36
- package/dist/wait-for.d.ts.map +1 -1
- package/dist/wait-for.js +74 -70
- package/dist/wait-for.js.map +1 -1
- package/dist/widget-accessible-properties.d.ts +34 -0
- package/dist/widget-accessible-properties.d.ts.map +1 -0
- package/dist/widget-accessible-properties.js +260 -0
- package/dist/widget-accessible-properties.js.map +1 -0
- package/dist/within.d.ts +10 -26
- package/dist/within.d.ts.map +1 -1
- package/dist/within.js +16 -25
- package/dist/within.js.map +1 -1
- package/package.json +23 -12
- package/src/act.ts +77 -0
- package/src/bound-queries.ts +37 -0
- package/src/build-queries.ts +226 -0
- package/src/cleanup-registry.ts +27 -0
- package/src/config.ts +35 -62
- package/src/editable.ts +37 -0
- package/src/errors.ts +191 -0
- package/src/fire-event.ts +13 -38
- package/src/frame-sync.ts +51 -0
- package/src/index.ts +58 -15
- package/src/matchers.ts +251 -0
- package/src/pretty-widget.ts +163 -130
- package/src/queries.ts +485 -396
- package/src/register-test-hooks.ts +19 -0
- package/src/render-hook.tsx +38 -45
- package/src/render.tsx +192 -155
- package/src/role-helpers.ts +65 -72
- package/src/screen.ts +26 -114
- package/src/screenshot.ts +128 -56
- package/src/suggestions.ts +106 -0
- package/src/traversal.ts +56 -22
- package/src/types.ts +144 -172
- package/src/user-event/adjustment.ts +62 -0
- package/src/user-event/click.ts +99 -0
- package/src/user-event/controller.ts +58 -0
- package/src/user-event/dispatch.ts +25 -0
- package/src/user-event/event-wrapper.ts +80 -0
- package/src/user-event/gesture.ts +239 -0
- package/src/user-event/index.ts +81 -0
- package/src/user-event/keyboard.ts +290 -0
- package/src/user-event/pointer.ts +46 -0
- package/src/user-event/selection.ts +159 -0
- package/src/user-event/state.ts +8 -0
- package/src/user-event/text.ts +172 -0
- package/src/wait-for.ts +117 -78
- package/src/widget-accessible-properties.ts +388 -0
- package/src/within.ts +29 -27
- package/dist/bind-queries.d.ts +0 -14
- package/dist/bind-queries.d.ts.map +0 -1
- package/dist/bind-queries.js +0 -29
- package/dist/bind-queries.js.map +0 -1
- package/dist/error-builder.d.ts +0 -28
- package/dist/error-builder.d.ts.map +0 -1
- package/dist/error-builder.js +0 -81
- package/dist/error-builder.js.map +0 -1
- package/dist/timing.d.ts +0 -18
- package/dist/timing.d.ts.map +0 -1
- package/dist/timing.js +0 -18
- package/dist/timing.js.map +0 -1
- package/dist/user-event.d.ts +0 -138
- package/dist/user-event.d.ts.map +0 -1
- package/dist/user-event.js +0 -411
- package/dist/user-event.js.map +0 -1
- package/dist/widget-text.d.ts +0 -69
- package/dist/widget-text.d.ts.map +0 -1
- package/dist/widget-text.js +0 -177
- package/dist/widget-text.js.map +0 -1
- package/dist/widget.d.ts +0 -3
- package/dist/widget.d.ts.map +0 -1
- package/dist/widget.js +0 -13
- package/dist/widget.js.map +0 -1
- package/src/bind-queries.ts +0 -52
- package/src/error-builder.ts +0 -102
- package/src/timing.ts +0 -17
- package/src/user-event.ts +0 -492
- package/src/widget-text.ts +0 -202
- package/src/widget.ts +0 -15
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { quit } from "@gtkx/runtime";
|
|
2
|
+
import { registerMatchers } from "./matchers.js";
|
|
3
|
+
import { cleanup } from "./render.js";
|
|
4
|
+
|
|
5
|
+
const callRunnerHook = (name: "afterEach" | "afterAll", callback: () => unknown): void => {
|
|
6
|
+
const hook: unknown = Reflect.get(globalThis, name);
|
|
7
|
+
|
|
8
|
+
if (typeof hook === "function") {
|
|
9
|
+
(hook as (callback: () => unknown) => void)(callback);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const registerTestRuntimeHooks = (): void => {
|
|
14
|
+
callRunnerHook("afterEach", cleanup);
|
|
15
|
+
callRunnerHook("afterAll", quit);
|
|
16
|
+
registerMatchers();
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
registerTestRuntimeHooks();
|
package/src/render-hook.tsx
CHANGED
|
@@ -1,71 +1,64 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { render } from "./render.js";
|
|
1
|
+
import * as Gtk from "@gtkx/gi/gtk";
|
|
3
2
|
import type { RenderHookOptions, RenderHookResult } from "./types.js";
|
|
3
|
+
import { render } from "./render.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Renders a
|
|
7
|
-
*
|
|
8
|
-
* Creates a test component that executes the hook and provides utilities
|
|
9
|
-
* for accessing the result, re-rendering with new props, and cleanup.
|
|
10
|
-
*
|
|
11
|
-
* @param callback - Function that calls the hook and returns its result
|
|
12
|
-
* @param options - Render options including initialProps and wrapper
|
|
13
|
-
* @returns A promise resolving to the hook result and utilities
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* ```tsx
|
|
17
|
-
* import { renderHook } from "@gtkx/testing";
|
|
18
|
-
* import { useState } from "react";
|
|
19
|
-
*
|
|
20
|
-
* test("useState hook", async () => {
|
|
21
|
-
* const { result } = await renderHook(() => useState(0));
|
|
22
|
-
* expect(result.current[0]).toBe(0);
|
|
23
|
-
* });
|
|
24
|
-
* ```
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```tsx
|
|
28
|
-
* import { renderHook } from "@gtkx/testing";
|
|
29
|
-
*
|
|
30
|
-
* test("hook with props", async () => {
|
|
31
|
-
* const { result, rerender } = await renderHook(
|
|
32
|
-
* ({ multiplier }) => useMultiplier(multiplier),
|
|
33
|
-
* { initialProps: { multiplier: 2 } }
|
|
34
|
-
* );
|
|
6
|
+
* Renders a test component that calls the given hook, exposing its latest
|
|
7
|
+
* return value along with rerender and unmount controls.
|
|
35
8
|
*
|
|
36
|
-
*
|
|
9
|
+
* @param callback The hook to run, invoked with the current props.
|
|
10
|
+
* @param options Optional initial props and wrapper component.
|
|
11
|
+
* @returns A result whose `result.current` holds the hook's latest value.
|
|
12
|
+
*/
|
|
13
|
+
function renderHook<Result>(
|
|
14
|
+
callback: () => Result,
|
|
15
|
+
options?: RenderHookOptions<undefined>,
|
|
16
|
+
): Promise<RenderHookResult<Result, undefined>>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Renders a test component that calls the given hook with props, exposing its
|
|
20
|
+
* latest return value along with rerender and unmount controls.
|
|
37
21
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* ```
|
|
22
|
+
* @param callback The hook to run, invoked with the current props.
|
|
23
|
+
* @param options Initial props and optional wrapper component.
|
|
24
|
+
* @returns A result whose `result.current` holds the hook's latest value.
|
|
42
25
|
*/
|
|
43
|
-
|
|
26
|
+
function renderHook<Result, Props>(
|
|
27
|
+
callback: (props: Props) => Result,
|
|
28
|
+
options: RenderHookOptions<Props>,
|
|
29
|
+
): Promise<RenderHookResult<Result, Props>>;
|
|
30
|
+
|
|
31
|
+
async function renderHook<Result, Props>(
|
|
44
32
|
callback: (props: Props) => Result,
|
|
45
33
|
options?: RenderHookOptions<Props>,
|
|
46
|
-
): Promise<RenderHookResult<Result, Props>>
|
|
47
|
-
const
|
|
48
|
-
|
|
34
|
+
): Promise<RenderHookResult<Result, Props>> {
|
|
35
|
+
const initialProps = (options as { initialProps?: Props } | undefined)?.initialProps as Props;
|
|
36
|
+
const resultRef: { current: Result | undefined } = { current: undefined };
|
|
37
|
+
let currentProps: Props = initialProps;
|
|
49
38
|
|
|
50
39
|
const TestComponent = ({ props }: { props: Props }): null => {
|
|
51
40
|
const result = callback(props);
|
|
52
|
-
|
|
53
|
-
|
|
41
|
+
resultRef.current = result;
|
|
42
|
+
|
|
54
43
|
return null;
|
|
55
44
|
};
|
|
56
45
|
|
|
57
46
|
const renderResult = await render(<TestComponent props={currentProps} />, {
|
|
58
|
-
|
|
47
|
+
container: new Gtk.Box(),
|
|
48
|
+
wrapper: options?.wrapper,
|
|
59
49
|
});
|
|
60
50
|
|
|
61
51
|
return {
|
|
62
|
-
result: resultRef,
|
|
52
|
+
result: resultRef as { current: Result },
|
|
63
53
|
rerender: async (newProps?: Props) => {
|
|
64
54
|
if (newProps !== undefined) {
|
|
65
55
|
currentProps = newProps;
|
|
66
56
|
}
|
|
57
|
+
|
|
67
58
|
await renderResult.rerender(<TestComponent props={currentProps} />);
|
|
68
59
|
},
|
|
69
60
|
unmount: renderResult.unmount,
|
|
70
61
|
};
|
|
71
|
-
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { renderHook };
|
package/src/render.tsx
CHANGED
|
@@ -1,192 +1,229 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import * as
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
1
|
+
import type { RootElement } from "@gtkx/react";
|
|
2
|
+
import * as Gtk from "@gtkx/gi/gtk";
|
|
3
|
+
import {
|
|
4
|
+
createReconcilerRoot,
|
|
5
|
+
isRootElement,
|
|
6
|
+
type ReconcilerRoot,
|
|
7
|
+
setReconcilerErrorHandler,
|
|
8
|
+
} from "@gtkx/react/internal";
|
|
9
|
+
import { type ErrorInfo, type ReactNode, StrictMode } from "react";
|
|
10
|
+
import type { RenderResult } from "./bound-queries.js";
|
|
11
|
+
import type { QueryMap, RenderOptions, ScreenshotOptions, WindowSelector } from "./types.js";
|
|
12
|
+
import { runInAct } from "./act.js";
|
|
13
|
+
import { addToCleanupQueue, runCleanup } from "./cleanup-registry.js";
|
|
14
|
+
import { scheduleAfterLayout } from "./frame-sync.js";
|
|
15
|
+
import { logWidget, type PrettyWidgetOptions } from "./pretty-widget.js";
|
|
16
|
+
import { logRoles } from "./role-helpers.js";
|
|
17
|
+
import { clearScreen, setScreen } from "./screen.js";
|
|
18
|
+
import { captureAndSaveScreenshot } from "./screenshot.js";
|
|
19
|
+
import { type Container, TOPLEVELS, traverse } from "./traversal.js";
|
|
20
|
+
import { resetClipboard } from "./user-event/index.js";
|
|
21
|
+
import { within } from "./within.js";
|
|
22
|
+
|
|
23
|
+
type ActiveRender = {
|
|
24
|
+
root: ReconcilerRoot;
|
|
25
|
+
window: Gtk.Window | null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type ResolvedContainer = {
|
|
29
|
+
containerInfo: Gtk.Widget | RootElement;
|
|
30
|
+
window: Gtk.Window | null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type ReconcilerErrorState = {
|
|
34
|
+
lastError: Error | null;
|
|
35
|
+
isHandlerInstalled: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const reconcilerErrors: ReconcilerErrorState = { lastError: null, isHandlerInstalled: false };
|
|
39
|
+
const activeRenders: Set<ActiveRender> = new Set();
|
|
40
|
+
const HARNESS_WINDOW_WIDTH = 800;
|
|
41
|
+
const HARNESS_WINDOW_HEIGHT = 600;
|
|
42
|
+
|
|
43
|
+
const flushLayout = (window: Gtk.Window | null): Promise<void> =>
|
|
44
|
+
new Promise((resolve) => {
|
|
45
|
+
scheduleAfterLayout(window, resolve);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const update = async (element: ReactNode, root: ReconcilerRoot): Promise<void> => {
|
|
49
|
+
await runInAct(() => {
|
|
50
|
+
root.update(element);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (reconcilerErrors.lastError) {
|
|
54
|
+
const captured = reconcilerErrors.lastError;
|
|
55
|
+
reconcilerErrors.lastError = null;
|
|
56
|
+
throw captured;
|
|
33
57
|
}
|
|
34
58
|
};
|
|
35
59
|
|
|
36
|
-
const
|
|
37
|
-
|
|
60
|
+
const disposeActiveRender = async (active: ActiveRender): Promise<void> => {
|
|
61
|
+
if (!activeRenders.delete(active)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await active.root.unmount(async (root) => {
|
|
66
|
+
await update(null, root);
|
|
67
|
+
active.window?.destroy();
|
|
68
|
+
});
|
|
38
69
|
};
|
|
39
70
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (!container) {
|
|
44
|
-
const instance = reconciler.getInstance();
|
|
45
|
-
container = instance.createContainer(
|
|
46
|
-
application,
|
|
47
|
-
1,
|
|
48
|
-
null,
|
|
49
|
-
false,
|
|
50
|
-
null,
|
|
51
|
-
"",
|
|
52
|
-
handleError,
|
|
53
|
-
handleError,
|
|
54
|
-
() => {},
|
|
55
|
-
() => {},
|
|
56
|
-
);
|
|
71
|
+
const disposeAllActiveRenders = async (): Promise<void> => {
|
|
72
|
+
for (const active of activeRenders) {
|
|
73
|
+
await disposeActiveRender(active);
|
|
57
74
|
}
|
|
75
|
+
};
|
|
58
76
|
|
|
59
|
-
|
|
77
|
+
const handleError = (error: unknown): void => {
|
|
78
|
+
reconcilerErrors.lastError = error instanceof Error ? error : new Error(String(error));
|
|
60
79
|
};
|
|
61
80
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
);
|
|
81
|
+
const installErrorHandler = (): void => {
|
|
82
|
+
if (reconcilerErrors.isHandlerInstalled) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
67
85
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
86
|
+
setReconcilerErrorHandler(handleError);
|
|
87
|
+
reconcilerErrors.isHandlerInstalled = true;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const resolveContainer = (container: RenderOptions["container"]): ResolvedContainer => {
|
|
91
|
+
if (isRootElement(container)) {
|
|
92
|
+
return { containerInfo: container, window: null };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (container instanceof Gtk.Widget) {
|
|
96
|
+
return { containerInfo: container, window: null };
|
|
72
97
|
}
|
|
73
|
-
|
|
98
|
+
|
|
99
|
+
const window = new Gtk.Window({ defaultWidth: HARNESS_WINDOW_WIDTH, defaultHeight: HARNESS_WINDOW_HEIGHT });
|
|
100
|
+
window.setTitlebar(new Gtk.HeaderBar({ showTitleButtons: false }));
|
|
101
|
+
|
|
102
|
+
return { containerInfo: window, window };
|
|
74
103
|
};
|
|
75
104
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
105
|
+
const firstToplevelWidget = (baseElement: Container): Gtk.Widget => {
|
|
106
|
+
if (baseElement instanceof Gtk.Widget) {
|
|
107
|
+
return baseElement;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const [first] = traverse(baseElement);
|
|
111
|
+
|
|
112
|
+
if (first) {
|
|
113
|
+
return first;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
throw new Error("render() produced no widgets: ensure the element renders visible content");
|
|
84
117
|
};
|
|
85
118
|
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
119
|
+
const resolveResultContainer = (
|
|
120
|
+
resolved: ResolvedContainer,
|
|
121
|
+
container: RenderOptions["container"],
|
|
89
122
|
baseElement: Container,
|
|
90
123
|
): Gtk.Widget => {
|
|
91
|
-
if (
|
|
92
|
-
return
|
|
124
|
+
if (resolved.window) {
|
|
125
|
+
return resolved.window;
|
|
93
126
|
}
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
|
|
127
|
+
|
|
128
|
+
if (container instanceof Gtk.Widget) {
|
|
129
|
+
return container;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return firstToplevelWidget(baseElement);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const renderErrorHandlers = <Q extends QueryMap>(options: RenderOptions<Q> | undefined) => ({
|
|
136
|
+
onUncaughtError: handleError,
|
|
137
|
+
onCaughtError: (error: unknown, errorInfo: ErrorInfo): void => {
|
|
138
|
+
handleError(error);
|
|
139
|
+
options?.onCaughtError?.(error, errorInfo);
|
|
140
|
+
},
|
|
141
|
+
onRecoverableError: (error: unknown, errorInfo: ErrorInfo): void => {
|
|
142
|
+
options?.onRecoverableError?.(error, errorInfo);
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const applyEnableAnimations = (areAnimationsEnabled: boolean): void => {
|
|
147
|
+
const settings = Gtk.Settings.getDefault();
|
|
148
|
+
|
|
149
|
+
if (settings) {
|
|
150
|
+
settings.gtkEnableAnimations = areAnimationsEnabled;
|
|
97
151
|
}
|
|
98
|
-
return firstWidget;
|
|
99
152
|
};
|
|
100
153
|
|
|
101
154
|
/**
|
|
102
|
-
* Renders a React element
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* query methods and utilities for interacting with the rendered widgets.
|
|
155
|
+
* Renders a React element into a GTK4 widget tree and returns queries
|
|
156
|
+
* scoped to it along with controls for rerendering and unmounting. When no
|
|
157
|
+
* container is supplied, a harness window is created and presented.
|
|
106
158
|
*
|
|
107
|
-
* @param element
|
|
108
|
-
* @param options
|
|
109
|
-
* @returns A
|
|
110
|
-
*
|
|
111
|
-
* @example
|
|
112
|
-
* ```tsx
|
|
113
|
-
* import { render, screen } from "@gtkx/testing";
|
|
114
|
-
*
|
|
115
|
-
* test("button click", async () => {
|
|
116
|
-
* await render(<MyButton />);
|
|
117
|
-
* const button = await screen.findByRole(Gtk.AccessibleRole.BUTTON);
|
|
118
|
-
* await userEvent.click(button);
|
|
119
|
-
* });
|
|
120
|
-
* ```
|
|
121
|
-
*
|
|
122
|
-
* @see {@link cleanup} for cleaning up after tests
|
|
123
|
-
* @see {@link screen} for global query access
|
|
159
|
+
* @param element The React element to render.
|
|
160
|
+
* @param options Optional container, wrapper, custom queries, and other render settings.
|
|
161
|
+
* @returns A render result with bound queries, debug helpers, and lifecycle controls.
|
|
124
162
|
*/
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
|
|
163
|
+
const render = async <Q extends QueryMap = Record<never, never>>(
|
|
164
|
+
element: ReactNode,
|
|
165
|
+
options?: RenderOptions<Q>,
|
|
166
|
+
): Promise<RenderResult<Q>> => {
|
|
167
|
+
installErrorHandler();
|
|
168
|
+
applyEnableAnimations(options?.animations === true);
|
|
169
|
+
const baseElement: Container = options?.baseElement ?? TOPLEVELS;
|
|
170
|
+
const Wrapper = options?.wrapper;
|
|
171
|
+
const resolved = resolveContainer(options?.container);
|
|
172
|
+
|
|
173
|
+
const root = createReconcilerRoot({
|
|
174
|
+
containerInfo: resolved.containerInfo,
|
|
175
|
+
...renderErrorHandlers(options),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const active: ActiveRender = { root, window: resolved.window };
|
|
179
|
+
activeRenders.add(active);
|
|
180
|
+
addToCleanupQueue(disposeAllActiveRenders);
|
|
181
|
+
addToCleanupQueue(clearScreen);
|
|
182
|
+
addToCleanupQueue(resetClipboard);
|
|
183
|
+
|
|
184
|
+
const wrap = (node: ReactNode): ReactNode => {
|
|
185
|
+
const wrapped = Wrapper ? <Wrapper>{node}</Wrapper> : node;
|
|
186
|
+
|
|
187
|
+
return options?.reactStrictMode ? <StrictMode>{wrapped}</StrictMode> : wrapped;
|
|
188
|
+
};
|
|
135
189
|
|
|
136
|
-
|
|
190
|
+
await update(wrap(element), root);
|
|
191
|
+
resolved.window?.present();
|
|
192
|
+
await flushLayout(resolved.window);
|
|
193
|
+
const container = resolveResultContainer(resolved, options?.container, baseElement);
|
|
137
194
|
|
|
138
|
-
|
|
139
|
-
|
|
195
|
+
const result: RenderResult<Q> = {
|
|
196
|
+
...within(baseElement, options?.queries),
|
|
197
|
+
container,
|
|
140
198
|
baseElement,
|
|
141
|
-
|
|
142
|
-
|
|
199
|
+
unmount: async () => {
|
|
200
|
+
await disposeActiveRender(active);
|
|
201
|
+
},
|
|
143
202
|
rerender: async (newElement: ReactNode) => {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
203
|
+
await update(wrap(newElement), root);
|
|
204
|
+
await flushLayout(resolved.window);
|
|
205
|
+
},
|
|
206
|
+
debug: (element: Container | Container[] = baseElement, debugOptions?: PrettyWidgetOptions) => {
|
|
207
|
+
logWidget(element, debugOptions);
|
|
148
208
|
},
|
|
149
|
-
|
|
150
|
-
|
|
209
|
+
logRoles: () => {
|
|
210
|
+
logRoles(baseElement);
|
|
151
211
|
},
|
|
212
|
+
screenshot: (selector?: WindowSelector, screenshotOptions?: ScreenshotOptions) =>
|
|
213
|
+
captureAndSaveScreenshot(selector, screenshotOptions),
|
|
152
214
|
};
|
|
215
|
+
|
|
216
|
+
setScreen(result);
|
|
217
|
+
|
|
218
|
+
return result;
|
|
153
219
|
};
|
|
154
220
|
|
|
155
221
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* Unmounts all rendered components and resets the testing environment.
|
|
159
|
-
* Call this in `afterEach` to ensure tests don't affect each other.
|
|
160
|
-
*
|
|
161
|
-
* @example
|
|
162
|
-
* ```tsx
|
|
163
|
-
* import { render, cleanup } from "@gtkx/testing";
|
|
164
|
-
*
|
|
165
|
-
* afterEach(async () => {
|
|
166
|
-
* await cleanup();
|
|
167
|
-
* });
|
|
168
|
-
*
|
|
169
|
-
* test("my test", async () => {
|
|
170
|
-
* await render(<MyComponent />);
|
|
171
|
-
* // ...
|
|
172
|
-
* });
|
|
173
|
-
* ```
|
|
222
|
+
* Unmounts every active render and runs all registered cleanup callbacks,
|
|
223
|
+
* resetting the screen and clipboard. Called automatically after each test.
|
|
174
224
|
*/
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const instance = reconciler.getInstance();
|
|
178
|
-
await update(instance, null, container);
|
|
179
|
-
}
|
|
180
|
-
container = null;
|
|
181
|
-
setScreenRoot(null);
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
const handleSignal = (): void => {
|
|
185
|
-
try {
|
|
186
|
-
stop();
|
|
187
|
-
} catch {}
|
|
188
|
-
process.exit(0);
|
|
225
|
+
const cleanup = async (): Promise<void> => {
|
|
226
|
+
await runCleanup();
|
|
189
227
|
};
|
|
190
228
|
|
|
191
|
-
|
|
192
|
-
process.on("SIGINT", handleSignal);
|
|
229
|
+
export { render, cleanup };
|