@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 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
@@ -4,6 +4,7 @@ const defaultGetElementError = (message, _container) => {
4
4
  const defaultConfig = {
5
5
  showSuggestions: true,
6
6
  getElementError: defaultGetElementError,
7
+ asyncUtilTimeout: 1000,
7
8
  };
8
9
  let currentConfig = { ...defaultConfig };
9
10
  /**
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";
@@ -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 = container.getWindows();
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 { timeout = DEFAULT_TIMEOUT, interval = DEFAULT_INTERVAL, onTimeout } = options ?? {};
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 { discardAllBatches, start, stop } from "@gtkx/ffi";
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, () => { }, () => { }, null);
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
@@ -1,4 +1,4 @@
1
- import type * as Gtk from "@gtkx/ffi/gtk";
1
+ import * as Gtk from "@gtkx/ffi/gtk";
2
2
  import { type ScreenshotOptions } from "./screenshot.js";
3
3
  import type { ScreenshotResult } from "./types.js";
4
4
  /** @internal */
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 root = getRoot();
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
  }
@@ -1,4 +1,4 @@
1
- import type * as Gtk from "@gtkx/ffi/gtk";
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 traverseApplication = function* (app) {
11
- const windows = app.getWindows();
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* traverseApplication(container);
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
+ };
@@ -46,13 +46,13 @@ export declare const userEvent: {
46
46
  /**
47
47
  * Double-clicks a widget.
48
48
  *
49
- * Emits two consecutive clicked signals.
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 three consecutive clicked signals. Useful for text selection.
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
  /**
@@ -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 fireEvent(element, "clicked");
40
- await fireEvent(element, "clicked");
52
+ await emitClickSequence(element, 2);
41
53
  };
42
54
  const tripleClick = async (element) => {
43
- await fireEvent(element, "clicked");
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
- getNativeObject(root.handle).childFocus(direction);
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 editable = getNativeObject(element.handle, Gtk.Editable);
60
- const currentText = editable.getText();
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
- getNativeObject(element.handle, Gtk.Editable)?.setText("");
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.newFromUint(0),
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 two consecutive clicked signals.
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 three consecutive clicked signals. Useful for text selection.
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
- const DEFAULT_TIMEOUT = 1000;
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 { timeout = DEFAULT_TIMEOUT, interval = DEFAULT_INTERVAL, onTimeout } = options ?? {};
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 { timeout = DEFAULT_TIMEOUT, interval = DEFAULT_INTERVAL, onTimeout } = options ?? {};
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");
@@ -1,4 +1,4 @@
1
- import { getNativeObject } from "@gtkx/ffi";
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 getNativeObject(widget.handle, Gtk.Editable).getText() ?? null;
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: Gtk.Widget) => boolean;
2
+ export declare const isEditable: (widget: unknown) => widget is Gtk.Editable;
package/dist/widget.js CHANGED
@@ -5,5 +5,8 @@ const EDITABLE_ROLES = new Set([
5
5
  Gtk.AccessibleRole.SPIN_BUTTON,
6
6
  ]);
7
7
  export const isEditable = (widget) => {
8
+ if (!(widget instanceof Gtk.Widget)) {
9
+ return false;
10
+ }
8
11
  return EDITABLE_ROLES.has(widget.getAccessibleRole());
9
12
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtkx/testing",
3
- "version": "0.15.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/ffi": "0.15.0",
40
- "@gtkx/react": "0.15.0"
39
+ "@gtkx/react": "0.17.1",
40
+ "@gtkx/ffi": "0.17.1"
41
41
  },
42
42
  "devDependencies": {
43
- "@types/react-reconciler": "^0.32.3",
43
+ "@types/react-reconciler": "^0.33.0",
44
44
  "react-reconciler": "^0.33.0",
45
- "@gtkx/vitest": "0.15.0"
45
+ "@gtkx/vitest": "0.17.1"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "tsc -b && cp ../../README.md .",