@bonsae/nrg 0.19.0 → 0.20.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 +150 -4
- package/package.json +17 -11
- package/test/client/component/config.js +1 -0
- package/test/client/e2e/index.js +6 -18
- package/test/client/unit/config.js +2 -1
- package/test/server/integration/config.js +21 -0
- package/test/server/integration/index.js +375 -0
- package/test/server/unit/config.js +5 -1
- package/test/server/unit/index.js +4 -1
- package/tsconfig/test/server/integration.json +6 -0
- package/types/test-client-e2e.d.ts +1 -0
- package/types/test-server-integration.d.ts +523 -0
- package/types/test-server-unit.d.ts +12 -0
- package/types/vite.d.ts +1 -14
- package/vite/index.js +34 -87
package/README.md
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
<a href="https://socket.dev/npm/package/@bonsae/nrg"><img src="https://badge.socket.dev/npm/package/@bonsae/nrg?v=1" alt="Socket Badge"></a>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
|
+
> [!WARNING]
|
|
13
|
+
> While **nrg** is at `v0`, breaking changes can land in any release and will **not** bump the major version. Pin an exact version and review the release notes before upgrading.
|
|
14
|
+
|
|
12
15
|
# nrg
|
|
13
16
|
|
|
14
17
|
Build Node-RED nodes with Vue 3, TypeScript, JSON Schema validations, Vite and Vitest.
|
|
@@ -36,10 +39,10 @@ Installing `node-red` as a dev dependency is recommended for fast, reliable dev
|
|
|
36
39
|
|
|
37
40
|
```typescript
|
|
38
41
|
import { defineConfig } from "vite";
|
|
39
|
-
import {
|
|
42
|
+
import { nrg } from "@bonsae/nrg/vite";
|
|
40
43
|
|
|
41
44
|
export default defineConfig({
|
|
42
|
-
plugins: [
|
|
45
|
+
plugins: [nrg()],
|
|
43
46
|
});
|
|
44
47
|
```
|
|
45
48
|
|
|
@@ -129,7 +132,7 @@ See the [consumer template](https://github.com/AllanOricil/node-red-vue-template
|
|
|
129
132
|
|
|
130
133
|
## Testing
|
|
131
134
|
|
|
132
|
-
NRG provides
|
|
135
|
+
NRG provides five test libraries and bundles most test infrastructure as direct dependencies. Install `vitest` plus any optional peer dependencies you need:
|
|
133
136
|
|
|
134
137
|
```bash
|
|
135
138
|
pnpm add -D vitest
|
|
@@ -146,16 +149,35 @@ Optional peer dependencies:
|
|
|
146
149
|
| `@vitest/coverage-istanbul` | Coverage with `--coverage` (Istanbul provider) |
|
|
147
150
|
|
|
148
151
|
- `@bonsae/nrg/test/server/unit` — server-side unit tests
|
|
152
|
+
- `@bonsae/nrg/test/server/integration` — server-side integration tests (real Node-RED runtime)
|
|
149
153
|
- `@bonsae/nrg/test/client/unit` — client-side unit tests (TypeScript logic)
|
|
150
154
|
- `@bonsae/nrg/test/client/component` — client component tests (Vue + browser)
|
|
151
155
|
- `@bonsae/nrg/test/client/e2e` — browser E2E tests (Playwright)
|
|
152
156
|
|
|
153
157
|
### Server Unit Tests
|
|
154
158
|
|
|
159
|
+
Instantiate your node with mocked Node-RED internals and exercise its full lifecycle in-process:
|
|
160
|
+
|
|
155
161
|
```typescript
|
|
162
|
+
// vitest.server.unit.config.ts
|
|
163
|
+
import { defineConfig, mergeConfig } from "vitest/config";
|
|
164
|
+
import { defaultConfig } from "@bonsae/nrg/test/server/unit/config";
|
|
165
|
+
|
|
166
|
+
export default mergeConfig(
|
|
167
|
+
defaultConfig,
|
|
168
|
+
defineConfig({
|
|
169
|
+
test: {
|
|
170
|
+
include: ["tests/server/unit/**/*.test.ts"],
|
|
171
|
+
},
|
|
172
|
+
}),
|
|
173
|
+
);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// tests/server/unit/my-node.test.ts
|
|
156
178
|
import { describe, it, expect } from "vitest";
|
|
157
179
|
import { createNode } from "@bonsae/nrg/test/server/unit";
|
|
158
|
-
import MyNode from "
|
|
180
|
+
import MyNode from "../../../src/server/nodes/my-node";
|
|
159
181
|
|
|
160
182
|
describe("my-node", () => {
|
|
161
183
|
it("should process messages", async () => {
|
|
@@ -170,6 +192,58 @@ describe("my-node", () => {
|
|
|
170
192
|
});
|
|
171
193
|
```
|
|
172
194
|
|
|
195
|
+
### Server Integration Tests
|
|
196
|
+
|
|
197
|
+
Boot a real, headless Node-RED runtime, deploy your nodes, and drive them with real messages — verifying config-node resolution, credentials, wiring, and context that unit mocks can't. Integration tests live in `tests/server/integration`, separate from `tests/server/unit`. Add `node-red` as a dev dependency, then:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// vitest.server.integration.config.ts
|
|
201
|
+
import { defineConfig, mergeConfig } from "vitest/config";
|
|
202
|
+
import { defaultConfig } from "@bonsae/nrg/test/server/integration/config";
|
|
203
|
+
|
|
204
|
+
export default mergeConfig(
|
|
205
|
+
defaultConfig,
|
|
206
|
+
defineConfig({
|
|
207
|
+
test: {
|
|
208
|
+
include: ["tests/server/integration/**/*.test.ts"],
|
|
209
|
+
},
|
|
210
|
+
}),
|
|
211
|
+
);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// tests/server/integration/my-node.test.ts
|
|
216
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
217
|
+
import {
|
|
218
|
+
startRuntime,
|
|
219
|
+
type Runtime,
|
|
220
|
+
} from "@bonsae/nrg/test/server/integration";
|
|
221
|
+
import MyNode from "../../../src/server/nodes/my-node";
|
|
222
|
+
|
|
223
|
+
describe("my-node (integration)", () => {
|
|
224
|
+
let runtime: Runtime;
|
|
225
|
+
|
|
226
|
+
beforeAll(async () => {
|
|
227
|
+
runtime = await startRuntime({ nodes: [MyNode] });
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
afterAll(async () => {
|
|
231
|
+
await runtime.stop();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("processes input in a real runtime", async () => {
|
|
235
|
+
const flow = runtime.flow();
|
|
236
|
+
const node = flow.addNode(MyNode, { greeting: "hello" });
|
|
237
|
+
await flow.deploy();
|
|
238
|
+
|
|
239
|
+
await node.receive({ payload: "world" });
|
|
240
|
+
|
|
241
|
+
const out = (await node.read()) as { output: { payload: string } };
|
|
242
|
+
expect(out.output.payload).toBe("hello world");
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
173
247
|
### Client Unit Tests
|
|
174
248
|
|
|
175
249
|
Test client-side TypeScript logic (validation, utilities) with mocked `RED` and `$` globals:
|
|
@@ -257,6 +331,77 @@ describe("MyForm", () => {
|
|
|
257
331
|
});
|
|
258
332
|
```
|
|
259
333
|
|
|
334
|
+
### Client E2E Tests
|
|
335
|
+
|
|
336
|
+
Drive the real editor in a live Node-RED instance with Playwright — schema-driven forms, validation, TypedInput, config selectors, and i18n. Install `playwright`, then point a global setup at a flow and walk the editor with `NodeRedEditor`:
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
// vitest.client.e2e.config.ts
|
|
340
|
+
import { defineConfig } from "vitest/config";
|
|
341
|
+
import { defaultConfig } from "@bonsae/nrg/test/client/e2e";
|
|
342
|
+
|
|
343
|
+
export default defineConfig({
|
|
344
|
+
test: {
|
|
345
|
+
...defaultConfig,
|
|
346
|
+
globalSetup: "tests/client/e2e/global-setup.ts",
|
|
347
|
+
include: ["tests/client/e2e/**/*.test.ts"],
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
// tests/client/e2e/global-setup.ts
|
|
354
|
+
import {
|
|
355
|
+
setup as baseSetup,
|
|
356
|
+
teardown as baseTeardown,
|
|
357
|
+
} from "@bonsae/nrg/test/client/e2e";
|
|
358
|
+
|
|
359
|
+
export async function setup() {
|
|
360
|
+
await baseSetup({
|
|
361
|
+
flow: [
|
|
362
|
+
{ id: "tab1", type: "tab", label: "E2E Tests" },
|
|
363
|
+
{ id: "n1", type: "my-node", z: "tab1", name: "", wires: [[]] },
|
|
364
|
+
],
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export async function teardown() {
|
|
369
|
+
await baseTeardown();
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
// tests/client/e2e/my-node.test.ts
|
|
375
|
+
import { describe, test, expect, beforeAll, afterAll } from "vitest";
|
|
376
|
+
import { chromium, type Browser } from "playwright";
|
|
377
|
+
import { NodeRedEditor } from "@bonsae/nrg/test/client/e2e";
|
|
378
|
+
|
|
379
|
+
describe("my-node editor", () => {
|
|
380
|
+
let browser: Browser;
|
|
381
|
+
let editor: NodeRedEditor;
|
|
382
|
+
|
|
383
|
+
beforeAll(async () => {
|
|
384
|
+
browser = await chromium.launch();
|
|
385
|
+
const port = Number(process.env.NODE_RED_PORT);
|
|
386
|
+
editor = new NodeRedEditor(await browser.newPage(), port);
|
|
387
|
+
await editor.open();
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
afterAll(() => browser.close());
|
|
391
|
+
|
|
392
|
+
test("name field round-trips", async () => {
|
|
393
|
+
await editor.editNode("n1");
|
|
394
|
+
const name = editor.field("Name");
|
|
395
|
+
await name.fill("Test Node");
|
|
396
|
+
await editor.clickDone();
|
|
397
|
+
|
|
398
|
+
await editor.editNode("n1");
|
|
399
|
+
expect(await name.getValue()).toBe("Test Node");
|
|
400
|
+
await editor.clickCancel();
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
```
|
|
404
|
+
|
|
260
405
|
See the [testing guide](https://bonsaedev.github.io/nrg/guide/testing) for full API reference.
|
|
261
406
|
|
|
262
407
|
## Development
|
|
@@ -270,6 +415,7 @@ pnpm validate:lint # eslint
|
|
|
270
415
|
pnpm validate:format # prettier check
|
|
271
416
|
pnpm test # run all tests
|
|
272
417
|
pnpm test:core:server:unit # server unit tests
|
|
418
|
+
pnpm test:core:server:integration # server integration tests (real Node-RED)
|
|
273
419
|
pnpm test:core:client:unit # client unit tests
|
|
274
420
|
pnpm test:core:client:component # client component tests
|
|
275
421
|
pnpm test:core:client:e2e # client E2E tests
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bonsae/nrg",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "NRG framework — build Node-RED nodes with Vue 3, TypeScript, and JSON Schema",
|
|
5
5
|
"author": "Allan Oricil <allanoricil@duck.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -52,6 +52,11 @@
|
|
|
52
52
|
"default": "./test/server/unit/index.js"
|
|
53
53
|
},
|
|
54
54
|
"./test/server/unit/config": "./test/server/unit/config.js",
|
|
55
|
+
"./test/server/integration": {
|
|
56
|
+
"types": "./types/test-server-integration.d.ts",
|
|
57
|
+
"default": "./test/server/integration/index.js"
|
|
58
|
+
},
|
|
59
|
+
"./test/server/integration/config": "./test/server/integration/config.js",
|
|
55
60
|
"./test/client/component": {
|
|
56
61
|
"types": "./types/test-client-component.d.ts",
|
|
57
62
|
"default": "./test/client/component/index.js"
|
|
@@ -72,19 +77,20 @@
|
|
|
72
77
|
"./tsconfig/core/server.json": "./tsconfig/core/server.json",
|
|
73
78
|
"./tsconfig/core/client.json": "./tsconfig/core/client.json",
|
|
74
79
|
"./tsconfig/test/server/unit.json": "./tsconfig/test/server/unit.json",
|
|
80
|
+
"./tsconfig/test/server/integration.json": "./tsconfig/test/server/integration.json",
|
|
75
81
|
"./tsconfig/test/client/component.json": "./tsconfig/test/client/component.json",
|
|
76
82
|
"./tsconfig/test/client/unit.json": "./tsconfig/test/client/unit.json",
|
|
77
83
|
"./tsconfig/test/client/e2e.json": "./tsconfig/test/client/e2e.json"
|
|
78
84
|
},
|
|
79
85
|
"peerDependencies": {
|
|
80
|
-
"
|
|
81
|
-
"vitest": "^4.0.0",
|
|
82
|
-
"vue": "^3.5.14",
|
|
86
|
+
"@vitest/browser-playwright": "^4.0.0",
|
|
83
87
|
"@vitest/coverage-istanbul": "^4.0.0",
|
|
84
88
|
"@vitest/coverage-v8": "^4.0.0",
|
|
85
|
-
"@vitest/browser-playwright": "^4.0.0",
|
|
86
89
|
"playwright": "^1.50.0",
|
|
87
|
-
"
|
|
90
|
+
"vite": "^6.0.0",
|
|
91
|
+
"vitest": "^4.0.0",
|
|
92
|
+
"vitest-browser-vue": "^2.0.0",
|
|
93
|
+
"vue": "^3.5.14"
|
|
88
94
|
},
|
|
89
95
|
"peerDependenciesMeta": {
|
|
90
96
|
"@vitest/coverage-istanbul": {
|
|
@@ -107,6 +113,7 @@
|
|
|
107
113
|
"@clack/prompts": "^1.0.1",
|
|
108
114
|
"@sinclair/typebox": "^0.34.33",
|
|
109
115
|
"@vitejs/plugin-vue": "^5.2.3",
|
|
116
|
+
"@vitest/browser-playwright": "^4.1.5",
|
|
110
117
|
"ajv": "^8.17.1",
|
|
111
118
|
"ajv-errors": "^3.0.0",
|
|
112
119
|
"ajv-formats": "^3.0.1",
|
|
@@ -115,16 +122,15 @@
|
|
|
115
122
|
"es-toolkit": "^1.37.2",
|
|
116
123
|
"esbuild": "^0.25.4",
|
|
117
124
|
"get-port": "^7.1.0",
|
|
125
|
+
"happy-dom": "^20.10.2",
|
|
118
126
|
"jsonpointer": "^5.0.1",
|
|
119
127
|
"mime-types": "^3.0.1",
|
|
128
|
+
"playwright": "^1.60.0",
|
|
120
129
|
"tree-kill": "^1.2.2",
|
|
121
130
|
"typescript": "^5.8.3",
|
|
122
131
|
"vite-plugin-dts": "^4.5.4",
|
|
123
132
|
"vite-plugin-static-copy": "^3.1.0",
|
|
124
|
-
"vue": "^
|
|
125
|
-
"
|
|
126
|
-
"happy-dom": "^20.10.2",
|
|
127
|
-
"playwright": "^1.60.0",
|
|
128
|
-
"vitest-browser-vue": "^2.1.0"
|
|
133
|
+
"vitest-browser-vue": "^2.1.0",
|
|
134
|
+
"vue": "^3.5.14"
|
|
129
135
|
}
|
|
130
136
|
}
|
package/test/client/e2e/index.js
CHANGED
|
@@ -2191,7 +2191,7 @@ async function compileRuntimeSettingsFile(runtimeSettingsFilepath, port) {
|
|
|
2191
2191
|
return compiledRuntimeSettingsFilepath;
|
|
2192
2192
|
}
|
|
2193
2193
|
async function generateRuntimeSettings(options) {
|
|
2194
|
-
const { outDir, port, settingsFilepath,
|
|
2194
|
+
const { outDir, port, settingsFilepath, logger: logger2 } = options;
|
|
2195
2195
|
const tempFiles = [];
|
|
2196
2196
|
const userRuntimeSettingsFilepath = findUserRuntimeSettingsFilepath(
|
|
2197
2197
|
settingsFilepath,
|
|
@@ -2210,17 +2210,13 @@ async function generateRuntimeSettings(options) {
|
|
|
2210
2210
|
const userDir = path11.resolve(cwd, ".node-red").split(path11.sep).join("/");
|
|
2211
2211
|
const userDirLiteral = JSON.stringify(userDir);
|
|
2212
2212
|
const outDirLiteral = JSON.stringify(normalizedOutDir);
|
|
2213
|
-
const httpAdminRootAssignment = httpAdminRoot ? `settings.httpAdminRoot = ${JSON.stringify(httpAdminRoot)};
|
|
2214
|
-
` : "";
|
|
2215
|
-
const httpAdminRootEntry = httpAdminRoot ? `
|
|
2216
|
-
httpAdminRoot: ${JSON.stringify(httpAdminRoot)},` : "";
|
|
2217
2213
|
const finalRuntimeSettingsFile = compiledRuntimeSettingsFilepath ? `
|
|
2218
2214
|
const compiledRuntimeSettings = require(${JSON.stringify(
|
|
2219
2215
|
compiledRuntimeSettingsFilepath.split(path11.sep).join("/")
|
|
2220
2216
|
)});
|
|
2221
2217
|
const settings = compiledRuntimeSettings.default || compiledRuntimeSettings;
|
|
2222
2218
|
settings.uiPort = ${port};
|
|
2223
|
-
|
|
2219
|
+
if(!settings.userDir){
|
|
2224
2220
|
settings.userDir = ${userDirLiteral};
|
|
2225
2221
|
}
|
|
2226
2222
|
settings.nodesDir = settings.nodesDir || [];
|
|
@@ -2242,7 +2238,7 @@ const settings = {
|
|
|
2242
2238
|
uiPort: ${port},
|
|
2243
2239
|
userDir: ${userDirLiteral},
|
|
2244
2240
|
flowFile: "flows.json",
|
|
2245
|
-
nodesDir: [${outDirLiteral}]
|
|
2241
|
+
nodesDir: [${outDirLiteral}],
|
|
2246
2242
|
// the welcome tour overlay intercepts pointer events \u2014 fatal for e2e
|
|
2247
2243
|
editorTheme: { tours: false },
|
|
2248
2244
|
};
|
|
@@ -2396,12 +2392,10 @@ var NodeRedLauncher = class {
|
|
|
2396
2392
|
port = null;
|
|
2397
2393
|
outDir;
|
|
2398
2394
|
options;
|
|
2399
|
-
_slug;
|
|
2400
2395
|
logger;
|
|
2401
|
-
constructor(outDir, options
|
|
2396
|
+
constructor(outDir, options) {
|
|
2402
2397
|
this.outDir = outDir;
|
|
2403
2398
|
this.options = options;
|
|
2404
|
-
this._slug = slug;
|
|
2405
2399
|
this.logger = new Logger({
|
|
2406
2400
|
name: "vite-plugin-node-red",
|
|
2407
2401
|
prefix: "node-red"
|
|
@@ -2410,12 +2404,6 @@ var NodeRedLauncher = class {
|
|
|
2410
2404
|
get preferredPort() {
|
|
2411
2405
|
return this.options.runtime?.port ?? 1880;
|
|
2412
2406
|
}
|
|
2413
|
-
get slug() {
|
|
2414
|
-
return this._slug;
|
|
2415
|
-
}
|
|
2416
|
-
get basePath() {
|
|
2417
|
-
return this._slug ? `/${this._slug}/` : "/";
|
|
2418
|
-
}
|
|
2419
2407
|
get restartDelay() {
|
|
2420
2408
|
return this.options.restartDelay ?? 1e3;
|
|
2421
2409
|
}
|
|
@@ -2496,7 +2484,6 @@ var NodeRedLauncher = class {
|
|
|
2496
2484
|
outDir: this.outDir,
|
|
2497
2485
|
port: this.port,
|
|
2498
2486
|
settingsFilepath: this.options.runtime?.settingsFilepath,
|
|
2499
|
-
httpAdminRoot: this._slug ? this.basePath : void 0,
|
|
2500
2487
|
logger: this.logger
|
|
2501
2488
|
});
|
|
2502
2489
|
for (const file of settings.tempFiles) {
|
|
@@ -2680,7 +2667,8 @@ var NodeRedTestEnvironment = class {
|
|
|
2680
2667
|
var defaultConfig = {
|
|
2681
2668
|
testTimeout: 6e4,
|
|
2682
2669
|
hookTimeout: 12e4,
|
|
2683
|
-
globalSetup: ["@bonsae/nrg/test/client/e2e"]
|
|
2670
|
+
globalSetup: ["@bonsae/nrg/test/client/e2e"],
|
|
2671
|
+
include: ["tests/client/e2e/**/*.test.ts"]
|
|
2684
2672
|
};
|
|
2685
2673
|
var _env = null;
|
|
2686
2674
|
async function setup(options) {
|
|
@@ -18,7 +18,8 @@ var defaultConfig = {
|
|
|
18
18
|
test: {
|
|
19
19
|
testTimeout: 3e4,
|
|
20
20
|
environment: "happy-dom",
|
|
21
|
-
setupFiles: ["@bonsae/nrg/test/client/unit/setup"]
|
|
21
|
+
setupFiles: ["@bonsae/nrg/test/client/unit/setup"],
|
|
22
|
+
include: ["tests/client/unit/**/*.test.ts"]
|
|
22
23
|
}
|
|
23
24
|
};
|
|
24
25
|
export {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/test/server/integration/config.ts
|
|
2
|
+
import path from "path";
|
|
3
|
+
var defaultConfig = {
|
|
4
|
+
resolve: {
|
|
5
|
+
alias: {
|
|
6
|
+
"@": path.resolve(process.cwd(), "src")
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
test: {
|
|
10
|
+
testTimeout: 3e4,
|
|
11
|
+
hookTimeout: 3e4,
|
|
12
|
+
pool: "forks",
|
|
13
|
+
fileParallelism: false,
|
|
14
|
+
// integration tests live under tests/server/integration, separate from the
|
|
15
|
+
// unit tier (tests/server/unit) so the two never overlap
|
|
16
|
+
include: ["tests/server/integration/**/*.test.ts"]
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export {
|
|
20
|
+
defaultConfig
|
|
21
|
+
};
|