@gtkx/testing 0.3.1 → 0.3.3

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
@@ -5,242 +5,138 @@
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 apps with React</strong>
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
12
  <a href="https://eugeniodepalo.github.io/gtkx">Documentation</a> ·
13
13
  <a href="#quick-start">Quick Start</a> ·
14
- <a href="#examples">Examples</a>
14
+ <a href="#examples">Examples</a> ·
15
+ <a href="#contributing">Contributing</a>
15
16
  </p>
16
17
 
17
18
  ---
18
19
 
19
- GTKX bridges React's component model with GTK4's native widget system. Write familiar React code and render it as native Linux desktop applications with full access to GTK4 widgets, signals, and styling.
20
+ GTKX lets you build native Linux desktop applications using React and TypeScript. Write familiar React code that renders as native GTK4 widgets—no Electron, no web views.
20
21
 
21
22
  ## Features
22
23
 
23
- - **React Components** — Use React hooks, state, and component patterns you already know
24
- - **Hot Module Replacement** — Edit your code and see changes instantly, powered by Vite
25
- - **Native Performance** — Direct FFI bindings to GTK4 via Rust and libffi
26
- - **CLI & Scaffolding** — Get started in seconds with `npx @gtkx/cli@latest create`
27
- - **CSS-in-JS Styling** — Emotion-style `css` template literals for GTK widgets
28
- - **Testing Library** — Familiar `screen`, `userEvent`, and query APIs for testing components
24
+ - **React** — Hooks, state, props, and components you already know
25
+ - **Hot Reload** — Edit code and see changes instantly via Vite
26
+ - **Native** — Direct FFI bindings to GTK4 via Rust and libffi
27
+ - **CLI** — `npx @gtkx/cli@latest create` scaffolds a ready-to-go project
28
+ - **CSS-in-JS** — Emotion-style `css` template literals for GTK styling
29
+ - **Testing** — Testing Library-style `screen`, `userEvent`, and queries
29
30
 
30
31
  ## Quick Start
31
32
 
32
- Create a new GTKX app with a single command:
33
-
34
- ```bash
35
- npx @gtkx/cli@latest create
36
- ```
37
-
38
- This launches an interactive wizard that sets up your project with TypeScript, your preferred package manager, and optional testing support.
39
-
40
- You can also pass options directly:
41
-
42
- ```bash
43
- npx @gtkx/cli@latest create my-app --app-id com.example.myapp --pm pnpm --testing vitest
44
- ```
45
-
46
- Then start developing with HMR:
47
-
48
33
  ```bash
34
+ npx @gtkx/cli@latest create my-app
49
35
  cd my-app
50
36
  npm run dev
51
37
  ```
52
38
 
53
- Edit your code and see changes instantly without restarting the app!
54
-
55
- ### Manual Setup
39
+ Edit your code and see changes instantly—no restart needed.
56
40
 
57
- Alternatively, install packages directly:
58
-
59
- ```bash
60
- npm install @gtkx/cli @gtkx/react @gtkx/ffi react
61
- npm install -D @types/react typescript
62
- ```
63
-
64
- Create your first app:
41
+ ### Example
65
42
 
66
43
  ```tsx
67
- // src/app.tsx
44
+ import { render, ApplicationWindow, Box, Button, Label, quit } from "@gtkx/react";
45
+ import { Orientation } from "@gtkx/ffi/gtk";
68
46
  import { useState } from "react";
69
- import * as Gtk from "@gtkx/ffi/gtk";
70
- import { ApplicationWindow, Box, Button, Label, quit } from "@gtkx/react";
71
47
 
72
- export default function App() {
48
+ const App = () => {
73
49
  const [count, setCount] = useState(0);
74
50
 
75
51
  return (
76
- <ApplicationWindow
77
- title="My App"
78
- defaultWidth={400}
79
- defaultHeight={300}
80
- onCloseRequest={quit}
81
- >
82
- <Box
83
- orientation={Gtk.Orientation.VERTICAL}
84
- spacing={12}
85
- marginStart={20}
86
- marginEnd={20}
87
- marginTop={20}
88
- marginBottom={20}
89
- >
52
+ <ApplicationWindow title="Counter" onCloseRequest={quit}>
53
+ <Box orientation={Orientation.VERTICAL} spacing={12} margin={20}>
90
54
  <Label.Root label={`Count: ${count}`} />
91
55
  <Button label="Increment" onClicked={() => setCount((c) => c + 1)} />
92
56
  </Box>
93
57
  </ApplicationWindow>
94
58
  );
95
- }
96
-
97
- export const appId = "org.example.MyApp";
98
- ```
99
-
100
- ```tsx
101
- // src/index.tsx
102
- import { render } from "@gtkx/react";
103
- import App, { appId } from "./app.js";
104
-
105
- render(<App />, appId);
106
- ```
59
+ };
107
60
 
108
- Run with HMR:
109
-
110
- ```bash
111
- npx gtkx dev src/app.tsx
112
- ```
113
-
114
- Or without HMR (production):
115
-
116
- ```bash
117
- npx tsc -b && node dist/index.js
61
+ render(<App />, "org.example.Counter");
118
62
  ```
119
63
 
120
64
  ## Styling
121
65
 
122
- Use `@gtkx/css` for CSS-in-JS styling:
123
-
124
66
  ```tsx
125
67
  import { css } from "@gtkx/css";
126
68
  import { Button } from "@gtkx/react";
127
69
 
128
- const primaryButton = css`
70
+ const primary = css`
129
71
  padding: 16px 32px;
130
72
  border-radius: 24px;
131
73
  background: linear-gradient(135deg, #3584e4, #9141ac);
132
74
  color: white;
133
- font-weight: bold;
134
75
  `;
135
76
 
136
- const MyButton = () => <Button label="Click me" cssClasses={[primaryButton]} />;
77
+ <Button label="Click me" cssClasses={[primary]} />
137
78
  ```
138
79
 
139
- GTK also provides built-in CSS classes like `suggested-action`, `destructive-action`, `card`, and `heading`.
80
+ GTK also provides built-in classes like `suggested-action`, `destructive-action`, `card`, and `heading`.
140
81
 
141
82
  ## Testing
142
83
 
143
- Use `@gtkx/testing` for Testing Library-style component tests:
144
-
145
84
  ```tsx
146
- import { cleanup, render, screen, userEvent, fireEvent } from "@gtkx/testing";
85
+ import { cleanup, render, screen, userEvent } from "@gtkx/testing";
147
86
  import { AccessibleRole } from "@gtkx/ffi/gtk";
148
- import { App } from "./app.js";
149
87
 
150
- // Clean up after each test
151
- afterEach(async () => {
152
- await cleanup();
153
- });
88
+ afterEach(() => cleanup());
154
89
 
155
- test("increments count when clicking button", async () => {
90
+ test("increments count", async () => {
156
91
  await render(<App />);
157
92
 
158
- const button = await screen.findByRole(AccessibleRole.BUTTON, {
159
- name: "Increment",
160
- });
93
+ const button = await screen.findByRole(AccessibleRole.BUTTON, { name: "Increment" });
161
94
  await userEvent.click(button);
162
95
 
163
96
  await screen.findByText("Count: 1");
164
97
  });
165
-
166
- test("can also use fireEvent for low-level signals", async () => {
167
- await render(<App />);
168
-
169
- const button = await screen.findByRole(AccessibleRole.BUTTON, {
170
- name: "Increment",
171
- });
172
- fireEvent(button, "clicked");
173
-
174
- await screen.findByText("Count: 1");
175
- });
176
98
  ```
177
99
 
178
- ### Available APIs
179
-
180
- **Queries** - Find elements in the rendered tree (all async):
181
-
182
- - `findBy*` / `findAllBy*` - Waits for element to appear
183
-
184
- Query types: `ByRole`, `ByText`, `ByLabelText`, `ByTestId`
185
-
186
- **User Interactions**:
100
+ Queries: `findByRole`, `findByText`, `findByLabelText`, `findByTestId`
187
101
 
188
- - `userEvent.click(element)` - Simulate click
189
- - `userEvent.dblClick(element)` - Simulate double click
190
- - `userEvent.activate(element)` - Activate element (e.g., press Enter in input)
191
- - `userEvent.type(element, text)` - Type text into input
192
- - `userEvent.clear(element)` - Clear input text
193
- - `userEvent.tab(element, options?)` - Simulate Tab navigation
194
- - `userEvent.selectOptions(element, values)` - Select options in ComboBox/ListBox
195
- - `userEvent.deselectOptions(element, values)` - Deselect options in ListBox
196
-
197
- **Low-level Events**:
198
-
199
- - `fireEvent(element, signalName, ...args)` - Emit any GTK signal with optional arguments
200
-
201
- **Utilities**:
202
-
203
- - `waitFor(callback)` - Wait for condition
204
- - `waitForElementToBeRemoved(element)` - Wait for element removal
102
+ User events: `click`, `dblClick`, `type`, `clear`, `tab`, `selectOptions`
205
103
 
206
104
  ## Examples
207
105
 
208
- ### GTK4 Demo
209
-
210
- A comprehensive showcase of GTK4 widgets and features:
106
+ | Example | Description |
107
+ | ------- | ----------- |
108
+ | [gtk4-demo](examples/gtk4-demo) | Widget showcase |
109
+ | [todo](examples/todo) | Todo app with tests |
211
110
 
212
111
  ```bash
213
- cd examples/gtk4-demo
214
- pnpm dev
215
- ```
216
-
217
- ### Todo App
218
-
219
- A todo app demonstrating `@gtkx/testing` with realistic component tests:
220
-
221
- ```bash
222
- cd examples/todo
223
- pnpm dev
224
- pnpm test
112
+ cd examples/gtk4-demo && pnpm dev
225
113
  ```
226
114
 
227
115
  ## Packages
228
116
 
229
- | Package | Description |
230
- | --------------------------------- | -------------------------------------------------- |
231
- | [@gtkx/cli](packages/cli) | CLI for creating and developing GTKX apps with HMR |
232
- | [@gtkx/react](packages/react) | React reconciler and JSX components |
233
- | [@gtkx/ffi](packages/ffi) | TypeScript FFI bindings for GTK4 |
234
- | [@gtkx/native](packages/native) | Rust native module for FFI bridge |
235
- | [@gtkx/css](packages/css) | CSS-in-JS styling for GTK widgets |
236
- | [@gtkx/testing](packages/testing) | Testing utilities for GTKX components |
237
- | [@gtkx/gir](packages/gir) | GObject Introspection parser for codegen |
117
+ | Package | Description |
118
+ | ------- | ----------- |
119
+ | [@gtkx/cli](packages/cli) | CLI with HMR dev server |
120
+ | [@gtkx/react](packages/react) | React reconciler and JSX components |
121
+ | [@gtkx/ffi](packages/ffi) | TypeScript bindings for GTK4/GLib/GIO |
122
+ | [@gtkx/native](packages/native) | Rust native module (libffi bridge) |
123
+ | [@gtkx/css](packages/css) | CSS-in-JS styling |
124
+ | [@gtkx/testing](packages/testing) | Testing utilities |
125
+ | [@gtkx/gir](packages/gir) | GObject Introspection parser |
238
126
 
239
127
  ## Requirements
240
128
 
241
129
  - Node.js 20+
242
- - GTK4
243
- - Linux (GTK4 is Linux-native)
130
+ - GTK4 (`gtk4-devel` on Fedora, `libgtk-4-dev` on Ubuntu)
131
+ - Linux
132
+
133
+ ## Contributing
134
+
135
+ We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
136
+
137
+ - [Report a bug](https://github.com/eugeniodepalo/gtkx/issues/new?template=bug_report.md)
138
+ - [Request a feature](https://github.com/eugeniodepalo/gtkx/issues/new?template=feature_request.md)
139
+ - [Read the Code of Conduct](CODE_OF_CONDUCT.md)
244
140
 
245
141
  ## License
246
142
 
@@ -9,9 +9,9 @@ import type { Arg } from "@gtkx/native";
9
9
  * @param args - Additional arguments to pass to the signal handlers
10
10
  *
11
11
  * @example
12
- * fireEvent(button, "clicked")
12
+ * await fireEvent(button, "clicked")
13
13
  *
14
14
  * @example
15
- * fireEvent(widget, "custom-signal", { type: { type: "int", size: 32 }, value: 42 })
15
+ * await fireEvent(widget, "custom-signal", { type: { type: "int", size: 32 }, value: 42 })
16
16
  */
17
- export declare const fireEvent: (element: Gtk.Widget, signalName: string, ...args: Arg[]) => void;
17
+ export declare const fireEvent: (element: Gtk.Widget, signalName: string, ...args: Arg[]) => Promise<void>;
@@ -1,4 +1,5 @@
1
1
  import { call } from "@gtkx/native";
2
+ import { tick } from "./timing.js";
2
3
  /**
3
4
  * Low-level utility to emit GTK signals on widgets. For common interactions
4
5
  * like clicking or typing, use `userEvent` instead.
@@ -8,11 +9,12 @@ import { call } from "@gtkx/native";
8
9
  * @param args - Additional arguments to pass to the signal handlers
9
10
  *
10
11
  * @example
11
- * fireEvent(button, "clicked")
12
+ * await fireEvent(button, "clicked")
12
13
  *
13
14
  * @example
14
- * fireEvent(widget, "custom-signal", { type: { type: "int", size: 32 }, value: 42 })
15
+ * await fireEvent(widget, "custom-signal", { type: { type: "int", size: 32 }, value: 42 })
15
16
  */
16
- export const fireEvent = (element, signalName, ...args) => {
17
+ export const fireEvent = async (element, signalName, ...args) => {
17
18
  call("libgobject-2.0.so.0", "g_signal_emit_by_name", [{ type: { type: "gobject" }, value: element.id }, { type: { type: "string" }, value: signalName }, ...args], { type: "undefined" });
19
+ await tick();
18
20
  };
package/dist/render.js CHANGED
@@ -1,12 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { getCurrentApp, getInterface, start, stop } from "@gtkx/ffi";
3
3
  import * as Gtk from "@gtkx/ffi/gtk";
4
- import { ApplicationWindow, reconciler } from "@gtkx/react";
4
+ import { ApplicationWindow, ROOT_NODE_CONTAINER, reconciler } from "@gtkx/react";
5
5
  import * as queries from "./queries.js";
6
6
  import { setScreenRoot } from "./screen.js";
7
7
  import { tick } from "./timing.js";
8
8
  import { hasLabel } from "./widget.js";
9
- const ROOT_NODE_CONTAINER = Symbol.for("ROOT_NODE_CONTAINER");
10
9
  const APP_ID = "com.gtkx.testing";
11
10
  let container = null;
12
11
  const getWidgetLabel = (widget) => {
package/dist/timing.d.ts CHANGED
@@ -1 +1,4 @@
1
+ /**
2
+ * Returns a promise that resolves on the next event loop tick.
3
+ */
1
4
  export declare const tick: () => Promise<void>;
package/dist/timing.js CHANGED
@@ -1 +1,4 @@
1
+ /**
2
+ * Returns a promise that resolves on the next event loop tick.
3
+ */
1
4
  export const tick = () => new Promise((resolve) => setTimeout(resolve, 0));
@@ -19,26 +19,20 @@ const click = async (element) => {
19
19
  const toggleButton = element;
20
20
  toggleButton.setActive(!toggleButton.getActive());
21
21
  }
22
- // Note: setActive() automatically emits the "toggled" signal, so we don't need to emit it manually
22
+ await tick();
23
23
  }
24
24
  else {
25
- fireEvent(element, "clicked");
25
+ await fireEvent(element, "clicked");
26
26
  }
27
- await tick();
28
27
  };
29
28
  const dblClick = async (element) => {
30
- fireEvent(element, "clicked");
31
- await tick();
32
- fireEvent(element, "clicked");
33
- await tick();
29
+ await fireEvent(element, "clicked");
30
+ await fireEvent(element, "clicked");
34
31
  };
35
32
  const tripleClick = async (element) => {
36
- fireEvent(element, "clicked");
37
- await tick();
38
- fireEvent(element, "clicked");
39
- await tick();
40
- fireEvent(element, "clicked");
41
- await tick();
33
+ await fireEvent(element, "clicked");
34
+ await fireEvent(element, "clicked");
35
+ await fireEvent(element, "clicked");
42
36
  };
43
37
  const activate = async (element) => {
44
38
  element.activate();
package/dist/widget.d.ts CHANGED
@@ -1,3 +1,9 @@
1
1
  import type * as Gtk from "@gtkx/ffi/gtk";
2
+ /**
3
+ * Checks if a widget has an editable accessible role (text box, search box, or spin button).
4
+ */
2
5
  export declare const isEditable: (widget: Gtk.Widget) => boolean;
6
+ /**
7
+ * Checks if a widget has an accessible role that supports labels.
8
+ */
3
9
  export declare const hasLabel: (widget: Gtk.Widget) => boolean;
package/dist/widget.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { getInterface } from "@gtkx/ffi";
2
2
  import { Accessible, AccessibleRole } from "@gtkx/ffi/gtk";
3
3
  const EDITABLE_ROLES = new Set([AccessibleRole.TEXT_BOX, AccessibleRole.SEARCH_BOX, AccessibleRole.SPIN_BUTTON]);
4
+ /**
5
+ * Checks if a widget has an editable accessible role (text box, search box, or spin button).
6
+ */
4
7
  export const isEditable = (widget) => {
5
8
  const role = getInterface(widget, Accessible).getAccessibleRole();
6
9
  return EDITABLE_ROLES.has(role);
@@ -15,6 +18,9 @@ const LABEL_ROLES = new Set([
15
18
  AccessibleRole.MENU_ITEM_CHECKBOX,
16
19
  AccessibleRole.MENU_ITEM_RADIO,
17
20
  ]);
21
+ /**
22
+ * Checks if a widget has an accessible role that supports labels.
23
+ */
18
24
  export const hasLabel = (widget) => {
19
25
  const role = getInterface(widget, Accessible).getAccessibleRole();
20
26
  return LABEL_ROLES.has(role);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtkx/testing",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Testing utilities for GTKX applications",
5
5
  "keywords": [
6
6
  "gtk",
@@ -32,9 +32,9 @@
32
32
  "dist"
33
33
  ],
34
34
  "dependencies": {
35
- "@gtkx/ffi": "0.3.1",
36
- "@gtkx/native": "0.3.1",
37
- "@gtkx/react": "0.3.1"
35
+ "@gtkx/ffi": "0.3.3",
36
+ "@gtkx/react": "0.3.3",
37
+ "@gtkx/native": "0.3.3"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "tsc -b && cp ../../README.md .",