@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 +8 -7
- package/dist/fire-event.d.ts +13 -8
- package/dist/fire-event.js +16 -12
- package/dist/render.d.ts +6 -6
- package/dist/render.js +6 -6
- package/dist/user-event.d.ts +7 -0
- package/dist/user-event.js +29 -20
- package/package.json +4 -5
package/README.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
|
|
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
|
-
|
|
8
|
+
<strong>Build native GTK4 desktop applications with React and TypeScript.</strong>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
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
|
package/dist/fire-event.d.ts
CHANGED
|
@@ -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
|
|
18
|
-
* await fireEvent(widget, "
|
|
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:
|
|
28
|
+
export declare const fireEvent: (element: Gtk.Widget | Gtk.EventController, signalName: string, ...args: Value[]) => Promise<void>;
|
package/dist/fire-event.js
CHANGED
|
@@ -1,30 +1,34 @@
|
|
|
1
|
-
import {
|
|
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
|
|
18
|
-
* await fireEvent(widget, "
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
|
-
*
|
|
39
|
+
* await cleanup();
|
|
40
40
|
* });
|
|
41
41
|
*
|
|
42
42
|
* test("my test", async () => {
|
|
43
|
-
*
|
|
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
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
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
|
-
*
|
|
99
|
+
* await cleanup();
|
|
100
100
|
* });
|
|
101
101
|
*
|
|
102
102
|
* test("my test", async () => {
|
|
103
|
-
*
|
|
104
|
-
*
|
|
103
|
+
* await render(<MyComponent />);
|
|
104
|
+
* // ...
|
|
105
105
|
* });
|
|
106
106
|
* ```
|
|
107
107
|
*/
|
package/dist/user-event.d.ts
CHANGED
|
@@ -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.
|
package/dist/user-event.js
CHANGED
|
@@ -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
|
|
170
|
-
const
|
|
171
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
266
|
-
|
|
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
|
-
|
|
278
|
+
signalEmitv(pressedArgs, getSignalId(controller, "pressed"), 0, null);
|
|
270
279
|
}
|
|
271
280
|
else if (input === "[/MouseLeft]" || input === "up") {
|
|
272
|
-
|
|
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.
|
|
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.
|
|
40
|
-
"@gtkx/
|
|
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.
|
|
45
|
+
"@gtkx/vitest": "0.15.0"
|
|
47
46
|
},
|
|
48
47
|
"scripts": {
|
|
49
48
|
"build": "tsc -b && cp ../../README.md .",
|