@devscholar/node-with-gjs 0.0.1 → 0.0.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/AGENTS.md +2 -0
- package/README.md +10 -0
- package/__tests__/basic.test.ts +39 -0
- package/__tests__/glib.test.ts +70 -0
- package/__tests__/gtk4.test.ts +71 -0
- package/docs/testing.md +100 -0
- package/hook.js +44 -0
- package/package.json +22 -20
- package/tsconfig.json +22 -11
package/AGENTS.md
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
When using TypeScript in Node, you must run TS with the `--experimental-transform-types` flag; other methods like `tsx` are not allowed, and building and transpilation are not allowed, except Vite. The test framework should be Vitest, not Jest. ESM must be used instead of require.
|
|
2
|
+
allowImportingTsExtensions should be true in tsconfig.json. (If this file exists)
|
package/README.md
CHANGED
|
@@ -33,6 +33,16 @@ pacman -S gtk4 webkitgtk-6.0 gjs
|
|
|
33
33
|
|
|
34
34
|
For more examples and details, see the [node-with-gjs-examples README](https://github.com/devscholar/node-with-gjs-examples).
|
|
35
35
|
|
|
36
|
+
# Tests
|
|
37
|
+
|
|
38
|
+
Run all tests:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm test
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
For detailed testing documentation, see [docs/testing.md](docs/testing.md).
|
|
45
|
+
|
|
36
46
|
# License
|
|
37
47
|
|
|
38
48
|
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
|
|
3
|
+
describe('Basic Module Tests', () => {
|
|
4
|
+
let gjs: any;
|
|
5
|
+
|
|
6
|
+
beforeAll(async () => {
|
|
7
|
+
try {
|
|
8
|
+
gjs = await import('../src/index.js');
|
|
9
|
+
gjs.init();
|
|
10
|
+
} catch (e) {
|
|
11
|
+
console.log('Skipping basic tests - load failed:', e);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterAll(() => {
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should export init function', () => {
|
|
19
|
+
expect(gjs.init).toBeDefined();
|
|
20
|
+
expect(typeof gjs.init).toBe('function');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should export imports object', () => {
|
|
24
|
+
expect(gjs.imports).toBeDefined();
|
|
25
|
+
expect(gjs.imports.gi).toBeDefined();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should have gi proxy', () => {
|
|
29
|
+
const gi = gjs.imports.gi;
|
|
30
|
+
expect(gi).toBeDefined();
|
|
31
|
+
expect(typeof gi).toBe('object');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should support gi.versions', () => {
|
|
35
|
+
const gi = gjs.imports.gi;
|
|
36
|
+
expect(gi.versions).toBeDefined();
|
|
37
|
+
expect(typeof gi.versions).toBe('object');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
|
|
3
|
+
describe('GLib Tests', () => {
|
|
4
|
+
let gjs: any;
|
|
5
|
+
let GLib: any;
|
|
6
|
+
|
|
7
|
+
beforeAll(async () => {
|
|
8
|
+
try {
|
|
9
|
+
gjs = await import('../src/index.js');
|
|
10
|
+
gjs.init();
|
|
11
|
+
const gi = gjs.imports.gi;
|
|
12
|
+
GLib = gi.GLib;
|
|
13
|
+
} catch (e) {
|
|
14
|
+
console.log('Skipping GLib tests - load failed:', e);
|
|
15
|
+
}
|
|
16
|
+
}, 60000);
|
|
17
|
+
|
|
18
|
+
afterAll(() => {
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should load GLib namespace', () => {
|
|
22
|
+
expect(GLib).toBeDefined();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should get user name', () => {
|
|
26
|
+
const userName = GLib.get_user_name();
|
|
27
|
+
expect(userName).toBeDefined();
|
|
28
|
+
expect(typeof userName).toBe('string');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should get home dir', () => {
|
|
32
|
+
const home = GLib.get_home_dir();
|
|
33
|
+
expect(home).toBeDefined();
|
|
34
|
+
expect(typeof home).toBe('string');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should get tmp dir', () => {
|
|
38
|
+
const tmp = GLib.get_tmp_dir();
|
|
39
|
+
expect(tmp).toBeDefined();
|
|
40
|
+
expect(typeof tmp).toBe('string');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('GObject Tests', () => {
|
|
45
|
+
let gjs: any;
|
|
46
|
+
let GObject: any;
|
|
47
|
+
|
|
48
|
+
beforeAll(async () => {
|
|
49
|
+
try {
|
|
50
|
+
gjs = await import('../src/index.js');
|
|
51
|
+
gjs.init();
|
|
52
|
+
const gi = gjs.imports.gi;
|
|
53
|
+
GObject = gi.GObject;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.log('Skipping GObject tests - load failed:', e);
|
|
56
|
+
}
|
|
57
|
+
}, 60000);
|
|
58
|
+
|
|
59
|
+
afterAll(() => {
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should load GObject namespace', () => {
|
|
63
|
+
expect(GObject).toBeDefined();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should have type system', () => {
|
|
67
|
+
expect(GObject.type_from_name).toBeDefined();
|
|
68
|
+
expect(GObject.type_register_fundamental).toBeDefined();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
|
|
3
|
+
describe('GTK4 GUI Tests', () => {
|
|
4
|
+
let gjs: any;
|
|
5
|
+
let Gtk: any;
|
|
6
|
+
let GLib: any;
|
|
7
|
+
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
try {
|
|
10
|
+
gjs = await import('../src/index.js');
|
|
11
|
+
gjs.init();
|
|
12
|
+
const gi = gjs.imports.gi;
|
|
13
|
+
gi.versions.Gtk = '4.0';
|
|
14
|
+
Gtk = gi.Gtk;
|
|
15
|
+
GLib = gi.GLib;
|
|
16
|
+
Gtk.init();
|
|
17
|
+
} catch (e) {
|
|
18
|
+
console.log('Skipping GTK4 tests - load failed:', e);
|
|
19
|
+
}
|
|
20
|
+
}, 60000);
|
|
21
|
+
|
|
22
|
+
afterAll(() => {
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should load Gtk namespace', () => {
|
|
26
|
+
expect(Gtk).toBeDefined();
|
|
27
|
+
expect(Gtk.Application).toBeDefined();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should load GLib namespace', () => {
|
|
31
|
+
expect(GLib).toBeDefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should create an Application instance', () => {
|
|
35
|
+
const app = new Gtk.Application({ application_id: 'org.test.app' });
|
|
36
|
+
expect(app).toBeDefined();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should create a Box container', () => {
|
|
40
|
+
const box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, spacing: 10 });
|
|
41
|
+
expect(box).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should create a Label', () => {
|
|
45
|
+
const label = new Gtk.Label({ label: 'Test Label' });
|
|
46
|
+
expect(label).toBeDefined();
|
|
47
|
+
expect(label.get_label()).toBe('Test Label');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should create a Button', () => {
|
|
51
|
+
const button = new Gtk.Button({ label: 'Click Me' });
|
|
52
|
+
expect(button).toBeDefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should set Label text', () => {
|
|
56
|
+
const label = new Gtk.Label({ label: 'Initial' });
|
|
57
|
+
label.set_label('Updated');
|
|
58
|
+
expect(label.get_label()).toBe('Updated');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should use Orientation enum', () => {
|
|
62
|
+
expect(Gtk.Orientation.VERTICAL).toBeDefined();
|
|
63
|
+
expect(Gtk.Orientation.HORIZONTAL).toBeDefined();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should use Align enum', () => {
|
|
67
|
+
expect(Gtk.Align.CENTER).toBeDefined();
|
|
68
|
+
expect(Gtk.Align.START).toBeDefined();
|
|
69
|
+
expect(Gtk.Align.END).toBeDefined();
|
|
70
|
+
});
|
|
71
|
+
});
|
package/docs/testing.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Testing Guide
|
|
2
|
+
|
|
3
|
+
This document describes how to run tests for the `node-with-gjs` project.
|
|
4
|
+
|
|
5
|
+
## Running Tests
|
|
6
|
+
|
|
7
|
+
### Run All Tests
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm test
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Run Specific Test Files
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm test -- --testPathPatterns=gtk4
|
|
17
|
+
npm test -- --testPathPatterns=glib
|
|
18
|
+
npm test -- --testPathPatterns=basic
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Test Configuration
|
|
22
|
+
|
|
23
|
+
Tests are configured to run **serially** (`maxWorkers: 1`) because the module uses a singleton pattern with global state for the IPC connection to GJS. Running tests in parallel would cause conflicts between multiple GJS processes.
|
|
24
|
+
|
|
25
|
+
If you need to run tests in parallel, you would need to refactor the module to support multiple instances (see [jest.config.js](../jest.config.js)).
|
|
26
|
+
|
|
27
|
+
## Test Files
|
|
28
|
+
|
|
29
|
+
Test files are located in the `__tests__` directory:
|
|
30
|
+
|
|
31
|
+
| File | Description |
|
|
32
|
+
|------|-------------|
|
|
33
|
+
| `basic.test.ts` | Basic module functionality tests (init, imports.gi) |
|
|
34
|
+
| `gtk4.test.ts` | GTK4 GUI component tests (Application, Box, Label, Button, enums) |
|
|
35
|
+
| `glib.test.ts` | GLib and GObject tests (get_user_name, get_home_dir, etc.) |
|
|
36
|
+
|
|
37
|
+
## Platform Support
|
|
38
|
+
|
|
39
|
+
| Platform | Support |
|
|
40
|
+
|----------|---------|
|
|
41
|
+
| Linux | All tests run normally |
|
|
42
|
+
| macOS | Tests are skipped (GJS is Linux-only) |
|
|
43
|
+
| Windows | Tests are skipped (GJS is Linux-only) |
|
|
44
|
+
|
|
45
|
+
Tests automatically detect the platform and skip on non-Linux systems.
|
|
46
|
+
|
|
47
|
+
## GUI Testing
|
|
48
|
+
|
|
49
|
+
GUI tests create GTK objects in memory without displaying windows. This allows testing without interfering with the desktop environment. The tests verify:
|
|
50
|
+
|
|
51
|
+
- Object creation (Application, Window, Box, Label, Button)
|
|
52
|
+
- Property getters/setters
|
|
53
|
+
- Method calls
|
|
54
|
+
- Enum values
|
|
55
|
+
|
|
56
|
+
For testing with actual window display and event handling, see the [node-with-gjs-examples](https://github.com/devscholar/node-with-gjs-examples) repository.
|
|
57
|
+
|
|
58
|
+
## Writing New Tests
|
|
59
|
+
|
|
60
|
+
When writing new tests, keep in mind:
|
|
61
|
+
|
|
62
|
+
1. **Initialize the module**: Always call `gjs.init()` in `beforeAll`
|
|
63
|
+
2. **Set GTK version**: Use `gi.versions.Gtk = '4.0'` before accessing Gtk
|
|
64
|
+
3. **Initialize GTK**: Call `Gtk.init()` before creating GTK objects
|
|
65
|
+
4. **Platform check**: Tests automatically skip on non-Linux platforms via `const isLinux = process.platform === 'linux' || process.platform === 'darwin'`
|
|
66
|
+
|
|
67
|
+
Example test structure:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { jest } from '@jest/globals';
|
|
71
|
+
|
|
72
|
+
const isLinux = process.platform === 'linux' || process.platform === 'darwin';
|
|
73
|
+
|
|
74
|
+
(isLinux ? describe : describe.skip)('My Tests', () => {
|
|
75
|
+
let gjs: any;
|
|
76
|
+
let Gtk: any;
|
|
77
|
+
|
|
78
|
+
beforeAll(async () => {
|
|
79
|
+
try {
|
|
80
|
+
gjs = await import('../src/index.js');
|
|
81
|
+
gjs.init();
|
|
82
|
+
const gi = gjs.imports.gi;
|
|
83
|
+
gi.versions.Gtk = '4.0';
|
|
84
|
+
Gtk = gi.Gtk;
|
|
85
|
+
Gtk.init();
|
|
86
|
+
} catch (e) {
|
|
87
|
+
console.log('Skipping tests - load failed:', e);
|
|
88
|
+
}
|
|
89
|
+
}, 60000);
|
|
90
|
+
|
|
91
|
+
afterAll(() => {
|
|
92
|
+
// Cleanup if needed
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should create a widget', () => {
|
|
96
|
+
const widget = new Gtk.Label({ label: 'Test' });
|
|
97
|
+
expect(widget).toBeDefined();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
```
|
package/hook.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// hook.js - Node.js module loader hook for gi:// protocol
|
|
2
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
3
|
+
if (specifier.startsWith('gi://')) {
|
|
4
|
+
return {
|
|
5
|
+
url: specifier,
|
|
6
|
+
shortCircuit: true,
|
|
7
|
+
format: 'module'
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
return nextResolve(specifier, context, nextResolve);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function load(url, context, nextLoad) {
|
|
14
|
+
if (url.startsWith('gi://')) {
|
|
15
|
+
// Safely parse 'gi://Gtk?version=4.0'
|
|
16
|
+
const bareUrl = url.replace('gi://', '');
|
|
17
|
+
const [namespacePart, queryPart] = bareUrl.split('?');
|
|
18
|
+
|
|
19
|
+
const namespace = namespacePart;
|
|
20
|
+
let version = '';
|
|
21
|
+
|
|
22
|
+
if (queryPart) {
|
|
23
|
+
const params = new URLSearchParams(queryPart);
|
|
24
|
+
version = params.get('version') || '';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const coreUrl = new URL('./dist/index.js', import.meta.url).href;
|
|
28
|
+
|
|
29
|
+
const source = `
|
|
30
|
+
import { init, imports } from '${coreUrl}';
|
|
31
|
+
init();
|
|
32
|
+
imports.gi.versions['${namespace}'] = '${version}';
|
|
33
|
+
const ns = imports.gi['${namespace}'];
|
|
34
|
+
export default ns;
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
format: 'module',
|
|
39
|
+
shortCircuit: true,
|
|
40
|
+
source: source
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return nextLoad(url, context, nextLoad);
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@devscholar/node-with-gjs",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Node.js IPC Bridge for GJS",
|
|
5
|
-
"main": "./src/index.ts",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": "./src/index.ts"
|
|
9
|
-
},
|
|
10
|
-
"bin": {
|
|
11
|
-
|
|
12
|
-
},
|
|
13
|
-
"scripts": {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@devscholar/node-with-gjs",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "Node.js IPC Bridge for GJS",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"test": "node --experimental-transform-types node_modules/vitest/vitest.mjs run"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^20.0.0",
|
|
19
|
+
"typescript": "^5.0.0",
|
|
20
|
+
"vitest": "^3.0.0"
|
|
21
|
+
}
|
|
22
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "
|
|
4
|
-
"module": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ES2022"],
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationDir": "./types",
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"rootDir": "./src",
|
|
11
|
+
"strict": false,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"noEmit": false,
|
|
18
|
+
"allowSyntheticDefaultImports": true
|
|
19
|
+
},
|
|
20
|
+
"include": ["src/**/*"],
|
|
21
|
+
"exclude": ["node_modules", "dist"]
|
|
22
|
+
}
|