@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 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 { nodeRed } from "@bonsae/nrg/vite";
42
+ import { nrg } from "@bonsae/nrg/vite";
40
43
 
41
44
  export default defineConfig({
42
- plugins: [nodeRed()],
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 four test libraries and bundles most test infrastructure as direct dependencies. Install `vitest` plus any optional peer dependencies you need:
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 "../src/server/nodes/my-node";
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.19.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
- "vite": "^6.0.0",
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
- "vitest-browser-vue": "^2.0.0"
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": "^3.5.14",
125
- "@vitest/browser-playwright": "^4.1.5",
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
  }
@@ -32,6 +32,7 @@ var defaultConfig = {
32
32
  test: {
33
33
  testTimeout: 3e4,
34
34
  setupFiles: ["@bonsae/nrg/test/client/component/setup"],
35
+ include: ["tests/client/component/**/*.test.ts"],
35
36
  browser: {
36
37
  enabled: true,
37
38
  instances: [
@@ -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, httpAdminRoot, logger: logger2 } = options;
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
- ${httpAdminRootAssignment}if(!settings.userDir){
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}],${httpAdminRootEntry}
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, slug = "") {
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
+ };