@iobroker/testing 5.1.1 → 5.2.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.
@@ -94,11 +94,28 @@ async function locateAdapterMainFile(adapterDir) {
94
94
  debug(` => found ${mainFile}`);
95
95
  return ret;
96
96
  }
97
+ // If the specified file doesn't exist and ends with .js, try the .ts equivalent
98
+ if (mainFile.endsWith('.js')) {
99
+ const tsFile = mainFile.replace(/\.js$/, '.ts');
100
+ ret = path.join(adapterDir, tsFile);
101
+ debug(` => trying ${ret}`);
102
+ if (await (0, fs_extra_1.pathExists)(ret)) {
103
+ debug(` => found ${tsFile}`);
104
+ return ret;
105
+ }
106
+ }
97
107
  // If both don't exist, JS-Controller uses <adapter name>.js as another fallback
98
108
  ret = path.join(adapterDir, `${ioPackage.common.name}.js`);
99
109
  debug(` => trying ${ret}`);
100
110
  if (await (0, fs_extra_1.pathExists)(ret)) {
101
- debug(` => found ${mainFile}`);
111
+ debug(` => found ${ioPackage.common.name}.js`);
112
+ return ret;
113
+ }
114
+ // Also try <adapter name>.ts as a fallback
115
+ ret = path.join(adapterDir, `${ioPackage.common.name}.ts`);
116
+ debug(` => trying ${ret}`);
117
+ if (await (0, fs_extra_1.pathExists)(ret)) {
118
+ debug(` => found ${ioPackage.common.name}.ts`);
102
119
  return ret;
103
120
  }
104
121
  throw new Error(`The adapter main file was not found in ${adapterDir}`);
@@ -75,9 +75,13 @@ class AdapterSetup {
75
75
  // Therefore pack it into a tarball and put it in the test dir for installation
76
76
  const packResult = await (0, executeCommand_1.executeCommand)('npm', ['pack', '--loglevel', 'silent'], {
77
77
  stdout: 'pipe',
78
+ stderr: 'pipe',
78
79
  });
79
80
  if (packResult.exitCode !== 0 || typeof packResult.stdout !== 'string') {
80
- throw new Error(`Packing the adapter tarball failed!`);
81
+ const errorMessage = packResult.stderr
82
+ ? `Packing the adapter tarball failed!\nstderr: ${packResult.stderr}`
83
+ : `Packing the adapter tarball failed!`;
84
+ throw new Error(errorMessage);
81
85
  }
82
86
  // The last non-empty line of `npm pack`s STDOUT contains the tarball path
83
87
  const stdoutLines = packResult.stdout.trim().split(/[\r\n]+/);
@@ -92,6 +96,7 @@ class AdapterSetup {
92
96
  debug('Removing the adapter from package-lock.json');
93
97
  await (0, executeCommand_1.executeCommand)('npm', ['uninstall', this.adapterFullName, '--package-lock-only', '--omit=dev'], {
94
98
  cwd: this.testDir,
99
+ stderr: 'pipe',
95
100
  });
96
101
  // Complete the package.json, so npm can do it's magic
97
102
  debug('Saving the adapter in package.json');
@@ -114,6 +119,7 @@ class AdapterSetup {
114
119
  // Defer to npm to install the controller (if it wasn't already)
115
120
  await (0, executeCommand_1.executeCommand)('npm', ['i', '--omit=dev'], {
116
121
  cwd: this.testDir,
122
+ stderr: 'pipe',
117
123
  });
118
124
  debug(' => done!');
119
125
  }
@@ -126,9 +132,13 @@ class AdapterSetup {
126
132
  const addResult = await (0, executeCommand_1.executeCommand)('node', [`${this.appName}.js`, 'add', this.adapterName, '--enabled', 'false'], {
127
133
  cwd: this.testControllerDir,
128
134
  stdout: 'ignore',
135
+ stderr: 'pipe',
129
136
  });
130
137
  if (addResult.exitCode !== 0) {
131
- throw new Error(`Adding the adapter instance failed!`);
138
+ const errorMessage = addResult.stderr
139
+ ? `Adding the adapter instance failed!\nstderr: ${addResult.stderr}`
140
+ : `Adding the adapter instance failed!`;
141
+ throw new Error(errorMessage);
132
142
  }
133
143
  debug(' => done!');
134
144
  }
@@ -80,6 +80,8 @@ class ControllerSetup {
80
80
  license: 'ISC',
81
81
  dependencies: {
82
82
  [`${this.appName}.js-controller`]: controllerVersion,
83
+ // @alcalzone/esbuild-register is needed to run TypeScript adapters
84
+ '@alcalzone/esbuild-register': '^2.5.1-1',
83
85
  },
84
86
  description: '',
85
87
  };
@@ -102,6 +104,7 @@ class ControllerSetup {
102
104
  debug('(Re-)installing JS Controller...');
103
105
  await (0, executeCommand_1.executeCommand)('npm', ['i', '--omit=dev'], {
104
106
  cwd: this.testDir,
107
+ stderr: 'pipe',
105
108
  });
106
109
  // Prepare/clean the databases and config
107
110
  if (wasJsControllerInstalled) {
@@ -182,13 +185,18 @@ class ControllerSetup {
182
185
  await (0, executeCommand_1.executeCommand)('node', [`${this.appName}.js`, 'stop'], {
183
186
  cwd: this.testControllerDir,
184
187
  stdout: 'ignore',
188
+ stderr: 'pipe',
185
189
  });
186
190
  const setupResult = await (0, executeCommand_1.executeCommand)('node', [`${this.appName}.js`, 'setup', 'first', '--console'], {
187
191
  cwd: this.testControllerDir,
188
192
  stdout: 'ignore',
193
+ stderr: 'pipe',
189
194
  });
190
195
  if (setupResult.exitCode !== 0) {
191
- throw new Error(`${this.appName} setup first failed!`);
196
+ const errorMessage = setupResult.stderr
197
+ ? `${this.appName} setup first failed!\nstderr: ${setupResult.stderr}`
198
+ : `${this.appName} setup first failed!`;
199
+ throw new Error(errorMessage);
192
200
  }
193
201
  debug(' => done!');
194
202
  }
@@ -164,7 +164,13 @@ class TestHarness extends node_events_1.EventEmitter {
164
164
  this._adapterExit = code != undefined ? code : signal;
165
165
  this.emit('failed', this._adapterExit);
166
166
  };
167
- this._adapterProcess = (0, node_child_process_1.spawn)(isWindows ? 'node.exe' : 'node', [mainFileRelative, '--console'], {
167
+ // Determine if we need to use esbuild-register for TypeScript files
168
+ const isTypeScript = mainFileAbsolute.endsWith('.ts');
169
+ const command = isWindows ? 'node.exe' : 'node';
170
+ const args = isTypeScript
171
+ ? ['-r', '@alcalzone/esbuild-register', mainFileRelative, '--console']
172
+ : [mainFileRelative, '--console'];
173
+ this._adapterProcess = (0, node_child_process_1.spawn)(command, args, {
168
174
  cwd: this.testAdapterDir,
169
175
  stdio: ['inherit', 'inherit', 'inherit'],
170
176
  env: { ...process.env, ...env },
@@ -32,11 +32,15 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  exports.validatePackageFiles = validatePackageFiles;
37
40
  const typeguards_1 = require("alcalzone-shared/typeguards");
38
41
  const chai_1 = require("chai");
39
42
  const fs = __importStar(require("fs"));
43
+ const json5_1 = __importDefault(require("json5"));
40
44
  const path = __importStar(require("path"));
41
45
  /**
42
46
  * Tests if the adapter files are valid.
@@ -72,6 +76,26 @@ function validatePackageFiles(adapterDir) {
72
76
  }
73
77
  });
74
78
  }
79
+ /**
80
+ * Recursively find all files matching a pattern in a directory
81
+ */
82
+ function findFiles(dir, pattern, results = []) {
83
+ if (!fs.existsSync(dir)) {
84
+ return results;
85
+ }
86
+ const files = fs.readdirSync(dir);
87
+ for (const file of files) {
88
+ const filePath = path.join(dir, file);
89
+ const stat = fs.statSync(filePath);
90
+ if (stat.isDirectory()) {
91
+ findFiles(filePath, pattern, results);
92
+ }
93
+ else if (pattern.test(file)) {
94
+ results.push(filePath);
95
+ }
96
+ }
97
+ return results;
98
+ }
75
99
  describe(`Validate the package files`, () => {
76
100
  describe(`Ensure they are readable`, () => {
77
101
  for (const filename of ['package.json', 'io-package.json']) {
@@ -142,6 +166,19 @@ function validatePackageFiles(adapterDir) {
142
166
  }
143
167
  }
144
168
  });
169
+ it('iobroker.js-controller is not listed as a dependency', () => {
170
+ for (const depType of [
171
+ 'dependencies',
172
+ 'devDependencies',
173
+ 'optionalDependencies',
174
+ 'peerDependencies',
175
+ ]) {
176
+ if ((0, typeguards_1.isObject)(packageContent[depType]) && 'iobroker.js-controller' in packageContent[depType]) {
177
+ // eslint-disable-next-line @typescript-eslint/only-throw-error
178
+ throw new chai_1.AssertionError(`iobroker.js-controller must not be listed in ${depType}, found "${packageContent[depType]['iobroker.js-controller']}"!`);
179
+ }
180
+ }
181
+ });
145
182
  });
146
183
  describe(`Check contents of io-package.json`, () => {
147
184
  beforeEach(function () {
@@ -253,22 +290,77 @@ function validatePackageFiles(adapterDir) {
253
290
  }
254
291
  });
255
292
  });
293
+ describe(`Validate JSON files`, () => {
294
+ // Validate base directory JSON files
295
+ describe(`Base directory JSON files`, () => {
296
+ for (const filename of ['package.json', 'io-package.json']) {
297
+ const filePath = path.join(adapterDir, filename);
298
+ if (fs.existsSync(filePath)) {
299
+ it(`${filename} contains valid JSON`, () => {
300
+ (0, chai_1.expect)(() => {
301
+ JSON.parse(fs.readFileSync(filePath, 'utf8'));
302
+ }, `${filename} contains invalid JSON!`).not.to.throw();
303
+ });
304
+ }
305
+ }
306
+ });
307
+ // Find all JSON and JSON5 files in admin/ directory (recursively)
308
+ const adminDir = path.join(adapterDir, 'admin');
309
+ const allAdminJsonFiles = findFiles(adminDir, /\.json$/);
310
+ const allAdminJson5Files = findFiles(adminDir, /\.json5$/);
311
+ // Split JSON files into admin/*.json and admin/i18n/**/*.json
312
+ // Exclude tsconfig.json as it may contain JSON5 syntax (comments, trailing commas)
313
+ const adminDirectJsonFiles = allAdminJsonFiles.filter(file => !file.includes(`${path.sep}i18n${path.sep}`) && !file.endsWith(`${path.sep}tsconfig.json`));
314
+ const i18nJsonFiles = allAdminJsonFiles.filter(file => file.includes(`${path.sep}i18n${path.sep}`));
315
+ if (adminDirectJsonFiles.length > 0) {
316
+ describe(`admin/*.json files`, () => {
317
+ for (const filePath of adminDirectJsonFiles) {
318
+ const relativePath = path.relative(adapterDir, filePath);
319
+ it(`${relativePath} contains valid JSON`, () => {
320
+ (0, chai_1.expect)(() => {
321
+ JSON.parse(fs.readFileSync(filePath, 'utf8'));
322
+ }, `${relativePath} contains invalid JSON!`).not.to.throw();
323
+ });
324
+ }
325
+ });
326
+ }
327
+ if (allAdminJson5Files.length > 0) {
328
+ describe(`admin/*.json5 files`, () => {
329
+ for (const filePath of allAdminJson5Files) {
330
+ const relativePath = path.relative(adapterDir, filePath);
331
+ it(`${relativePath} contains valid JSON5`, () => {
332
+ (0, chai_1.expect)(() => {
333
+ json5_1.default.parse(fs.readFileSync(filePath, 'utf8'));
334
+ }, `${relativePath} contains invalid JSON5!`).not.to.throw();
335
+ });
336
+ }
337
+ });
338
+ }
339
+ if (i18nJsonFiles.length > 0) {
340
+ describe(`admin/i18n/**/*.json files`, () => {
341
+ for (const filePath of i18nJsonFiles) {
342
+ const relativePath = path.relative(adapterDir, filePath);
343
+ it(`${relativePath} contains valid JSON`, () => {
344
+ (0, chai_1.expect)(() => {
345
+ JSON.parse(fs.readFileSync(filePath, 'utf8'));
346
+ }, `${relativePath} contains invalid JSON!`).not.to.throw();
347
+ });
348
+ }
349
+ });
350
+ }
351
+ });
352
+ });
353
+ describe(`Check additional files`, () => {
354
+ it('README.md exists', () => {
355
+ (0, chai_1.expect)(fs.existsSync(path.join(adapterDir, 'README.md')), `README.md is missing in the adapter dir. Please create it!`).to.be.true;
356
+ });
357
+ it('LICENSE exists or is present in the README.md', () => {
358
+ const licenseExists = fs.existsSync(path.join(adapterDir, 'LICENSE'));
359
+ if (licenseExists) {
360
+ return;
361
+ }
362
+ const readmeContent = fs.readFileSync(path.join(adapterDir, 'README.md'), 'utf8');
363
+ (0, chai_1.expect)(readmeContent).to.match(/## LICENSE/i, `The license should be in a file "LICENSE" or be included in "README.md" as a 2nd level headline!`);
364
+ });
256
365
  });
257
- // describe(`Check additional files`, () => {
258
- // it("README.md exists", () => {
259
- // expect(
260
- // fs.existsSync(path.join(adapterDir, "README.md")),
261
- // `README.md is missing in the adapter dir. Please create it!`,
262
- // ).to.be.true;
263
- // });
264
- // it("LICENSE exists or is present in the README.md", () => {
265
- // const licenseExists = fs.existsSync(path.join(adapterDir, "LICENSE"));
266
- // if (licenseExists) return;
267
- // const readmeContent = fs.readFileSync(path.join(adapterDir, "README.md"), "utf8");
268
- // expect(readmeContent).to.match(
269
- // /## LICENSE/i,
270
- // `The license should be in a file "LICENSE" or be included in "README.md" as a 2nd level headline!`,
271
- // );
272
- // });
273
- // });
274
366
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iobroker/testing",
3
- "version": "5.1.1",
3
+ "version": "5.2.2",
4
4
  "description": "Shared utilities for adapter and module testing in ioBroker",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -8,8 +8,8 @@
8
8
  "build/"
9
9
  ],
10
10
  "scripts": {
11
- "test": "mocha src/**/*.test.ts",
12
- "test:watch": "mocha src/**/*.test.ts --watch",
11
+ "test": "mocha \"src/**/*.test.ts\"",
12
+ "test:watch": "mocha \"src/**/*.test.ts\" --watch",
13
13
  "prebuild": "rimraf ./build",
14
14
  "build": "tsc -p tsconfig.build.json",
15
15
  "prewatch": "rimraf ./build",
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "repository": {
24
24
  "type": "git",
25
- "url": "git+https://github.com/AlCalzone/testing.git"
25
+ "url": "https://github.com/ioBroker/testing"
26
26
  },
27
27
  "publishConfig": {
28
28
  "access": "public"
@@ -39,21 +39,22 @@
39
39
  },
40
40
  "homepage": "https://github.com/AlCalzone/testing#readme",
41
41
  "devDependencies": {
42
- "@alcalzone/release-script": "^3.8.0",
43
- "@alcalzone/release-script-plugin-license": "^3.7.0",
42
+ "@alcalzone/release-script": "^5.0.0",
43
+ "@alcalzone/release-script-plugin-license": "^4.0.0",
44
44
  "@iobroker/adapter-core": "^3.3.2",
45
- "@iobroker/eslint-config": "^2.1.0",
45
+ "@iobroker/eslint-config": "^2.2.0",
46
46
  "@iobroker/types": "^7.0.7",
47
- "@tsconfig/node16": "^16.1.4",
47
+ "@tsconfig/node16": "^16.1.6",
48
48
  "@types/debug": "^4.1.12",
49
49
  "@types/fs-extra": "^11.0.4",
50
- "@types/node": "^24.3.0",
51
- "rimraf": "^6.0.1",
50
+ "@types/node": "^24.6.1",
51
+ "rimraf": "^6.1.0",
52
52
  "source-map-support": "^0.5.21",
53
53
  "ts-node": "^10.9.2",
54
- "typescript": "~5.9.2"
54
+ "typescript": "~5.9.3"
55
55
  },
56
56
  "dependencies": {
57
+ "@alcalzone/esbuild-register": "^2.5.1-1",
57
58
  "@types/chai": "^4.3.20",
58
59
  "@types/chai-as-promised": "^7.1.8",
59
60
  "@types/mocha": "^10.0.10",
@@ -62,9 +63,10 @@
62
63
  "alcalzone-shared": "~5.0.0",
63
64
  "chai": "^4.5.0",
64
65
  "chai-as-promised": "^7.1.2",
65
- "debug": "^4.4.1",
66
- "fs-extra": "^11.3.1",
67
- "mocha": "^11.7.1",
66
+ "debug": "^4.4.3",
67
+ "fs-extra": "^11.3.2",
68
+ "json5": "^2.2.3",
69
+ "mocha": "^11.7.3",
68
70
  "sinon": "^21.0.0",
69
71
  "sinon-chai": "^3.7.0"
70
72
  }