From 88a8b84a3e9c7d938523fa5169f25a43f81d9a87 Mon Sep 17 00:00:00 2001 From: "matthias@klein0r.de" Date: Sun, 20 Oct 2019 15:29:44 +0200 Subject: [PATCH] Applied latest adapter template --- .eslintrc.json | 35 ++ .gitignore | 8 + .npmignore | 32 +- .travis.yml | 23 +- .vscode/extensions.json | 6 + .vscode/settings.json | 5 + README.md | 3 + gulpfile.js | 871 ++++++++++++++++++--------------------- lib/adapter-config.d.ts | 16 + lib/tools.js | 91 ++++ main.test.js | 30 ++ package.json | 41 +- test/integration.js | 5 + test/lib/setup.js | 728 -------------------------------- test/mocha.custom.opts | 2 + test/mocha.setup.js | 14 + test/package.js | 5 + test/testAdapter.js | 140 ------- test/testPackageFiles.js | 91 ---- test/tsconfig.json | 9 + test/unit.js | 5 + tsconfig.json | 38 ++ 22 files changed, 749 insertions(+), 1449 deletions(-) create mode 100644 .eslintrc.json create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 lib/adapter-config.d.ts create mode 100644 lib/tools.js create mode 100644 main.test.js create mode 100644 test/integration.js delete mode 100644 test/lib/setup.js create mode 100644 test/mocha.custom.opts create mode 100644 test/mocha.setup.js create mode 100644 test/package.js delete mode 100644 test/testAdapter.js delete mode 100644 test/testPackageFiles.js create mode 100644 test/tsconfig.json create mode 100644 test/unit.js create mode 100644 tsconfig.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..ef39dc6 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,35 @@ +{ + "env": { + "es6": true, + "node": true, + "mocha": true + }, + "extends": "eslint:recommended", + "rules": { + "indent": [ + "error", + 4, + { + "SwitchCase": 1 + } + ], + "no-console": "off", + "no-var": "error", + "prefer-const": "error", + "quotes": [ + "error", + "single", + { + "avoidEscape": true, + "allowTemplateLiterals": true + } + ], + "semi": [ + "error", + "always" + ] + }, + "parserOptions": { + "ecmaVersion": 2018 + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7228c33..000d0ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,14 @@ .git .idea +*.code-workspace node_modules nbproject + +# npm package files +iobroker.*.tgz + +Thumbs.db + +# i18n intermediate files admin/i18n/flat.txt admin/i18n/*/flat.txt \ No newline at end of file diff --git a/.npmignore b/.npmignore index e966b4f..ed16ee5 100644 --- a/.npmignore +++ b/.npmignore @@ -1,10 +1,28 @@ -gulpfile.js -admin/i18n -tasks -node_modules -.idea .git -/node_modules -test +.idea +node_modules/ +nbproject/ +.vs*/ +*.code-workspace +Thumbs.db +gulpfile.js + +test/ +travis/ .travis.yml appveyor.yml +.travis.yaml +appveyor.yaml + +.eslintrc.json +.eslintrc.js + +# npm package files +iobroker.*.tgz +package-lock.json + +# i18n intermediate files +admin/i18n + +# maintenance scripts +maintenance/** \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 2456688..95eebe6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,11 @@ os: - linux - osx + - windows language: node_js node_js: - - '4' - - '6' - '8' - '10' -before_script: - - export NPMVERSION=$(echo "$($(which npm) -v)"|cut -c1) - - 'if [[ $NPMVERSION == 5 ]]; then npm install -g npm@5; fi' - - npm -v - - npm install winston@2.3.1 - - 'npm install https://github.com/ioBroker/ioBroker.js-controller/tarball/master --production' env: - CXX=g++-4.8 addons: @@ -20,4 +13,16 @@ addons: sources: - ubuntu-toolchain-r-test packages: - - g++-4.8 + - g++-4.9 +before_install: + - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then CC=gcc-4.9; fi' + - 'if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then CC=g++-4.9; fi' +before_script: + - export NPMVERSION=$(echo "$($(which npm) -v)"|cut -c1) + - 'if [[ $NPMVERSION == 5 ]]; then npm install -g npm; fi' + - npm -v +script: + - npm run test:package + - npm run test:unit + - export DEBUG=testing:* + - npm run test:integration diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..a7f9c3b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c356a61 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "eslint.enable": true, + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode" +} \ No newline at end of file diff --git a/README.md b/README.md index 360a05a..82cb4e9 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ [![Downloads](https://img.shields.io/npm/dm/iobroker.lametric.svg)](https://www.npmjs.com/package/iobroker.lametric) [![Stable](http://iobroker.live/badges/lametric-stable.svg)](http://iobroker.live/badges/lametric-stable.svg) [![installed](http://iobroker.live/badges/lametric-installed.svg)](http://iobroker.live/badges/lametric-installed.svg) +[![Dependency Status](https://img.shields.io/david/klein0r/iobroker.lametric.svg)](https://david-dm.org/klein0r/iobroker.lametric) +[![Known Vulnerabilities](https://snyk.io/test/github/klein0r/ioBroker.lametric/badge.svg)](https://snyk.io/test/github/klein0r/ioBroker.lametric) +[![Build Status](http://img.shields.io/travis/klein0r/ioBroker.lametric.svg)](https://travis-ci.org/klein0r/ioBroker.lametric) [![NPM](https://nodei.co/npm/iobroker.lametric.png?downloads=true)](https://nodei.co/npm/iobroker.lametric/) diff --git a/gulpfile.js b/gulpfile.js index ed5d775..b6dfa8d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,485 +1,434 @@ -'use strict'; - -const gulp = require('gulp'); -const fs = require('fs'); -const pkg = require('./package.json'); -const iopackage = require('./io-package.json'); -const version = (pkg && pkg.version) ? pkg.version : iopackage.common.version; -/*const appName = getAppName(); - -function getAppName() { - const parts = __dirname.replace(/\\/g, '/').split('/'); - return parts[parts.length - 1].split('.')[0].toLowerCase(); -} -*/ -const fileName = 'words.js'; -const languages = { - en: {}, - de: {}, - ru: {}, - pt: {}, - nl: {}, - fr: {}, - it: {}, - es: {}, - pl: {} +/*! + * ioBroker gulpfile + * Date: 2019-01-28 + */ +"use strict"; +const gulp = require("gulp"); +const fs = require("fs"); +const pkg = require("./package.json"); +const iopackage = require("./io-package.json"); +const version = (pkg && pkg.version) ? pkg.version : iopackage.common.version; +const fileName = "words.js"; +const EMPTY = ""; +const translate = require("${useTypeScript ? "./build/lib/tools" : "./lib/tools"}").translateText; +const languages = { + en: {}, + de: {}, + ru: {}, + pt: {}, + nl: {}, + fr: {}, + it: {}, + es: {}, + pl: {}, + "zh-cn": {} }; - function lang2data(lang, isFlat) { - let str = isFlat ? '' : '{\n'; - let count = 0; - for (const w in lang) { - if (lang.hasOwnProperty(w)) { - count++; - if (isFlat) { - str += (lang[w] === '' ? (isFlat[w] || w) : lang[w]) + '\n'; - } else { - const key = ' "' + w.replace(/"/g, '\\"') + '": '; - str += key + '"' + lang[w].replace(/"/g, '\\"') + '",\n'; - } - } - } - if (!count) return isFlat ? '' : '{\n}'; - if (isFlat) { - return str; - } else { - return str.substring(0, str.length - 2) + '\n}'; - } + let str = isFlat ? "" : "{\\n"; + let count = 0; + for (const w in lang) { + if (lang.hasOwnProperty(w)) { + count++; + if (isFlat) { + str += (lang[w] === "" ? (isFlat[w] || w) : lang[w]) + "\\n"; + } else { + const key = ' "' + w.replace(/"/g, '\\\\"') + '": '; + str += key + '"' + lang[w].replace(/"/g, '\\\\"') + '",\\n'; + } + } + } + if (!count) + return isFlat ? "" : "{\\n}"; + if (isFlat) { + return str; + } else { + return str.substring(0, str.length - 2) + "\\n}"; + } } - function readWordJs(src) { - try { - let words; - if (fs.existsSync(src + 'js/' + fileName)) { - words = fs.readFileSync(src + 'js/' + fileName).toString(); - } else { - words = fs.readFileSync(src + fileName).toString(); - } - - const lines = words.split(/\r\n|\r|\n/g); - let i = 0; - while (!lines[i].match(/^systemDictionary = {/)) { - i++; - } - lines.splice(0, i); - - // remove last empty lines - i = lines.length - 1; - while (!lines[i]) { - i--; - } - if (i < lines.length - 1) { - lines.splice(i + 1); - } - - lines[0] = lines[0].replace('systemDictionary = ', ''); - lines[lines.length - 1] = lines[lines.length - 1].trim().replace(/};$/, '}'); - words = lines.join('\n'); - const resultFunc = new Function('return ' + words + ';'); - - return resultFunc(); - } catch (e) { - return null; - } + try { + let words; + if (fs.existsSync(src + "js/" + fileName)) { + words = fs.readFileSync(src + "js/" + fileName).toString(); + } else { + words = fs.readFileSync(src + fileName).toString(); + } + words = words.substring(words.indexOf('{'), words.length); + words = words.substring(0, words.lastIndexOf(';')); + const resultFunc = new Function("return " + words + ";"); + return resultFunc(); + } catch (e) { + return null; + } } function padRight(text, totalLength) { - return text + (text.length < totalLength ? new Array(totalLength - text.length).join(' ') : ''); + return text + (text.length < totalLength ? new Array(totalLength - text.length).join(" ") : ""); } function writeWordJs(data, src) { - let text = ''; - text += '/*global systemDictionary:true */\n'; - text += '\'use strict\';\n\n'; - text += 'systemDictionary = {\n'; - for (const word in data) { - if (data.hasOwnProperty(word)) { - text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50); - let line = ''; - for (const lang in data[word]) { - if (data[word].hasOwnProperty(lang)) { - line += '"' + lang + '": "' + padRight(data[word][lang].replace(/"/g, '\\"') + '",', 50) + ' '; - } - } - if (line) { - line = line.trim(); - line = line.substring(0, line.length - 1); - } - text += line + '},\n'; - } - } - text += '};'; - if (fs.existsSync(src + 'js/' + fileName)) { - fs.writeFileSync(src + 'js/' + fileName, text); - } else { - fs.writeFileSync(src + '' + fileName, text); - } + let text = ""; + text += "/*global systemDictionary:true */\\n"; + text += "'use strict';\\n\\n"; + text += "systemDictionary = {\\n"; + for (const word in data) { + if (data.hasOwnProperty(word)) { + text += " " + padRight('"' + word.replace(/"/g, '\\\\"') + '": {', 50); + let line = ""; + for (const lang in data[word]) { + if (data[word].hasOwnProperty(lang)) { + line += '"' + lang + '": "' + padRight(data[word][lang].replace(/"/g, '\\\\"') + '",', 50) + " "; + } + } + if (line) { + line = line.trim(); + line = line.substring(0, line.length - 1); + } + text += line + "},\\n"; + } + } + text += "};"; + if (fs.existsSync(src + "js/" + fileName)) { + fs.writeFileSync(src + "js/" + fileName, text); + } else { + fs.writeFileSync(src + "" + fileName, text); + } } - -const EMPTY = ''; - function words2languages(src) { - const langs = Object.assign({}, languages); - const data = readWordJs(src); - if (data) { - for (const word in data) { - if (data.hasOwnProperty(word)) { - for (const lang in data[word]) { - if (data[word].hasOwnProperty(lang)) { - langs[lang][word] = data[word][lang]; - // pre-fill all other languages - for (const j in langs) { - if (langs.hasOwnProperty(j)) { - langs[j][word] = langs[j][word] || EMPTY; - } - } - } - } - } - } - if (!fs.existsSync(src + 'i18n/')) { - fs.mkdirSync(src + 'i18n/'); - } - for (const l in langs) { - if (!langs.hasOwnProperty(l)) continue; - const keys = Object.keys(langs[l]); - keys.sort(); - const obj = {}; - for (let k = 0; k < keys.length; k++) { - obj[keys[k]] = langs[l][keys[k]]; - } - if (!fs.existsSync(src + 'i18n/' + l)) { - fs.mkdirSync(src + 'i18n/' + l); - } - - fs.writeFileSync(src + 'i18n/' + l + '/translations.json', lang2data(obj)); - } - } else { - console.error('Cannot read or parse ' + fileName); - } + const langs = Object.assign({}, languages); + const data = readWordJs(src); + if (data) { + for (const word in data) { + if (data.hasOwnProperty(word)) { + for (const lang in data[word]) { + if (data[word].hasOwnProperty(lang)) { + langs[lang][word] = data[word][lang]; + // pre-fill all other languages + for (const j in langs) { + if (langs.hasOwnProperty(j)) { + langs[j][word] = langs[j][word] || EMPTY; + } + } + } + } + } + } + if (!fs.existsSync(src + "i18n/")) { + fs.mkdirSync(src + "i18n/"); + } + for (const l in langs) { + if (!langs.hasOwnProperty(l)) + continue; + const keys = Object.keys(langs[l]); + keys.sort(); + const obj = {}; + for (let k = 0; k < keys.length; k++) { + obj[keys[k]] = langs[l][keys[k]]; + } + if (!fs.existsSync(src + "i18n/" + l)) { + fs.mkdirSync(src + "i18n/" + l); + } + fs.writeFileSync(src + "i18n/" + l + "/translations.json", lang2data(obj)); + } + } else { + console.error("Cannot read or parse " + fileName); + } } function words2languagesFlat(src) { - const langs = Object.assign({}, languages); - const data = readWordJs(src); - if (data) { - for (const word in data) { - if (data.hasOwnProperty(word)) { - for (const lang in data[word]) { - if (data[word].hasOwnProperty(lang)) { - langs[lang][word] = data[word][lang]; - // pre-fill all other languages - for (const j in langs) { - if (langs.hasOwnProperty(j)) { - langs[j][word] = langs[j][word] || EMPTY; - } - } - } - } - } - } - const keys = Object.keys(langs.en); - keys.sort(); - for (const l in langs) { - if (!langs.hasOwnProperty(l)) continue; - const obj = {}; - for (let k = 0; k < keys.length; k++) { - obj[keys[k]] = langs[l][keys[k]]; - } - langs[l] = obj; - } - if (!fs.existsSync(src + 'i18n/')) { - fs.mkdirSync(src + 'i18n/'); - } - for (const ll in langs) { - if (!langs.hasOwnProperty(ll)) continue; - if (!fs.existsSync(src + 'i18n/' + ll)) { - fs.mkdirSync(src + 'i18n/' + ll); - } - - fs.writeFileSync(src + 'i18n/' + ll + '/flat.txt', lang2data(langs[ll], langs.en)); - } - fs.writeFileSync(src + 'i18n/flat.txt', keys.join('\n')); - } else { - console.error('Cannot read or parse ' + fileName); - } + const langs = Object.assign({}, languages); + const data = readWordJs(src); + if (data) { + for (const word in data) { + if (data.hasOwnProperty(word)) { + for (const lang in data[word]) { + if (data[word].hasOwnProperty(lang)) { + langs[lang][word] = data[word][lang]; + // pre-fill all other languages + for (const j in langs) { + if (langs.hasOwnProperty(j)) { + langs[j][word] = langs[j][word] || EMPTY; + } + } + } + } + } + } + const keys = Object.keys(langs.en); + keys.sort(); + for (const l in langs) { + if (!langs.hasOwnProperty(l)) + continue; + const obj = {}; + for (let k = 0; k < keys.length; k++) { + obj[keys[k]] = langs[l][keys[k]]; + } + langs[l] = obj; + } + if (!fs.existsSync(src + "i18n/")) { + fs.mkdirSync(src + "i18n/"); + } + for (const ll in langs) { + if (!langs.hasOwnProperty(ll)) + continue; + if (!fs.existsSync(src + "i18n/" + ll)) { + fs.mkdirSync(src + "i18n/" + ll); + } + fs.writeFileSync(src + "i18n/" + ll + "/flat.txt", lang2data(langs[ll], langs.en)); + } + fs.writeFileSync(src + "i18n/flat.txt", keys.join("\\n")); + } else { + console.error("Cannot read or parse " + fileName); + } } function languagesFlat2words(src) { - const dirs = fs.readdirSync(src + 'i18n/'); - const langs = {}; - const bigOne = {}; - const order = Object.keys(languages); - dirs.sort(function (a, b) { - const posA = order.indexOf(a); - const posB = order.indexOf(b); - if (posA === -1 && posB === -1) { - if (a > b) return 1; - if (a < b) return -1; - return 0; - } else if (posA === -1) { - return -1; - } else if (posB === -1) { - return 1; - } else { - if (posA > posB) return 1; - if (posA < posB) return -1; - return 0; - } - }); - const keys = fs.readFileSync(src + 'i18n/flat.txt').toString().split('\n'); - - for (let l = 0; l < dirs.length; l++) { - if (dirs[l] === 'flat.txt') continue; - const lang = dirs[l]; - const values = fs.readFileSync(src + 'i18n/' + lang + '/flat.txt').toString().split('\n'); - langs[lang] = {}; - keys.forEach(function (word, i) { - langs[lang][word] = values[i]; - }); - - const words = langs[lang]; - for (const word in words) { - if (words.hasOwnProperty(word)) { - bigOne[word] = bigOne[word] || {}; - if (words[word] !== EMPTY) { - bigOne[word][lang] = words[word]; - } - } - } - } - // read actual words.js - const aWords = readWordJs(); - - const temporaryIgnore = ['pt', 'fr', 'nl', 'flat.txt']; - if (aWords) { - // Merge words together - for (const w in aWords) { - if (aWords.hasOwnProperty(w)) { - if (!bigOne[w]) { - console.warn('Take from actual words.js: ' + w); - bigOne[w] = aWords[w] - } - dirs.forEach(function (lang) { - if (temporaryIgnore.indexOf(lang) !== -1) return; - if (!bigOne[w][lang]) { - console.warn('Missing "' + lang + '": ' + w); - } - }); - } - } - - } - - writeWordJs(bigOne, src); + const dirs = fs.readdirSync(src + "i18n/"); + const langs = {}; + const bigOne = {}; + const order = Object.keys(languages); + dirs.sort(function (a, b) { + const posA = order.indexOf(a); + const posB = order.indexOf(b); + if (posA === -1 && posB === -1) { + if (a > b) + return 1; + if (a < b) + return -1; + return 0; + } else if (posA === -1) { + return -1; + } else if (posB === -1) { + return 1; + } else { + if (posA > posB) + return 1; + if (posA < posB) + return -1; + return 0; + } + }); + const keys = fs.readFileSync(src + "i18n/flat.txt").toString().split("\\n"); + for (const lang of dirs) { + if (lang === "flat.txt") + continue; + const values = fs.readFileSync(src + "i18n/" + lang + "/flat.txt").toString().split("\\n"); + langs[lang] = {}; + keys.forEach(function (word, i) { + langs[lang][word] = values[i]; + }); + const words = langs[lang]; + for (const word in words) { + if (words.hasOwnProperty(word)) { + bigOne[word] = bigOne[word] || {}; + if (words[word] !== EMPTY) { + bigOne[word][lang] = words[word]; + } + } + } + } + // read actual words.js + const aWords = readWordJs(); + const temporaryIgnore = ["flat.txt"]; + if (aWords) { + // Merge words together + for (const w in aWords) { + if (aWords.hasOwnProperty(w)) { + if (!bigOne[w]) { + console.warn("Take from actual words.js: " + w); + bigOne[w] = aWords[w]; + } + dirs.forEach(function (lang) { + if (temporaryIgnore.indexOf(lang) !== -1) + return; + if (!bigOne[w][lang]) { + console.warn('Missing "' + lang + '": ' + w); + } + }); + } + } + } + writeWordJs(bigOne, src); } function languages2words(src) { - const dirs = fs.readdirSync(src + 'i18n/'); - const langs = {}; - const bigOne = {}; - const order = Object.keys(languages); - dirs.sort(function (a, b) { - const posA = order.indexOf(a); - const posB = order.indexOf(b); - if (posA === -1 && posB === -1) { - if (a > b) return 1; - if (a < b) return -1; - return 0; - } else if (posA === -1) { - return -1; - } else if (posB === -1) { - return 1; - } else { - if (posA > posB) return 1; - if (posA < posB) return -1; - return 0; - } - }); - for (let l = 0; l < dirs.length; l++) { - if (dirs[l] === 'flat.txt') continue; - const lang = dirs[l]; - langs[lang] = fs.readFileSync(src + 'i18n/' + lang + '/translations.json').toString(); - langs[lang] = JSON.parse(langs[lang]); - const words = langs[lang]; - for (const word in words) { - if (words.hasOwnProperty(word)) { - bigOne[word] = bigOne[word] || {}; - if (words[word] !== EMPTY) { - bigOne[word][lang] = words[word]; - } - } - } - } - // read actual words.js - const aWords = readWordJs(); - - const temporaryIgnore = ['pt', 'fr', 'nl', 'it']; - if (aWords) { - // Merge words together - for (const w in aWords) { - if (aWords.hasOwnProperty(w)) { - if (!bigOne[w]) { - console.warn('Take from actual words.js: ' + w); - bigOne[w] = aWords[w] - } - dirs.forEach(function (lang) { - if (temporaryIgnore.indexOf(lang) !== -1) return; - if (!bigOne[w][lang]) { - console.warn('Missing "' + lang + '": ' + w); - } - }); - } - } - - } - - writeWordJs(bigOne, src); + const dirs = fs.readdirSync(src + "i18n/"); + const langs = {}; + const bigOne = {}; + const order = Object.keys(languages); + dirs.sort(function (a, b) { + const posA = order.indexOf(a); + const posB = order.indexOf(b); + if (posA === -1 && posB === -1) { + if (a > b) + return 1; + if (a < b) + return -1; + return 0; + } else if (posA === -1) { + return -1; + } else if (posB === -1) { + return 1; + } else { + if (posA > posB) + return 1; + if (posA < posB) + return -1; + return 0; + } + }); + for (const lang of dirs) { + if (lang === "flat.txt") + continue; + langs[lang] = fs.readFileSync(src + "i18n/" + lang + "/translations.json").toString(); + langs[lang] = JSON.parse(langs[lang]); + const words = langs[lang]; + for (const word in words) { + if (words.hasOwnProperty(word)) { + bigOne[word] = bigOne[word] || {}; + if (words[word] !== EMPTY) { + bigOne[word][lang] = words[word]; + } + } + } + } + // read actual words.js + const aWords = readWordJs(); + const temporaryIgnore = ["flat.txt"]; + if (aWords) { + // Merge words together + for (const w in aWords) { + if (aWords.hasOwnProperty(w)) { + if (!bigOne[w]) { + console.warn("Take from actual words.js: " + w); + bigOne[w] = aWords[w]; + } + dirs.forEach(function (lang) { + if (temporaryIgnore.indexOf(lang) !== -1) + return; + if (!bigOne[w][lang]) { + console.warn('Missing "' + lang + '": ' + w); + } + }); + } + } + } + writeWordJs(bigOne, src); +} +async function translateNotExisting(obj, baseText, yandex) { + let t = obj['en']; + if (!t) { + t = baseText; + } + if (t) { + for (let l in languages) { + if (!obj[l]) { + const time = new Date().getTime(); + obj[l] = await translate(t, l, yandex); + console.log("en -> " + l + " " + (new Date().getTime() - time) + " ms"); + } + } + } } - -gulp.task('adminWords2languages', function (done) { - words2languages('./admin/'); - done(); +//TASKS +gulp.task("adminWords2languages", function (done) { + words2languages("./admin/"); + done(); }); - -gulp.task('adminWords2languagesFlat', function (done) { - words2languagesFlat('./admin/'); - done(); +gulp.task("adminWords2languagesFlat", function (done) { + words2languagesFlat("./admin/"); + done(); }); - -gulp.task('adminLanguagesFlat2words', function (done) { - languagesFlat2words('./admin/'); - done(); +gulp.task("adminLanguagesFlat2words", function (done) { + languagesFlat2words("./admin/"); + done(); }); - -gulp.task('adminLanguages2words', function (done) { - languages2words('./admin/'); - done(); +gulp.task("adminLanguages2words", function (done) { + languages2words("./admin/"); + done(); }); - - -gulp.task('updatePackages', function (done) { - iopackage.common.version = pkg.version; - iopackage.common.news = iopackage.common.news || {}; - if (!iopackage.common.news[pkg.version]) { - const news = iopackage.common.news; - const newNews = {}; - - newNews[pkg.version] = { - en: 'news', - de: 'neues', - ru: 'новое' - }; - iopackage.common.news = Object.assign(newNews, news); - } - fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4)); - done(); +gulp.task("updatePackages", function (done) { + iopackage.common.version = pkg.version; + iopackage.common.news = iopackage.common.news || {}; + if (!iopackage.common.news[pkg.version]) { + const news = iopackage.common.news; + const newNews = {}; + newNews[pkg.version] = { + en: "news", + de: "neues", + ru: "новое", + pt: "novidades", + nl: "nieuws", + fr: "nouvelles", + it: "notizie", + es: "noticias", + pl: "nowości", + "zh-cn": "新" + }; + iopackage.common.news = Object.assign(newNews, news); + } + fs.writeFileSync("io-package.json", JSON.stringify(iopackage, null, 4)); + done(); }); - -gulp.task('rename', function () { - let newname; - let author = '@@Author@@'; - let email = '@@email@@'; - for (let a = 0; a < process.argv.length; a++) { - if (process.argv[a] === '--name') { - newname = process.argv[a + 1] - } else if (process.argv[a] === '--email') { - email = process.argv[a + 1] - } else if (process.argv[a] === '--author') { - author = process.argv[a + 1] - } - } - - - console.log('Try to rename to "' + newname + '"'); - if (!newname) { - console.log('Please write the new template name, like: "gulp rename --name mywidgetset" --author "Author Name"'); - process.exit(); - } - if (newname.indexOf(' ') !== -1) { - console.log('Name may not have space in it.'); - process.exit(); - } - if (newname.toLowerCase() !== newname) { - console.log('Name must be lower case.'); - process.exit(); - } - if (fs.existsSync(__dirname + '/admin/template.png')) { - fs.renameSync(__dirname + '/admin/template.png', __dirname + '/admin/' + newname + '.png'); - } - if (fs.existsSync(__dirname + '/widgets/template.html')) { - fs.renameSync(__dirname + '/widgets/template.html', __dirname + '/widgets/' + newname + '.html'); - } - if (fs.existsSync(__dirname + '/widgets/template/js/template.js')) { - fs.renameSync(__dirname + '/widgets/template/js/template.js', __dirname + '/widgets/template/js/' + newname + '.js'); - } - if (fs.existsSync(__dirname + '/widgets/template')) { - fs.renameSync(__dirname + '/widgets/template', __dirname + '/widgets/' + newname); - } - const patterns = [ - { - match: /template/g, - replacement: newname - }, - { - match: /Template/g, - replacement: newname ? (newname[0].toUpperCase() + newname.substring(1)) : 'Template' - }, - { - match: /@@Author@@/g, - replacement: author - }, - { - match: /@@email@@/g, - replacement: email - } - ]; - const files = [ - __dirname + '/io-package.json', - __dirname + '/LICENSE', - __dirname + '/package.json', - __dirname + '/README.md', - __dirname + '/main.js', - __dirname + '/Gruntfile.js', - __dirname + '/widgets/' + newname +'.html', - __dirname + '/www/index.html', - __dirname + '/admin/index.html', - __dirname + '/admin/index_m.html', - __dirname + '/widgets/' + newname + '/js/' + newname +'.js', - __dirname + '/widgets/' + newname + '/css/style.css' - ]; - files.forEach(function (f) { - try { - if (fs.existsSync(f)) { - let data = fs.readFileSync(f).toString('utf-8'); - for (let r = 0; r < patterns.length; r++) { - data = data.replace(patterns[r].match, patterns[r].replacement); - } - fs.writeFileSync(f, data); - } - } catch (e) { - - } - }); +gulp.task("updateReadme", function (done) { + const readme = fs.readFileSync("README.md").toString(); + const pos = readme.indexOf("## Changelog\\n"); + if (pos !== -1) { + const readmeStart = readme.substring(0, pos + "## Changelog\\n".length); + const readmeEnd = readme.substring(pos + "## Changelog\\n".length); + if (readme.indexOf(version) === -1) { + const timestamp = new Date(); + const date = timestamp.getFullYear() + "-" + + ("0" + (timestamp.getMonth() + 1).toString(10)).slice(-2) + "-" + + ("0" + (timestamp.getDate()).toString(10)).slice(-2); + let news = ""; + if (iopackage.common.news && iopackage.common.news[pkg.version]) { + news += "* " + iopackage.common.news[pkg.version].en; + } + fs.writeFileSync("README.md", readmeStart + "### " + version + " (" + date + ")\\n" + (news ? news + "\\n\\n" : "\\n") + readmeEnd); + } + } + done(); }); - -gulp.task('updateReadme', function (done) { - const readme = fs.readFileSync('README.md').toString(); - const pos = readme.indexOf('## Changelog\n'); - if (pos !== -1) { - const readmeStart = readme.substring(0, pos + '## Changelog\n'.length); - const readmeEnd = readme.substring(pos + '## Changelog\n'.length); - - if (readme.indexOf(version) === -1) { - const timestamp = new Date(); - const date = timestamp.getFullYear() + '-' + - ('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' + - ('0' + (timestamp.getDate()).toString(10)).slice(-2); - - let news = ''; - if (iopackage.common.news && iopackage.common.news[pkg.version]) { - news += '* ' + iopackage.common.news[pkg.version].en; - } - - fs.writeFileSync('README.md', readmeStart + '### ' + version + ' (' + date + ')\n' + (news ? news + '\n\n' : '\n') + readmeEnd); - } - } - done(); +gulp.task('translate', async function (done) { + let yandex; + const i = process.argv.indexOf("--yandex"); + if (i > -1) { + yandex = process.argv[i + 1]; + } + if (iopackage && iopackage.common) { + if (iopackage.common.news) { + console.log("Translate News"); + for (let k in iopackage.common.news) { + console.log("News: " + k); + let nw = iopackage.common.news[k]; + await translateNotExisting(nw, null, yandex); + } + } + if (iopackage.common.titleLang) { + console.log("Translate Title"); + await translateNotExisting(iopackage.common.titleLang, iopackage.common.title, yandex); + } + if (iopackage.common.desc) { + console.log("Translate Description"); + await translateNotExisting(iopackage.common.desc, null, yandex); + } + if (fs.existsSync('./admin/i18n/en/translations.json')) { + let enTranslations = require('./admin/i18n/en/translations.json'); + for (let l in languages) { + console.log("Translate Text: " + l); + let existing = {}; + if (fs.existsSync('./admin/i18n/' + l + '/translations.json')) { + existing = require('./admin/i18n/' + l + '/translations.json'); + } + for (let t in enTranslations) { + if (!existing[t]) { + existing[t] = await translate(enTranslations[t], l, yandex); + } + } + if (!fs.existsSync('./admin/i18n/' + l + '/')) { + fs.mkdirSync('./admin/i18n/' + l + '/'); + } + fs.writeFileSync('./admin/i18n/' + l + '/translations.json', JSON.stringify(existing, null, 4)); + } + } + } + fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4)); }); - -gulp.task('default', gulp.parallel('updatePackages', 'updateReadme')); +gulp.task("translateAndUpdateWordsJS", gulp.series("translate", "adminLanguages2words", "adminWords2languages")); +gulp.task("default", gulp.series("updatePackages", "updateReadme")); \ No newline at end of file diff --git a/lib/adapter-config.d.ts b/lib/adapter-config.d.ts new file mode 100644 index 0000000..6fb7fe5 --- /dev/null +++ b/lib/adapter-config.d.ts @@ -0,0 +1,16 @@ +// This file extends the AdapterConfig type from "@types/iobroker" +// using the actual properties present in io-package.json +// in order to provide typings for adapter.config properties + +import { native } from "../io-package.json"; + +type _AdapterConfig = typeof native; + +// Augment the globally declared type ioBroker.AdapterConfig +declare global { + namespace ioBroker { + interface AdapterConfig extends _AdapterConfig { + // Do not enter anything here! + } + } +} \ No newline at end of file diff --git a/lib/tools.js b/lib/tools.js new file mode 100644 index 0000000..7d9e8b6 --- /dev/null +++ b/lib/tools.js @@ -0,0 +1,91 @@ +const axios = require("axios"); + +/** + * Tests whether the given variable is a real object and not an Array + * @param {any} it The variable to test + * @returns {it is Record} + */ +function isObject(it) { + // This is necessary because: + // typeof null === 'object' + // typeof [] === 'object' + // [] instanceof Object === true + return Object.prototype.toString.call(it) === "[object Object]"; +} + +/** + * Tests whether the given variable is really an Array + * @param {any} it The variable to test + * @returns {it is any[]} + */ +function isArray(it) { + if (typeof Array.isArray === "function") return Array.isArray(it); + return Object.prototype.toString.call(it) === "[object Array]"; +} + +/** + * Translates text to the target language. Automatically chooses the right translation API. + * @param {string} text The text to translate + * @param {string} targetLang The target languate + * @param {string} [yandexApiKey] The yandex API key. You can create one for free at https://translate.yandex.com/developers + * @returns {Promise} + */ +async function translateText(text, targetLang, yandexApiKey) { + if (targetLang === "en") { + return text; + } + if (yandexApiKey) { + return await translateYandex(text, targetLang, yandexApiKey); + } else { + return await translateGoogle(text, targetLang); + } +} + +/** + * Translates text with Yandex API + * @param {string} text The text to translate + * @param {string} targetLang The target languate + * @param {string} [apiKey] The yandex API key. You can create one for free at https://translate.yandex.com/developers + * @returns {Promise} + */ +async function translateYandex(text, targetLang, apiKey) { + if (targetLang === "zh-cn") { + targetLang = "zh"; + } + try { + const url = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${apiKey}&text=${encodeURIComponent(text)}&lang=en-${targetLang}`; + const response = await axios({url, timeout: 15000}); + if (response.data && response.data["text"]) { + return response.data["text"][0]; + } + throw new Error("Invalid response for translate request"); + } catch (e) { + throw new Error(`Could not translate to "${targetLang}": ${e}`); + } +} + +/** + * Translates text with Google API + * @param {string} text The text to translate + * @param {string} targetLang The target languate + * @returns {Promise} + */ +async function translateGoogle(text, targetLang) { + try { + const url = `http://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}&ie=UTF-8&oe=UTF-8`; + const response = await axios({url, timeout: 15000}); + if (isArray(response.data)) { + // we got a valid response + return response.data[0][0][0]; + } + throw new Error("Invalid response for translate request"); + } catch (e) { + throw new Error(`Could not translate to "${targetLang}": ${e}`); + } +} + +module.exports = { + isArray, + isObject, + translateText +}; \ No newline at end of file diff --git a/main.test.js b/main.test.js new file mode 100644 index 0000000..6f54028 --- /dev/null +++ b/main.test.js @@ -0,0 +1,30 @@ +"use strict"; + +/** + * This is a dummy TypeScript test file using chai and mocha + * + * It's automatically excluded from npm and its build output is excluded from both git and npm. + * It is advised to test all your modules with accompanying *.test.js-files + */ + +// tslint:disable:no-unused-expression + +const { expect } = require("chai"); +// import { functionToTest } from "./moduleToTest"; + +describe("module to test => function to test", () => { + // initializing logic + const expected = 5; + + it(`should return ${expected}`, () => { + const result = 5; + // assign result a value from functionToTest + expect(result).to.equal(expected); + // or using the should() syntax + result.should.equal(expected); + }); + // ... more tests => it + +}); + +// ... more test suites => describe \ No newline at end of file diff --git a/package.json b/package.json index cc87222..8e171bf 100644 --- a/package.json +++ b/package.json @@ -1,40 +1,55 @@ { "name": "iobroker.lametric", "version": "0.0.5", - "description": "ioBroker LaMetric", + "description": "ioBroker LaMetric Adapter", "author": { "name": "Matthias Kleine", "email": "info@haus-automatisierung.com" }, - "contributors": [ - { - "name": "Matthias Kleine", - "email": "info@haus-automatisierung.com" - } - ], "homepage": "https://github.com/klein0r/ioBroker.lametric", "license": "MIT", "keywords": [ "ioBroker", - "lametric", "Smart Home", - "home automation" + "home automation", + "lametric" ], "repository": { "type": "git", "url": "https://github.com/klein0r/ioBroker.lametric" }, "dependencies": { + "@iobroker/adapter-core": "^1.0.3", "request": "^2.88.0" }, "devDependencies": { - "gulp": "^4.0.0", - "mocha": "^5.2.0", - "chai": "^4.2.0" + "@iobroker/testing": "^1.3.0", + "@types/chai": "^4.2.3", + "@types/chai-as-promised": "^7.1.2", + "@types/gulp": "^4.0.6", + "@types/mocha": "^5.2.7", + "@types/node": "^10.14.22", + "@types/proxyquire": "^1.3.28", + "@types/sinon": "^7.5.0", + "@types/sinon-chai": "^3.2.3", + "axios": "^0.19.0", + "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", + "eslint": "^6.5.1", + "gulp": "^4.0.2", + "mocha": "^6.2.2", + "proxyquire": "^2.1.3", + "sinon": "^7.5.0", + "sinon-chai": "^3.3.0" }, "main": "main.js", "scripts": { - "test": "node node_modules/mocha/bin/mocha --exit" + "test:js": "mocha --opts test/mocha.custom.opts", + "test:package": "mocha test/package --exit", + "test:unit": "mocha test/unit --exit", + "test:integration": "mocha test/integration --exit", + "test": "npm run test:js && npm run test:package", + "lint": "eslint" }, "bugs": { "url": "https://github.com/klein0r/ioBroker.lametric/issues" diff --git a/test/integration.js b/test/integration.js new file mode 100644 index 0000000..1b3453e --- /dev/null +++ b/test/integration.js @@ -0,0 +1,5 @@ +const path = require('path'); +const { tests } = require('@iobroker/testing'); + +// Run integration tests - See https://github.com/ioBroker/testing for a detailed explanation and further options +tests.integration(path.join(__dirname, '..')); diff --git a/test/lib/setup.js b/test/lib/setup.js deleted file mode 100644 index 16857ed..0000000 --- a/test/lib/setup.js +++ /dev/null @@ -1,728 +0,0 @@ -/* jshint -W097 */// jshint strict:false -/*jslint node: true */ -// check if tmp directory exists -var fs = require('fs'); -var path = require('path'); -var child_process = require('child_process'); -var rootDir = path.normalize(__dirname + '/../../'); -var pkg = require(rootDir + 'package.json'); -var debug = typeof v8debug === 'object'; -pkg.main = pkg.main || 'main.js'; - -var adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/'); -adapterName = adapterName[adapterName.length - 2]; -var adapterStarted = false; - -function getAppName() { - var parts = __dirname.replace(/\\/g, '/').split('/'); - return parts[parts.length - 3].split('.')[0]; -} - -var appName = getAppName().toLowerCase(); - -var objects; -var states; - -var pid = null; - -function copyFileSync(source, target) { - - var targetFile = target; - - //if target is a directory a new file with the same name will be created - if (fs.existsSync(target)) { - if ( fs.lstatSync( target ).isDirectory() ) { - targetFile = path.join(target, path.basename(source)); - } - } - - try { - fs.writeFileSync(targetFile, fs.readFileSync(source)); - } - catch (err) { - console.log("file copy error: " +source +" -> " + targetFile + " (error ignored)"); - } -} - -function copyFolderRecursiveSync(source, target, ignore) { - var files = []; - - var base = path.basename(source); - if (base === adapterName) { - base = pkg.name; - } - //check if folder needs to be created or integrated - var targetFolder = path.join(target, base); - if (!fs.existsSync(targetFolder)) { - fs.mkdirSync(targetFolder); - } - - //copy - if (fs.lstatSync(source).isDirectory()) { - files = fs.readdirSync(source); - files.forEach(function (file) { - if (ignore && ignore.indexOf(file) !== -1) { - return; - } - - var curSource = path.join(source, file); - var curTarget = path.join(targetFolder, file); - if (fs.lstatSync(curSource).isDirectory()) { - // ignore grunt files - if (file.indexOf('grunt') !== -1) return; - if (file === 'chai') return; - if (file === 'mocha') return; - copyFolderRecursiveSync(curSource, targetFolder, ignore); - } else { - copyFileSync(curSource, curTarget); - } - }); - } -} - -if (!fs.existsSync(rootDir + 'tmp')) { - fs.mkdirSync(rootDir + 'tmp'); -} - -function storeOriginalFiles() { - console.log('Store original files...'); - var dataDir = rootDir + 'tmp/' + appName + '-data/'; - - var f = fs.readFileSync(dataDir + 'objects.json'); - var objects = JSON.parse(f.toString()); - if (objects['system.adapter.admin.0'] && objects['system.adapter.admin.0'].common) { - objects['system.adapter.admin.0'].common.enabled = false; - } - if (objects['system.adapter.admin.1'] && objects['system.adapter.admin.1'].common) { - objects['system.adapter.admin.1'].common.enabled = false; - } - - fs.writeFileSync(dataDir + 'objects.json.original', JSON.stringify(objects)); - try { - f = fs.readFileSync(dataDir + 'states.json'); - fs.writeFileSync(dataDir + 'states.json.original', f); - } - catch (err) { - console.log('no states.json found - ignore'); - } -} - -function restoreOriginalFiles() { - console.log('restoreOriginalFiles...'); - var dataDir = rootDir + 'tmp/' + appName + '-data/'; - - var f = fs.readFileSync(dataDir + 'objects.json.original'); - fs.writeFileSync(dataDir + 'objects.json', f); - try { - f = fs.readFileSync(dataDir + 'states.json.original'); - fs.writeFileSync(dataDir + 'states.json', f); - } - catch (err) { - console.log('no states.json.original found - ignore'); - } - -} - -function checkIsAdapterInstalled(cb, counter, customName) { - customName = customName || pkg.name.split('.').pop(); - counter = counter || 0; - var dataDir = rootDir + 'tmp/' + appName + '-data/'; - console.log('checkIsAdapterInstalled...'); - - try { - var f = fs.readFileSync(dataDir + 'objects.json'); - var objects = JSON.parse(f.toString()); - if (objects['system.adapter.' + customName + '.0']) { - console.log('checkIsAdapterInstalled: ready!'); - setTimeout(function () { - if (cb) cb(); - }, 100); - return; - } else { - console.warn('checkIsAdapterInstalled: still not ready'); - } - } catch (err) { - - } - - if (counter > 20) { - console.error('checkIsAdapterInstalled: Cannot install!'); - if (cb) cb('Cannot install'); - } else { - console.log('checkIsAdapterInstalled: wait...'); - setTimeout(function() { - checkIsAdapterInstalled(cb, counter + 1); - }, 1000); - } -} - -function checkIsControllerInstalled(cb, counter) { - counter = counter || 0; - var dataDir = rootDir + 'tmp/' + appName + '-data/'; - - console.log('checkIsControllerInstalled...'); - try { - var f = fs.readFileSync(dataDir + 'objects.json'); - var objects = JSON.parse(f.toString()); - if (objects['system.adapter.admin.0']) { - console.log('checkIsControllerInstalled: installed!'); - setTimeout(function () { - if (cb) cb(); - }, 100); - return; - } - } catch (err) { - - } - - if (counter > 20) { - console.log('checkIsControllerInstalled: Cannot install!'); - if (cb) cb('Cannot install'); - } else { - console.log('checkIsControllerInstalled: wait...'); - setTimeout(function() { - checkIsControllerInstalled(cb, counter + 1); - }, 1000); - } -} - -function installAdapter(customName, cb) { - if (typeof customName === 'function') { - cb = customName; - customName = null; - } - customName = customName || pkg.name.split('.').pop(); - console.log('Install adapter...'); - var startFile = 'node_modules/' + appName + '.js-controller/' + appName + '.js'; - // make first install - if (debug) { - child_process.execSync('node ' + startFile + ' add ' + customName + ' --enabled false', { - cwd: rootDir + 'tmp', - stdio: [0, 1, 2] - }); - checkIsAdapterInstalled(function (error) { - if (error) console.error(error); - console.log('Adapter installed.'); - if (cb) cb(); - }); - } else { - // add controller - var _pid = child_process.fork(startFile, ['add', customName, '--enabled', 'false'], { - cwd: rootDir + 'tmp', - stdio: [0, 1, 2, 'ipc'] - }); - - waitForEnd(_pid, function () { - checkIsAdapterInstalled(function (error) { - if (error) console.error(error); - console.log('Adapter installed.'); - if (cb) cb(); - }); - }); - } -} - -function waitForEnd(_pid, cb) { - if (!_pid) { - cb(-1, -1); - return; - } - _pid.on('exit', function (code, signal) { - if (_pid) { - _pid = null; - cb(code, signal); - } - }); - _pid.on('close', function (code, signal) { - if (_pid) { - _pid = null; - cb(code, signal); - } - }); -} - -function installJsController(cb) { - console.log('installJsController...'); - if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller') || - !fs.existsSync(rootDir + 'tmp/' + appName + '-data')) { - // try to detect appName.js-controller in node_modules/appName.js-controller - // travis CI installs js-controller into node_modules - if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller')) { - console.log('installJsController: no js-controller => copy it from "' + rootDir + 'node_modules/' + appName + '.js-controller"'); - // copy all - // stop controller - console.log('Stop controller if running...'); - var _pid; - if (debug) { - // start controller - _pid = child_process.exec('node ' + appName + '.js stop', { - cwd: rootDir + 'node_modules/' + appName + '.js-controller', - stdio: [0, 1, 2] - }); - } else { - _pid = child_process.fork(appName + '.js', ['stop'], { - cwd: rootDir + 'node_modules/' + appName + '.js-controller', - stdio: [0, 1, 2, 'ipc'] - }); - } - - waitForEnd(_pid, function () { - // copy all files into - if (!fs.existsSync(rootDir + 'tmp')) fs.mkdirSync(rootDir + 'tmp'); - if (!fs.existsSync(rootDir + 'tmp/node_modules')) fs.mkdirSync(rootDir + 'tmp/node_modules'); - - if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')){ - console.log('Copy js-controller...'); - copyFolderRecursiveSync(rootDir + 'node_modules/' + appName + '.js-controller', rootDir + 'tmp/node_modules/'); - } - - console.log('Setup js-controller...'); - var __pid; - if (debug) { - // start controller - _pid = child_process.exec('node ' + appName + '.js setup first --console', { - cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', - stdio: [0, 1, 2] - }); - } else { - __pid = child_process.fork(appName + '.js', ['setup', 'first', '--console'], { - cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', - stdio: [0, 1, 2, 'ipc'] - }); - } - waitForEnd(__pid, function () { - checkIsControllerInstalled(function () { - // change ports for object and state DBs - var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json'); - config.objects.port = 19001; - config.states.port = 19000; - fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2)); - console.log('Setup finished.'); - - copyAdapterToController(); - - installAdapter(function () { - storeOriginalFiles(); - if (cb) cb(true); - }); - }); - }); - }); - } else { - // check if port 9000 is free, else admin adapter will be added to running instance - var client = new require('net').Socket(); - client.connect(9000, '127.0.0.1', function() { - console.error('Cannot initiate fisrt run of test, because one instance of application is running on this PC. Stop it and repeat.'); - process.exit(0); - }); - - setTimeout(function () { - client.destroy(); - if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')) { - console.log('installJsController: no js-controller => install from git'); - - child_process.execSync('npm install https://github.com/' + appName + '/' + appName + '.js-controller/tarball/master --prefix ./ --production', { - cwd: rootDir + 'tmp/', - stdio: [0, 1, 2] - }); - } else { - console.log('Setup js-controller...'); - var __pid; - if (debug) { - // start controller - child_process.exec('node ' + appName + '.js setup first', { - cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', - stdio: [0, 1, 2] - }); - } else { - child_process.fork(appName + '.js', ['setup', 'first'], { - cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller', - stdio: [0, 1, 2, 'ipc'] - }); - } - } - - // let npm install admin and run setup - checkIsControllerInstalled(function () { - var _pid; - - if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller/' + appName + '.js')) { - _pid = child_process.fork(appName + '.js', ['stop'], { - cwd: rootDir + 'node_modules/' + appName + '.js-controller', - stdio: [0, 1, 2, 'ipc'] - }); - } - - waitForEnd(_pid, function () { - // change ports for object and state DBs - var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json'); - config.objects.port = 19001; - config.states.port = 19000; - fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2)); - - copyAdapterToController(); - - installAdapter(function () { - storeOriginalFiles(); - if (cb) cb(true); - }); - }); - }); - }, 1000); - } - } else { - setTimeout(function () { - console.log('installJsController: js-controller installed'); - if (cb) cb(false); - }, 0); - } -} - -function copyAdapterToController() { - console.log('Copy adapter...'); - // Copy adapter to tmp/node_modules/appName.adapter - copyFolderRecursiveSync(rootDir, rootDir + 'tmp/node_modules/', ['.idea', 'test', 'tmp', '.git', appName + '.js-controller']); - console.log('Adapter copied.'); -} - -function clearControllerLog() { - var dirPath = rootDir + 'tmp/log'; - var files; - try { - if (fs.existsSync(dirPath)) { - console.log('Clear controller log...'); - files = fs.readdirSync(dirPath); - } else { - console.log('Create controller log directory...'); - files = []; - fs.mkdirSync(dirPath); - } - } catch(e) { - console.error('Cannot read "' + dirPath + '"'); - return; - } - if (files.length > 0) { - try { - for (var i = 0; i < files.length; i++) { - var filePath = dirPath + '/' + files[i]; - fs.unlinkSync(filePath); - } - console.log('Controller log cleared'); - } catch (err) { - console.error('cannot clear log: ' + err); - } - } -} - -function clearDB() { - var dirPath = rootDir + 'tmp/iobroker-data/sqlite'; - var files; - try { - if (fs.existsSync(dirPath)) { - console.log('Clear sqlite DB...'); - files = fs.readdirSync(dirPath); - } else { - console.log('Create controller log directory...'); - files = []; - fs.mkdirSync(dirPath); - } - } catch(e) { - console.error('Cannot read "' + dirPath + '"'); - return; - } - if (files.length > 0) { - try { - for (var i = 0; i < files.length; i++) { - var filePath = dirPath + '/' + files[i]; - fs.unlinkSync(filePath); - } - console.log('Clear sqlite DB'); - } catch (err) { - console.error('cannot clear DB: ' + err); - } - } -} - -function setupController(cb) { - installJsController(function (isInited) { - clearControllerLog(); - clearDB(); - - if (!isInited) { - restoreOriginalFiles(); - copyAdapterToController(); - } - // read system.config object - var dataDir = rootDir + 'tmp/' + appName + '-data/'; - - var objs; - try { - objs = fs.readFileSync(dataDir + 'objects.json'); - objs = JSON.parse(objs); - } - catch (e) { - console.log('ERROR reading/parsing system configuration. Ignore'); - objs = {'system.config': {}}; - } - if (!objs || !objs['system.config']) { - objs = {'system.config': {}}; - } - - if (cb) cb(objs['system.config']); - }); -} - -function startAdapter(objects, states, callback) { - if (adapterStarted) { - console.log('Adapter already started ...'); - if (callback) callback(objects, states); - return; - } - adapterStarted = true; - console.log('startAdapter...'); - if (fs.existsSync(rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main)) { - try { - if (debug) { - // start controller - pid = child_process.exec('node node_modules/' + pkg.name + '/' + pkg.main + ' --console silly', { - cwd: rootDir + 'tmp', - stdio: [0, 1, 2] - }); - } else { - // start controller - pid = child_process.fork('node_modules/' + pkg.name + '/' + pkg.main, ['--console', 'silly'], { - cwd: rootDir + 'tmp', - stdio: [0, 1, 2, 'ipc'] - }); - } - } catch (error) { - console.error(JSON.stringify(error)); - } - } else { - console.error('Cannot find: ' + rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main); - } - if (callback) callback(objects, states); -} - -function startController(isStartAdapter, onObjectChange, onStateChange, callback) { - if (typeof isStartAdapter === 'function') { - callback = onStateChange; - onStateChange = onObjectChange; - onObjectChange = isStartAdapter; - isStartAdapter = true; - } - - if (onStateChange === undefined) { - callback = onObjectChange; - onObjectChange = undefined; - } - - if (pid) { - console.error('Controller is already started!'); - } else { - console.log('startController...'); - adapterStarted = false; - var isObjectConnected; - var isStatesConnected; - - var Objects = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/objects/objectsInMemServer'); - objects = new Objects({ - connection: { - "type" : "file", - "host" : "127.0.0.1", - "port" : 19001, - "user" : "", - "pass" : "", - "noFileCache": false, - "connectTimeout": 2000 - }, - logger: { - silly: function (msg) { - console.log(msg); - }, - debug: function (msg) { - console.log(msg); - }, - info: function (msg) { - console.log(msg); - }, - warn: function (msg) { - console.warn(msg); - }, - error: function (msg) { - console.error(msg); - } - }, - connected: function () { - isObjectConnected = true; - if (isStatesConnected) { - console.log('startController: started!'); - if (isStartAdapter) { - startAdapter(objects, states, callback); - } else { - if (callback) { - callback(objects, states); - callback = null; - } - } - } - }, - change: onObjectChange - }); - - // Just open in memory DB itself - var States = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/states/statesInMemServer'); - states = new States({ - connection: { - type: 'file', - host: '127.0.0.1', - port: 19000, - options: { - auth_pass: null, - retry_max_delay: 15000 - } - }, - logger: { - silly: function (msg) { - console.log(msg); - }, - debug: function (msg) { - console.log(msg); - }, - info: function (msg) { - console.log(msg); - }, - warn: function (msg) { - console.log(msg); - }, - error: function (msg) { - console.log(msg); - } - }, - connected: function () { - isStatesConnected = true; - if (isObjectConnected) { - console.log('startController: started!!'); - if (isStartAdapter) { - startAdapter(objects, states, callback); - } else { - if (callback) { - callback(objects, states); - callback = null; - } - } - } - }, - change: onStateChange - }); - } -} - -function stopAdapter(cb) { - if (!pid) { - console.error('Controller is not running!'); - if (cb) { - setTimeout(function () { - cb(false); - }, 0); - } - } else { - adapterStarted = false; - pid.on('exit', function (code, signal) { - if (pid) { - console.log('child process terminated due to receipt of signal ' + signal); - if (cb) cb(); - pid = null; - } - }); - - pid.on('close', function (code, signal) { - if (pid) { - if (cb) cb(); - pid = null; - } - }); - - pid.kill('SIGTERM'); - } -} - -function _stopController() { - if (objects) { - objects.destroy(); - objects = null; - } - if (states) { - states.destroy(); - states = null; - } -} - -function stopController(cb) { - var timeout; - if (objects) { - console.log('Set system.adapter.' + pkg.name + '.0'); - objects.setObject('system.adapter.' + pkg.name + '.0', { - common:{ - enabled: false - } - }); - } - - stopAdapter(function () { - if (timeout) { - clearTimeout(timeout); - timeout = null; - } - - _stopController(); - - if (cb) { - cb(true); - cb = null; - } - }); - - timeout = setTimeout(function () { - timeout = null; - console.log('child process NOT terminated'); - - _stopController(); - - if (cb) { - cb(false); - cb = null; - } - pid = null; - }, 5000); -} - -// Setup the adapter -function setAdapterConfig(common, native, instance) { - var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString()); - var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0); - if (common) objects[id].common = common; - if (native) objects[id].native = native; - fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/objects.json', JSON.stringify(objects)); -} - -// Read config of the adapter -function getAdapterConfig(instance) { - var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString()); - var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0); - return objects[id]; -} - -if (typeof module !== undefined && module.parent) { - module.exports.getAdapterConfig = getAdapterConfig; - module.exports.setAdapterConfig = setAdapterConfig; - module.exports.startController = startController; - module.exports.stopController = stopController; - module.exports.setupController = setupController; - module.exports.stopAdapter = stopAdapter; - module.exports.startAdapter = startAdapter; - module.exports.installAdapter = installAdapter; - module.exports.appName = appName; - module.exports.adapterName = adapterName; - module.exports.adapterStarted = adapterStarted; -} diff --git a/test/mocha.custom.opts b/test/mocha.custom.opts new file mode 100644 index 0000000..703f749 --- /dev/null +++ b/test/mocha.custom.opts @@ -0,0 +1,2 @@ +--require test/mocha.setup.js +{!(node_modules|test)/**/*.test.js,*.test.js,test/**/test!(PackageFiles|Startup).js} \ No newline at end of file diff --git a/test/mocha.setup.js b/test/mocha.setup.js new file mode 100644 index 0000000..15b9051 --- /dev/null +++ b/test/mocha.setup.js @@ -0,0 +1,14 @@ +// Don't silently swallow unhandled rejections +process.on('unhandledRejection', (e) => { + throw e; +}); + +// enable the should interface with sinon +// and load chai-as-promised and sinon-chai by default +const sinonChai = require('sinon-chai'); +const chaiAsPromised = require('chai-as-promised'); +const { should, use } = require('chai'); + +should(); +use(sinonChai); +use(chaiAsPromised); \ No newline at end of file diff --git a/test/package.js b/test/package.js new file mode 100644 index 0000000..38eacc8 --- /dev/null +++ b/test/package.js @@ -0,0 +1,5 @@ +const path = require('path'); +const { tests } = require('@iobroker/testing'); + +// Validate the package files +tests.packageFiles(path.join(__dirname, '..')); diff --git a/test/testAdapter.js b/test/testAdapter.js deleted file mode 100644 index ae9c289..0000000 --- a/test/testAdapter.js +++ /dev/null @@ -1,140 +0,0 @@ -/* jshint -W097 */// jshint strict:false -/*jslint node: true */ -var expect = require('chai').expect; -var setup = require(__dirname + '/lib/setup'); - -var objects = null; -var states = null; -var onStateChanged = null; -var onObjectChanged = null; -var sendToID = 1; - -var adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.')+1); - -function checkConnectionOfAdapter(cb, counter) { - counter = counter || 0; - console.log('Try check #' + counter); - if (counter > 30) { - if (cb) cb('Cannot check connection'); - return; - } - - states.getState('system.adapter.' + adapterShortName + '.0.alive', function (err, state) { - if (err) console.error(err); - if (state && state.val) { - if (cb) cb(); - } else { - setTimeout(function () { - checkConnectionOfAdapter(cb, counter + 1); - }, 1000); - } - }); -} - -function checkValueOfState(id, value, cb, counter) { - counter = counter || 0; - if (counter > 20) { - if (cb) cb('Cannot check value Of State ' + id); - return; - } - - states.getState(id, function (err, state) { - if (err) console.error(err); - if (value === null && !state) { - if (cb) cb(); - } else - if (state && (value === undefined || state.val === value)) { - if (cb) cb(); - } else { - setTimeout(function () { - checkValueOfState(id, value, cb, counter + 1); - }, 500); - } - }); -} - -function sendTo(target, command, message, callback) { - onStateChanged = function (id, state) { - if (id === 'messagebox.system.adapter.test.0') { - callback(state.message); - } - }; - - states.pushMessage('system.adapter.' + target, { - command: command, - message: message, - from: 'system.adapter.test.0', - callback: { - message: message, - id: sendToID++, - ack: false, - time: (new Date()).getTime() - } - }); -} - -describe('Test ' + adapterShortName + ' adapter', function() { - before('Test ' + adapterShortName + ' adapter: Start js-controller', function (_done) { - this.timeout(600000); // because of first install from npm - - setup.setupController(function () { - var config = setup.getAdapterConfig(); - // enable adapter - config.common.enabled = true; - config.common.loglevel = 'debug'; - - //config.native.dbtype = 'sqlite'; - - setup.setAdapterConfig(config.common, config.native); - - setup.startController(true, function(id, obj) {}, function (id, state) { - if (onStateChanged) onStateChanged(id, state); - }, - function (_objects, _states) { - objects = _objects; - states = _states; - _done(); - }); - }); - }); - -/* - ENABLE THIS WHEN ADAPTER RUNS IN DEAMON MODE TO CHECK THAT IT HAS STARTED SUCCESSFULLY -*/ - it('Test ' + adapterShortName + ' adapter: Check if adapter started', function (done) { - this.timeout(60000); - checkConnectionOfAdapter(function (res) { - if (res) console.log(res); - expect(res).not.to.be.equal('Cannot check connection'); - objects.setObject('system.adapter.test.0', { - common: { - - }, - type: 'instance' - }, - function () { - states.subscribeMessage('system.adapter.test.0'); - done(); - }); - }); - }); -/**/ - -/* - PUT YOUR OWN TESTS HERE USING - it('Testname', function ( done) { - ... - }); - - You can also use "sendTo" method to send messages to the started adapter -*/ - - after('Test ' + adapterShortName + ' adapter: Stop js-controller', function (done) { - this.timeout(10000); - - setup.stopController(function (normalTerminated) { - console.log('Adapter normal terminated: ' + normalTerminated); - done(); - }); - }); -}); diff --git a/test/testPackageFiles.js b/test/testPackageFiles.js deleted file mode 100644 index c600a60..0000000 --- a/test/testPackageFiles.js +++ /dev/null @@ -1,91 +0,0 @@ -/* jshint -W097 */ -/* jshint strict:false */ -/* jslint node: true */ -/* jshint expr: true */ -var expect = require('chai').expect; -var fs = require('fs'); - -describe('Test package.json and io-package.json', function() { - it('Test package files', function (done) { - console.log(); - - var fileContentIOPackage = fs.readFileSync(__dirname + '/../io-package.json', 'utf8'); - var ioPackage = JSON.parse(fileContentIOPackage); - - var fileContentNPMPackage = fs.readFileSync(__dirname + '/../package.json', 'utf8'); - var npmPackage = JSON.parse(fileContentNPMPackage); - - expect(ioPackage).to.be.an('object'); - expect(npmPackage).to.be.an('object'); - - expect(ioPackage.common.version, 'ERROR: Version number in io-package.json needs to exist').to.exist; - expect(npmPackage.version, 'ERROR: Version number in package.json needs to exist').to.exist; - - expect(ioPackage.common.version, 'ERROR: Version numbers in package.json and io-package.json needs to match').to.be.equal(npmPackage.version); - - if (!ioPackage.common.news || !ioPackage.common.news[ioPackage.common.version]) { - console.log('WARNING: No news entry for current version exists in io-package.json, no rollback in Admin possible!'); - console.log(); - } - - expect(npmPackage.author, 'ERROR: Author in package.json needs to exist').to.exist; - expect(ioPackage.common.authors, 'ERROR: Authors in io-package.json needs to exist').to.exist; - - if (ioPackage.common.name.indexOf('template') !== 0) { - if (Array.isArray(ioPackage.common.authors)) { - expect(ioPackage.common.authors.length, 'ERROR: Author in io-package.json needs to be set').to.not.be.equal(0); - if (ioPackage.common.authors.length === 1) { - expect(ioPackage.common.authors[0], 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name '); - } - } - else { - expect(ioPackage.common.authors, 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name '); - } - } - else { - console.log('WARNING: Testing for set authors field in io-package skipped because template adapter'); - console.log(); - } - expect(fs.existsSync(__dirname + '/../README.md'), 'ERROR: README.md needs to exist! Please create one with description, detail information and changelog. English is mandatory.').to.be.true; - if (!ioPackage.common.titleLang || typeof ioPackage.common.titleLang !== 'object') { - console.log('WARNING: titleLang is not existing in io-package.json. Please add'); - console.log(); - } - if ( - ioPackage.common.title.indexOf('iobroker') !== -1 || - ioPackage.common.title.indexOf('ioBroker') !== -1 || - ioPackage.common.title.indexOf('adapter') !== -1 || - ioPackage.common.title.indexOf('Adapter') !== -1 - ) { - console.log('WARNING: title contains Adapter or ioBroker. It is clear anyway, that it is adapter for ioBroker.'); - console.log(); - } - - if (ioPackage.common.name.indexOf('vis-') !== 0) { - if (!ioPackage.common.materialize || !fs.existsSync(__dirname + '/../admin/index_m.html') || !fs.existsSync(__dirname + '/../gulpfile.js')) { - console.log('WARNING: Admin3 support is missing! Please add it'); - console.log(); - } - if (ioPackage.common.materialize) { - expect(fs.existsSync(__dirname + '/../admin/index_m.html'), 'Admin3 support is enabled in io-package.json, but index_m.html is missing!').to.be.true; - } - } - - var licenseFileExists = fs.existsSync(__dirname + '/../LICENSE'); - var fileContentReadme = fs.readFileSync(__dirname + '/../README.md', 'utf8'); - if (fileContentReadme.indexOf('## Changelog') === -1) { - console.log('Warning: The README.md should have a section ## Changelog'); - console.log(); - } - expect((licenseFileExists || fileContentReadme.indexOf('## License') !== -1), 'A LICENSE must exist as LICENSE file or as part of the README.md').to.be.true; - if (!licenseFileExists) { - console.log('Warning: The License should also exist as LICENSE file'); - console.log(); - } - if (fileContentReadme.indexOf('## License') === -1) { - console.log('Warning: The README.md should also have a section ## License to be shown in Admin3'); - console.log(); - } - done(); - }); -}); diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..a2308c1 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "noImplicitAny": false + }, + "include": [ + "./**/*.js" + ] +} diff --git a/test/unit.js b/test/unit.js new file mode 100644 index 0000000..8e27b3a --- /dev/null +++ b/test/unit.js @@ -0,0 +1,5 @@ +const path = require('path'); +const { tests } = require('@iobroker/testing'); + +// Run unit tests - See https://github.com/ioBroker/testing for a detailed explanation and further options +tests.unit(path.join(__dirname, '..')); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1cb9e07 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,38 @@ +{ + "compileOnSave": true, + "compilerOptions": { + // do not compile anything, this file is just to configure type checking + "noEmit": true, + + // check JS files + "allowJs": true, + "checkJs": true, + + "module": "commonjs", + "moduleResolution": "node", + // this is necessary for the automatic typing of the adapter config + "resolveJsonModule": true, + + // Set this to false if you want to disable the very strict rules (not recommended) + "strict": true, + // Or enable some of those features for more fine-grained control + // "strictNullChecks": true, + // "strictPropertyInitialization": true, + // "strictBindCallApply": true, + "noImplicitAny": false, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + + // Consider targetting es2017 or higher if you require the new NodeJS 8+ features + "target": "es2017", + + }, + "include": [ + "**/*.js", + "**/*.d.ts" + ], + "exclude": [ + "node_modules/**", + "admin/**" + ] +} \ No newline at end of file