@gtkx/testing 0.9.0 → 0.9.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 +4 -14
- package/dist/queries.js +9 -9
- package/dist/render.js +4 -4
- package/dist/screen.d.ts +0 -4
- package/dist/screen.js +0 -4
- package/dist/timing.d.ts +0 -3
- package/dist/timing.js +0 -3
- package/dist/traversal.d.ts +0 -6
- package/dist/traversal.js +0 -6
- package/dist/user-event.js +9 -9
- package/dist/widget.d.ts +0 -6
- package/dist/widget.js +3 -9
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ GTKX lets you build native Linux desktop applications using React and TypeScript
|
|
|
22
22
|
## Features
|
|
23
23
|
|
|
24
24
|
- **React** — Hooks, state, props, and components you already know
|
|
25
|
-
- **
|
|
25
|
+
- **HMR** — Edit code and see changes instantly via Vite
|
|
26
26
|
- **Native** — Direct FFI bindings to GTK4 via Rust and libffi
|
|
27
27
|
- **CLI** — `npx @gtkx/cli@latest create` scaffolds a ready-to-go project
|
|
28
28
|
- **CSS-in-JS** — Emotion-style `css` template literals for GTK styling
|
|
@@ -41,14 +41,7 @@ Edit your code and see changes instantly—no restart needed.
|
|
|
41
41
|
### Example
|
|
42
42
|
|
|
43
43
|
```tsx
|
|
44
|
-
import {
|
|
45
|
-
render,
|
|
46
|
-
ApplicationWindow,
|
|
47
|
-
Box,
|
|
48
|
-
Button,
|
|
49
|
-
Label,
|
|
50
|
-
quit,
|
|
51
|
-
} from "@gtkx/react";
|
|
44
|
+
import { render, ApplicationWindow, Box, Button, quit } from "@gtkx/react";
|
|
52
45
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
53
46
|
import { useState } from "react";
|
|
54
47
|
|
|
@@ -58,7 +51,7 @@ const App = () => {
|
|
|
58
51
|
return (
|
|
59
52
|
<ApplicationWindow title="Counter" onCloseRequest={quit}>
|
|
60
53
|
<Box orientation={Gtk.Orientation.VERTICAL} spacing={12}>
|
|
61
|
-
|
|
54
|
+
{`Count: ${count}`}
|
|
62
55
|
<Button label="Increment" onClicked={() => setCount((c) => c + 1)} />
|
|
63
56
|
</Box>
|
|
64
57
|
</ApplicationWindow>
|
|
@@ -84,8 +77,6 @@ const primary = css`
|
|
|
84
77
|
<Button label="Click me" cssClasses={[primary]} />;
|
|
85
78
|
```
|
|
86
79
|
|
|
87
|
-
GTK also provides built-in classes like `suggested-action`, `destructive-action`, `card`, and `heading`.
|
|
88
|
-
|
|
89
80
|
## Testing
|
|
90
81
|
|
|
91
82
|
```tsx
|
|
@@ -109,9 +100,8 @@ test("increments count", async () => {
|
|
|
109
100
|
|
|
110
101
|
## Requirements
|
|
111
102
|
|
|
112
|
-
- Node.js 20+
|
|
103
|
+
- Node.js 20+ (Deno support experimental)
|
|
113
104
|
- GTK4 Runtime (`gtk4` on Fedora, `libgtk-4-1` on Ubuntu)
|
|
114
|
-
- Linux
|
|
115
105
|
|
|
116
106
|
## Contributing
|
|
117
107
|
|
package/dist/queries.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getNativeObject } from "@gtkx/ffi";
|
|
2
2
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
3
3
|
import { findAll } from "./traversal.js";
|
|
4
4
|
import { waitFor } from "./wait-for.js";
|
|
@@ -50,13 +50,13 @@ const ROLES_WITH_INTERNAL_LABELS = new Set([
|
|
|
50
50
|
Gtk.AccessibleRole.LINK,
|
|
51
51
|
]);
|
|
52
52
|
const isInternalLabel = (widget) => {
|
|
53
|
-
const accessible =
|
|
53
|
+
const accessible = getNativeObject(widget.id, Gtk.Accessible);
|
|
54
54
|
if (!accessible || accessible.getAccessibleRole() !== Gtk.AccessibleRole.LABEL)
|
|
55
55
|
return false;
|
|
56
56
|
const parent = widget.getParent();
|
|
57
57
|
if (!parent)
|
|
58
58
|
return false;
|
|
59
|
-
const parentAccessible =
|
|
59
|
+
const parentAccessible = getNativeObject(parent.id, Gtk.Accessible);
|
|
60
60
|
if (!parentAccessible)
|
|
61
61
|
return false;
|
|
62
62
|
return ROLES_WITH_INTERNAL_LABELS.has(parentAccessible.getAccessibleRole());
|
|
@@ -70,7 +70,7 @@ const collectChildLabels = (widget) => {
|
|
|
70
70
|
const labels = [];
|
|
71
71
|
let child = widget.getFirstChild();
|
|
72
72
|
while (child) {
|
|
73
|
-
const childAccessible =
|
|
73
|
+
const childAccessible = getNativeObject(child.id, Gtk.Accessible);
|
|
74
74
|
if (childAccessible?.getAccessibleRole() === Gtk.AccessibleRole.LABEL) {
|
|
75
75
|
const labelText = getLabelText(child);
|
|
76
76
|
if (labelText)
|
|
@@ -84,7 +84,7 @@ const collectChildLabels = (widget) => {
|
|
|
84
84
|
const getWidgetText = (widget) => {
|
|
85
85
|
if (isInternalLabel(widget))
|
|
86
86
|
return null;
|
|
87
|
-
const role =
|
|
87
|
+
const role = getNativeObject(widget.id, Gtk.Accessible)?.getAccessibleRole();
|
|
88
88
|
if (role === undefined)
|
|
89
89
|
return null;
|
|
90
90
|
switch (role) {
|
|
@@ -109,7 +109,7 @@ const getWidgetText = (widget) => {
|
|
|
109
109
|
case Gtk.AccessibleRole.TEXT_BOX:
|
|
110
110
|
case Gtk.AccessibleRole.SEARCH_BOX:
|
|
111
111
|
case Gtk.AccessibleRole.SPIN_BUTTON:
|
|
112
|
-
return
|
|
112
|
+
return getNativeObject(widget.id, Gtk.Editable)?.getText() ?? null;
|
|
113
113
|
case Gtk.AccessibleRole.GROUP:
|
|
114
114
|
return widget.getLabel?.() ?? null;
|
|
115
115
|
case Gtk.AccessibleRole.WINDOW:
|
|
@@ -135,7 +135,7 @@ const getWidgetTestId = (widget) => {
|
|
|
135
135
|
return widget.getName();
|
|
136
136
|
};
|
|
137
137
|
const getWidgetCheckedState = (widget) => {
|
|
138
|
-
const accessible =
|
|
138
|
+
const accessible = getNativeObject(widget.id, Gtk.Accessible);
|
|
139
139
|
if (!accessible)
|
|
140
140
|
return undefined;
|
|
141
141
|
const role = accessible.getAccessibleRole();
|
|
@@ -152,7 +152,7 @@ const getWidgetCheckedState = (widget) => {
|
|
|
152
152
|
}
|
|
153
153
|
};
|
|
154
154
|
const getWidgetExpandedState = (widget) => {
|
|
155
|
-
const accessible =
|
|
155
|
+
const accessible = getNativeObject(widget.id, Gtk.Accessible);
|
|
156
156
|
if (!accessible)
|
|
157
157
|
return undefined;
|
|
158
158
|
const role = accessible.getAccessibleRole();
|
|
@@ -203,7 +203,7 @@ const formatByRoleError = (role, options) => {
|
|
|
203
203
|
};
|
|
204
204
|
const getAllByRole = (container, role, options) => {
|
|
205
205
|
const matches = findAll(container, (node) => {
|
|
206
|
-
const accessible =
|
|
206
|
+
const accessible = getNativeObject(node.id, Gtk.Accessible);
|
|
207
207
|
if (!accessible || accessible.getAccessibleRole() !== role)
|
|
208
208
|
return false;
|
|
209
209
|
return matchByRoleOptions(node, options);
|
package/dist/render.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { getApplication, getNativeObject, start, stop } from "@gtkx/ffi";
|
|
3
3
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
4
4
|
import { ApplicationWindow, ROOT_NODE_CONTAINER, reconciler } from "@gtkx/react";
|
|
5
5
|
import * as queries from "./queries.js";
|
|
@@ -11,7 +11,7 @@ let container = null;
|
|
|
11
11
|
const getWidgetLabel = (widget) => {
|
|
12
12
|
if (!hasLabel(widget))
|
|
13
13
|
return null;
|
|
14
|
-
const accessible =
|
|
14
|
+
const accessible = getNativeObject(widget.id, Gtk.Accessible);
|
|
15
15
|
if (!accessible)
|
|
16
16
|
return null;
|
|
17
17
|
const role = accessible.getAccessibleRole();
|
|
@@ -22,7 +22,7 @@ const getWidgetLabel = (widget) => {
|
|
|
22
22
|
};
|
|
23
23
|
const printWidgetTree = (root, indent = 0) => {
|
|
24
24
|
const prefix = " ".repeat(indent);
|
|
25
|
-
const accessibleRole =
|
|
25
|
+
const accessibleRole = getNativeObject(root.id, Gtk.Accessible)?.getAccessibleRole();
|
|
26
26
|
const role = accessibleRole !== undefined ? (Gtk.AccessibleRole[accessibleRole] ?? "UNKNOWN") : "UNKNOWN";
|
|
27
27
|
const labelText = getWidgetLabel(root);
|
|
28
28
|
const label = labelText ? ` label="${labelText}"` : "";
|
|
@@ -96,7 +96,7 @@ export const render = async (element, options) => {
|
|
|
96
96
|
*/
|
|
97
97
|
export const cleanup = async () => {
|
|
98
98
|
if (container) {
|
|
99
|
-
const app =
|
|
99
|
+
const app = getApplication();
|
|
100
100
|
const instance = reconciler.getInstance();
|
|
101
101
|
await update(instance, null, container);
|
|
102
102
|
for (const window of app.getWindows()) {
|
package/dist/screen.d.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import type * as Gtk from "@gtkx/ffi/gtk";
|
|
2
2
|
import type { ByRoleOptions, TextMatch, TextMatchOptions } from "./types.js";
|
|
3
|
-
/**
|
|
4
|
-
* Sets the root application for screen queries. Called internally by render().
|
|
5
|
-
* @param root - The GTK application to use as query root, or null to clear
|
|
6
|
-
*/
|
|
7
3
|
export declare const setScreenRoot: (root: Gtk.Application | null) => void;
|
|
8
4
|
/**
|
|
9
5
|
* Global screen object providing query methods bound to the current render root.
|
package/dist/screen.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import * as queries from "./queries.js";
|
|
2
2
|
let currentRoot = null;
|
|
3
|
-
/**
|
|
4
|
-
* Sets the root application for screen queries. Called internally by render().
|
|
5
|
-
* @param root - The GTK application to use as query root, or null to clear
|
|
6
|
-
*/
|
|
7
3
|
export const setScreenRoot = (root) => {
|
|
8
4
|
currentRoot = root;
|
|
9
5
|
};
|
package/dist/timing.d.ts
CHANGED
package/dist/timing.js
CHANGED
package/dist/traversal.d.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
import type * as Gtk from "@gtkx/ffi/gtk";
|
|
2
2
|
type Container = Gtk.Application | Gtk.Widget;
|
|
3
|
-
/**
|
|
4
|
-
* Traverses the widget tree and returns all widgets matching the predicate.
|
|
5
|
-
* @param container - The application or widget to search within
|
|
6
|
-
* @param predicate - Function that returns true for matching widgets
|
|
7
|
-
* @returns Array of widgets that match the predicate
|
|
8
|
-
*/
|
|
9
3
|
export declare const findAll: (container: Container, predicate: (node: Gtk.Widget) => boolean) => Gtk.Widget[];
|
|
10
4
|
export {};
|
package/dist/traversal.js
CHANGED
|
@@ -21,12 +21,6 @@ const traverse = function* (container) {
|
|
|
21
21
|
yield* traverseWidgetTree(container);
|
|
22
22
|
}
|
|
23
23
|
};
|
|
24
|
-
/**
|
|
25
|
-
* Traverses the widget tree and returns all widgets matching the predicate.
|
|
26
|
-
* @param container - The application or widget to search within
|
|
27
|
-
* @param predicate - Function that returns true for matching widgets
|
|
28
|
-
* @returns Array of widgets that match the predicate
|
|
29
|
-
*/
|
|
30
24
|
export const findAll = (container, predicate) => {
|
|
31
25
|
const results = [];
|
|
32
26
|
for (const node of traverse(container)) {
|
package/dist/user-event.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getNativeObject } from "@gtkx/ffi";
|
|
2
2
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
3
3
|
import { fireEvent } from "./fire-event.js";
|
|
4
4
|
import { tick } from "./timing.js";
|
|
@@ -10,14 +10,14 @@ const TOGGLEABLE_ROLES = new Set([
|
|
|
10
10
|
Gtk.AccessibleRole.SWITCH,
|
|
11
11
|
]);
|
|
12
12
|
const isToggleable = (widget) => {
|
|
13
|
-
const accessible =
|
|
13
|
+
const accessible = getNativeObject(widget.id, Gtk.Accessible);
|
|
14
14
|
if (!accessible)
|
|
15
15
|
return false;
|
|
16
16
|
return TOGGLEABLE_ROLES.has(accessible.getAccessibleRole());
|
|
17
17
|
};
|
|
18
18
|
const click = async (element) => {
|
|
19
19
|
if (isToggleable(element)) {
|
|
20
|
-
const role =
|
|
20
|
+
const role = getNativeObject(element.id, Gtk.Accessible)?.getAccessibleRole();
|
|
21
21
|
if (role === Gtk.AccessibleRole.CHECKBOX || role === Gtk.AccessibleRole.RADIO) {
|
|
22
22
|
const checkButton = element;
|
|
23
23
|
checkButton.setActive(!checkButton.getActive());
|
|
@@ -53,7 +53,7 @@ const tab = async (element, options) => {
|
|
|
53
53
|
const direction = options?.shift ? Gtk.DirectionType.TAB_BACKWARD : Gtk.DirectionType.TAB_FORWARD;
|
|
54
54
|
const root = element.getRoot();
|
|
55
55
|
if (root) {
|
|
56
|
-
|
|
56
|
+
getNativeObject(root.id).childFocus(direction);
|
|
57
57
|
}
|
|
58
58
|
await tick();
|
|
59
59
|
};
|
|
@@ -61,7 +61,7 @@ const type = async (element, text) => {
|
|
|
61
61
|
if (!isEditable(element)) {
|
|
62
62
|
throw new Error("Cannot type into element: element is not editable (TEXT_BOX, SEARCH_BOX, or SPIN_BUTTON)");
|
|
63
63
|
}
|
|
64
|
-
const editable =
|
|
64
|
+
const editable = getNativeObject(element.id, Gtk.Editable);
|
|
65
65
|
if (!editable)
|
|
66
66
|
return;
|
|
67
67
|
const currentText = editable.getText();
|
|
@@ -72,12 +72,12 @@ const clear = async (element) => {
|
|
|
72
72
|
if (!isEditable(element)) {
|
|
73
73
|
throw new Error("Cannot clear element: element is not editable (TEXT_BOX, SEARCH_BOX, or SPIN_BUTTON)");
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
getNativeObject(element.id, Gtk.Editable)?.setText("");
|
|
76
76
|
await tick();
|
|
77
77
|
};
|
|
78
78
|
const SELECTABLE_ROLES = new Set([Gtk.AccessibleRole.COMBO_BOX, Gtk.AccessibleRole.LIST]);
|
|
79
79
|
const isSelectable = (widget) => {
|
|
80
|
-
const accessible =
|
|
80
|
+
const accessible = getNativeObject(widget.id, Gtk.Accessible);
|
|
81
81
|
if (!accessible)
|
|
82
82
|
return false;
|
|
83
83
|
return SELECTABLE_ROLES.has(accessible.getAccessibleRole());
|
|
@@ -86,7 +86,7 @@ const selectOptions = async (element, values) => {
|
|
|
86
86
|
if (!isSelectable(element)) {
|
|
87
87
|
throw new Error("Cannot select options: element is not a selectable widget (COMBO_BOX or LIST)");
|
|
88
88
|
}
|
|
89
|
-
const role =
|
|
89
|
+
const role = getNativeObject(element.id, Gtk.Accessible)?.getAccessibleRole();
|
|
90
90
|
const valueArray = Array.isArray(values) ? values : [values];
|
|
91
91
|
if (role === Gtk.AccessibleRole.COMBO_BOX) {
|
|
92
92
|
if (valueArray.length > 1) {
|
|
@@ -120,7 +120,7 @@ const selectOptions = async (element, values) => {
|
|
|
120
120
|
await tick();
|
|
121
121
|
};
|
|
122
122
|
const deselectOptions = async (element, values) => {
|
|
123
|
-
const role =
|
|
123
|
+
const role = getNativeObject(element.id, Gtk.Accessible)?.getAccessibleRole();
|
|
124
124
|
if (role !== Gtk.AccessibleRole.LIST) {
|
|
125
125
|
throw new Error("Cannot deselect options: only ListBox supports deselection");
|
|
126
126
|
}
|
package/dist/widget.d.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
1
|
import * 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
|
-
*/
|
|
5
2
|
export declare const isEditable: (widget: Gtk.Widget) => boolean;
|
|
6
|
-
/**
|
|
7
|
-
* Checks if a widget has an accessible role that supports labels.
|
|
8
|
-
*/
|
|
9
3
|
export declare const hasLabel: (widget: Gtk.Widget) => boolean;
|
package/dist/widget.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getNativeObject } from "@gtkx/ffi";
|
|
2
2
|
import * as Gtk from "@gtkx/ffi/gtk";
|
|
3
3
|
const EDITABLE_ROLES = new Set([
|
|
4
4
|
Gtk.AccessibleRole.TEXT_BOX,
|
|
5
5
|
Gtk.AccessibleRole.SEARCH_BOX,
|
|
6
6
|
Gtk.AccessibleRole.SPIN_BUTTON,
|
|
7
7
|
]);
|
|
8
|
-
/**
|
|
9
|
-
* Checks if a widget has an editable accessible role (text box, search box, or spin button).
|
|
10
|
-
*/
|
|
11
8
|
export const isEditable = (widget) => {
|
|
12
|
-
const accessible =
|
|
9
|
+
const accessible = getNativeObject(widget.id, Gtk.Accessible);
|
|
13
10
|
if (!accessible)
|
|
14
11
|
return false;
|
|
15
12
|
return EDITABLE_ROLES.has(accessible.getAccessibleRole());
|
|
@@ -24,11 +21,8 @@ const LABEL_ROLES = new Set([
|
|
|
24
21
|
Gtk.AccessibleRole.MENU_ITEM_CHECKBOX,
|
|
25
22
|
Gtk.AccessibleRole.MENU_ITEM_RADIO,
|
|
26
23
|
]);
|
|
27
|
-
/**
|
|
28
|
-
* Checks if a widget has an accessible role that supports labels.
|
|
29
|
-
*/
|
|
30
24
|
export const hasLabel = (widget) => {
|
|
31
|
-
const accessible =
|
|
25
|
+
const accessible = getNativeObject(widget.id, Gtk.Accessible);
|
|
32
26
|
if (!accessible)
|
|
33
27
|
return false;
|
|
34
28
|
return LABEL_ROLES.has(accessible.getAccessibleRole());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/testing",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Testing utilities for GTKX applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gtk",
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
"dist"
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@gtkx/ffi": "0.9.
|
|
36
|
-
"@gtkx/
|
|
37
|
-
"@gtkx/
|
|
35
|
+
"@gtkx/ffi": "0.9.2",
|
|
36
|
+
"@gtkx/react": "0.9.2",
|
|
37
|
+
"@gtkx/native": "0.9.2"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsc -b && cp ../../README.md .",
|
|
41
|
-
"test": "
|
|
41
|
+
"test": "../../scripts/run-tests.sh"
|
|
42
42
|
}
|
|
43
43
|
}
|