@gtkx/testing 0.13.3 → 0.15.0

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
@@ -1,18 +1,18 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/eugeniodepalo/gtkx/main/logo.svg" alt="GTKX" width="60" height="60">
2
+ <img src="https://raw.githubusercontent.com/eugeniodepalo/gtkx/main/logo.svg" alt="GTKX" width="60" height="60">
3
3
  </p>
4
4
 
5
5
  <h1 align="center">GTKX</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>Build native GTK4 desktop applications with React and TypeScript.</strong>
8
+ <strong>Build native GTK4 desktop applications with React and TypeScript.</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <a href="https://www.npmjs.com/package/@gtkx/react"><img src="https://img.shields.io/npm/v/@gtkx/react.svg" alt="npm version"></a>
13
- <a href="https://github.com/eugeniodepalo/gtkx/actions"><img src="https://img.shields.io/github/actions/workflow/status/eugeniodepalo/gtkx/ci.yml" alt="CI"></a>
14
- <a href="https://github.com/eugeniodepalo/gtkx/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MPL--2.0-blue.svg" alt="License"></a>
15
- <a href="https://github.com/eugeniodepalo/gtkx/discussions"><img src="https://img.shields.io/badge/discussions-GitHub-blue" alt="GitHub Discussions"></a>
12
+ <a href="https://www.npmjs.com/package/@gtkx/react"><img src="https://img.shields.io/npm/v/@gtkx/react.svg" alt="npm version"></a>
13
+ <a href="https://github.com/eugeniodepalo/gtkx/actions"><img src="https://img.shields.io/github/actions/workflow/status/eugeniodepalo/gtkx/ci.yml" alt="CI"></a>
14
+ <a href="https://github.com/eugeniodepalo/gtkx/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MPL--2.0-blue.svg" alt="License"></a>
15
+ <a href="https://github.com/eugeniodepalo/gtkx/discussions"><img src="https://img.shields.io/badge/discussions-GitHub-blue" alt="GitHub Discussions"></a>
16
16
  </p>
17
17
 
18
18
  ---
@@ -83,7 +83,8 @@ Explore complete applications in the [`examples/`](./examples) directory:
83
83
  - **[gtk-demo](./examples/gtk-demo)** — Full replica of the official GTK demo app
84
84
  - **[hello-world](./examples/hello-world)** — Minimal application showing a counter
85
85
  - **[todo](./examples/todo)** — Full-featured todo application with Adwaita styling and testing
86
- - **[x-showcase](./examples/x-showcase)** — Showcase of all x.* virtual components
86
+ - **[x-showcase](./examples/x-showcase)** — Showcase of all x.\* virtual components
87
+ - **[browser](./examples/browser)** — Simple browser using WebKitWebView
87
88
  - **[deploying](./examples/deploying)** — Example of packaging and distributing a GTKX app
88
89
 
89
90
  ## Documentation
@@ -1,23 +1,28 @@
1
+ import { Value } from "@gtkx/ffi/gobject";
1
2
  import type * as Gtk from "@gtkx/ffi/gtk";
2
- import type { Arg } from "@gtkx/native";
3
3
  /**
4
- * Emits a GTK signal on a widget.
4
+ * Emits a GTK signal on a widget or event controller.
5
5
  *
6
6
  * Low-level utility for triggering signals directly. Prefer {@link userEvent}
7
7
  * for common interactions like clicking and typing.
8
8
  *
9
- * @param element - The widget to emit the signal on
10
- * @param signalName - GTK signal name (e.g., "clicked", "activate")
11
- * @param args - Additional signal arguments
9
+ * @param element - The widget or event controller to emit the signal on
10
+ * @param signalName - GTK signal name (e.g., "clicked", "activate", "drag-begin")
11
+ * @param args - Additional signal arguments as GValues
12
12
  *
13
13
  * @example
14
14
  * ```tsx
15
15
  * import { fireEvent } from "@gtkx/testing";
16
+ * import { Value } from "@gtkx/ffi/gobject";
16
17
  *
17
- * // Emit custom signal
18
- * await fireEvent(widget, "my-custom-signal");
18
+ * // Emit signal on widget
19
+ * await fireEvent(widget, "clicked");
20
+ *
21
+ * // Emit signal on gesture controller
22
+ * const gesture = widget.observeControllers().getObject(0) as Gtk.GestureDrag;
23
+ * await fireEvent(gesture, "drag-begin", Value.newFromDouble(100), Value.newFromDouble(100));
19
24
  * ```
20
25
  *
21
26
  * @see {@link userEvent} for high-level user interactions
22
27
  */
23
- export declare const fireEvent: (element: Gtk.Widget, signalName: string, ...args: Arg[]) => Promise<void>;
28
+ export declare const fireEvent: (element: Gtk.Widget | Gtk.EventController, signalName: string, ...args: Value[]) => Promise<void>;
@@ -1,30 +1,34 @@
1
- import { call } from "@gtkx/native";
1
+ import { signalEmitv, signalLookup, typeFromName, Value } from "@gtkx/ffi/gobject";
2
2
  import { tick } from "./timing.js";
3
3
  /**
4
- * Emits a GTK signal on a widget.
4
+ * Emits a GTK signal on a widget or event controller.
5
5
  *
6
6
  * Low-level utility for triggering signals directly. Prefer {@link userEvent}
7
7
  * for common interactions like clicking and typing.
8
8
  *
9
- * @param element - The widget to emit the signal on
10
- * @param signalName - GTK signal name (e.g., "clicked", "activate")
11
- * @param args - Additional signal arguments
9
+ * @param element - The widget or event controller to emit the signal on
10
+ * @param signalName - GTK signal name (e.g., "clicked", "activate", "drag-begin")
11
+ * @param args - Additional signal arguments as GValues
12
12
  *
13
13
  * @example
14
14
  * ```tsx
15
15
  * import { fireEvent } from "@gtkx/testing";
16
+ * import { Value } from "@gtkx/ffi/gobject";
16
17
  *
17
- * // Emit custom signal
18
- * await fireEvent(widget, "my-custom-signal");
18
+ * // Emit signal on widget
19
+ * await fireEvent(widget, "clicked");
20
+ *
21
+ * // Emit signal on gesture controller
22
+ * const gesture = widget.observeControllers().getObject(0) as Gtk.GestureDrag;
23
+ * await fireEvent(gesture, "drag-begin", Value.newFromDouble(100), Value.newFromDouble(100));
19
24
  * ```
20
25
  *
21
26
  * @see {@link userEvent} for high-level user interactions
22
27
  */
23
28
  export const fireEvent = async (element, signalName, ...args) => {
24
- call("libgobject-2.0.so.0", "g_signal_emit_by_name", [
25
- { type: { type: "gobject", ownership: "borrowed" }, value: element.handle },
26
- { type: { type: "string", ownership: "borrowed" }, value: signalName },
27
- ...args,
28
- ], { type: "undefined" });
29
+ const gtype = typeFromName(element.constructor.glibTypeName);
30
+ const signalId = signalLookup(signalName, gtype);
31
+ const instanceValue = Value.newFromObject(element);
32
+ signalEmitv([instanceValue, ...args], signalId, 0, null);
29
33
  await tick();
30
34
  };
package/dist/render.d.ts CHANGED
@@ -15,9 +15,9 @@ import type { RenderOptions, RenderResult } from "./types.js";
15
15
  * import { render, screen } from "@gtkx/testing";
16
16
  *
17
17
  * test("button click", async () => {
18
- * await render(<MyButton />);
19
- * const button = await screen.findByRole(Gtk.AccessibleRole.BUTTON);
20
- * await userEvent.click(button);
18
+ * await render(<MyButton />);
19
+ * const button = await screen.findByRole(Gtk.AccessibleRole.BUTTON);
20
+ * await userEvent.click(button);
21
21
  * });
22
22
  * ```
23
23
  *
@@ -36,12 +36,12 @@ export declare const render: (element: ReactNode, options?: RenderOptions) => Pr
36
36
  * import { render, cleanup } from "@gtkx/testing";
37
37
  *
38
38
  * afterEach(async () => {
39
- * await cleanup();
39
+ * await cleanup();
40
40
  * });
41
41
  *
42
42
  * test("my test", async () => {
43
- * await render(<MyComponent />);
44
- * // ...
43
+ * await render(<MyComponent />);
44
+ * // ...
45
45
  * });
46
46
  * ```
47
47
  */
package/dist/render.js CHANGED
@@ -55,9 +55,9 @@ const wrapElement = (element, wrapper = true) => {
55
55
  * import { render, screen } from "@gtkx/testing";
56
56
  *
57
57
  * test("button click", async () => {
58
- * await render(<MyButton />);
59
- * const button = await screen.findByRole(Gtk.AccessibleRole.BUTTON);
60
- * await userEvent.click(button);
58
+ * await render(<MyButton />);
59
+ * const button = await screen.findByRole(Gtk.AccessibleRole.BUTTON);
60
+ * await userEvent.click(button);
61
61
  * });
62
62
  * ```
63
63
  *
@@ -96,12 +96,12 @@ export const render = async (element, options) => {
96
96
  * import { render, cleanup } from "@gtkx/testing";
97
97
  *
98
98
  * afterEach(async () => {
99
- * await cleanup();
99
+ * await cleanup();
100
100
  * });
101
101
  *
102
102
  * test("my test", async () => {
103
- * await render(<MyComponent />);
104
- * // ...
103
+ * await render(<MyComponent />);
104
+ * // ...
105
105
  * });
106
106
  * ```
107
107
  */
@@ -6,6 +6,13 @@ export type TabOptions = {
6
6
  /** Navigate backwards (Shift+Tab) instead of forwards */
7
7
  shift?: boolean;
8
8
  };
9
+ /**
10
+ * Pointer input actions for simulating mouse interactions.
11
+ *
12
+ * - `"click"` or `"[MouseLeft]"`: Full click (press + release)
13
+ * - `"down"` or `"[MouseLeft>]"`: Press and hold
14
+ * - `"up"` or `"[/MouseLeft]"`: Release
15
+ */
9
16
  export type PointerInput = "click" | "down" | "up" | "[MouseLeft]" | "[MouseLeft>]" | "[/MouseLeft]";
10
17
  /**
11
18
  * User interaction utilities for testing.
@@ -1,7 +1,7 @@
1
1
  import { getNativeObject } from "@gtkx/ffi";
2
2
  import * as Gdk from "@gtkx/ffi/gdk";
3
+ import { signalEmitv, signalLookup, typeFromName, Value } from "@gtkx/ffi/gobject";
3
4
  import * as Gtk from "@gtkx/ffi/gtk";
4
- import { call } from "@gtkx/native";
5
5
  import { fireEvent } from "./fire-event.js";
6
6
  import { tick } from "./timing.js";
7
7
  import { isEditable } from "./widget.js";
@@ -166,27 +166,18 @@ const getOrCreateController = (element, controllerType) => {
166
166
  element.addController(controller);
167
167
  return controller;
168
168
  };
169
- const emitSignal = (target, signalName, ...args) => {
170
- const signalArgs = args.map((arg) => {
171
- if (arg.type === "float") {
172
- return { type: { type: "float", size: 64 }, value: arg.value };
173
- }
174
- return { type: { type: "int", size: 32, unsigned: true }, value: arg.value };
175
- });
176
- call("libgobject-2.0.so.0", "g_signal_emit_by_name", [
177
- { type: { type: "gobject", ownership: "borrowed" }, value: target.handle },
178
- { type: { type: "string", ownership: "borrowed" }, value: signalName },
179
- ...signalArgs,
180
- ], { type: "undefined" });
169
+ const getSignalId = (target, signalName) => {
170
+ const gtype = typeFromName(target.constructor.glibTypeName);
171
+ return signalLookup(signalName, gtype);
181
172
  };
182
173
  const hover = async (element) => {
183
174
  const controller = getOrCreateController(element, Gtk.EventControllerMotion);
184
- emitSignal(controller, "enter", { type: "float", value: 0 }, { type: "float", value: 0 });
175
+ signalEmitv([Value.newFromObject(controller), Value.newFromDouble(0), Value.newFromDouble(0)], getSignalId(controller, "enter"), 0, null);
185
176
  await tick();
186
177
  };
187
178
  const unhover = async (element) => {
188
179
  const controller = getOrCreateController(element, Gtk.EventControllerMotion);
189
- emitSignal(controller, "leave");
180
+ signalEmitv([Value.newFromObject(controller)], getSignalId(controller, "leave"), 0, null);
190
181
  await tick();
191
182
  };
192
183
  const KEY_MAP = {
@@ -252,7 +243,13 @@ const keyboard = async (element, input) => {
252
243
  const actions = parseKeyboardInput(input);
253
244
  for (const action of actions) {
254
245
  const signalName = action.press ? "key-pressed" : "key-released";
255
- emitSignal(controller, signalName, { type: "int", value: action.keyval }, { type: "int", value: 0 }, { type: "int", value: 0 });
246
+ const returnValue = Value.newFromBoolean(false);
247
+ signalEmitv([
248
+ Value.newFromObject(controller),
249
+ Value.newFromUint(action.keyval),
250
+ Value.newFromUint(0),
251
+ Value.newFromUint(0),
252
+ ], getSignalId(controller, signalName), 0, returnValue);
256
253
  if (action.press && action.keyval === Gdk.KEY_Return && isEditable(element)) {
257
254
  await fireEvent(element, "activate");
258
255
  }
@@ -261,15 +258,27 @@ const keyboard = async (element, input) => {
261
258
  };
262
259
  const pointer = async (element, input) => {
263
260
  const controller = getOrCreateController(element, Gtk.GestureClick);
261
+ const pressedArgs = [
262
+ Value.newFromObject(controller),
263
+ Value.newFromInt(1),
264
+ Value.newFromDouble(0),
265
+ Value.newFromDouble(0),
266
+ ];
267
+ const releasedArgs = [
268
+ Value.newFromObject(controller),
269
+ Value.newFromInt(1),
270
+ Value.newFromDouble(0),
271
+ Value.newFromDouble(0),
272
+ ];
264
273
  if (input === "[MouseLeft]" || input === "click") {
265
- emitSignal(controller, "pressed", { type: "int", value: 1 }, { type: "float", value: 0 }, { type: "float", value: 0 });
266
- emitSignal(controller, "released", { type: "int", value: 1 }, { type: "float", value: 0 }, { type: "float", value: 0 });
274
+ signalEmitv(pressedArgs, getSignalId(controller, "pressed"), 0, null);
275
+ signalEmitv(releasedArgs, getSignalId(controller, "released"), 0, null);
267
276
  }
268
277
  else if (input === "[MouseLeft>]" || input === "down") {
269
- emitSignal(controller, "pressed", { type: "int", value: 1 }, { type: "float", value: 0 }, { type: "float", value: 0 });
278
+ signalEmitv(pressedArgs, getSignalId(controller, "pressed"), 0, null);
270
279
  }
271
280
  else if (input === "[/MouseLeft]" || input === "up") {
272
- emitSignal(controller, "released", { type: "int", value: 1 }, { type: "float", value: 0 }, { type: "float", value: 0 });
281
+ signalEmitv(releasedArgs, getSignalId(controller, "released"), 0, null);
273
282
  }
274
283
  await tick();
275
284
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtkx/testing",
3
- "version": "0.13.3",
3
+ "version": "0.15.0",
4
4
  "description": "Testing utilities for GTKX applications",
5
5
  "keywords": [
6
6
  "gtkx",
@@ -36,14 +36,13 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@gtkx/ffi": "0.13.3",
40
- "@gtkx/native": "0.13.3",
41
- "@gtkx/react": "0.13.3"
39
+ "@gtkx/ffi": "0.15.0",
40
+ "@gtkx/react": "0.15.0"
42
41
  },
43
42
  "devDependencies": {
44
43
  "@types/react-reconciler": "^0.32.3",
45
44
  "react-reconciler": "^0.33.0",
46
- "@gtkx/vitest": "0.13.3"
45
+ "@gtkx/vitest": "0.15.0"
47
46
  },
48
47
  "scripts": {
49
48
  "build": "tsc -b && cp ../../README.md .",