diff --git a/.eslintrc.json b/.eslintrc.json index 3e685a0d..31aae27f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -23,7 +23,6 @@ }, "rules": { "prettier/prettier": "error", - "eqeqeq": "error", "no-prototype-builtins": "off", "no-unused-vars": "off" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ebe571f..c62a2634 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ _This release is scheduled to be released on 2021-10-01._ - Bump electron to v13 (and spectron to v15) and update other dependencies in package.json. - Refactor test configs, use default test config for all tests. +- Actually test all js and css files when lint script is run. ### Fixed diff --git a/index.html b/index.html index c9f2239c..37fc2f6e 100644 --- a/index.html +++ b/index.html @@ -16,7 +16,7 @@ diff --git a/js/class.js b/js/class.js index b25ced0a..339e24b2 100644 --- a/js/class.js +++ b/js/class.js @@ -8,8 +8,8 @@ * MIT Licensed. */ (function () { - var initializing = false; - var fnTest = /xyz/.test(function () { + let initializing = false; + const fnTest = /xyz/.test(function () { xyz; }) ? /\b_super\b/ @@ -20,27 +20,27 @@ // Create a new Class that inherits from this class Class.extend = function (prop) { - var _super = this.prototype; + let _super = this.prototype; // Instantiate a base class (but only create the instance, // don't run the init constructor) initializing = true; - var prototype = new this(); + const prototype = new this(); initializing = false; // Make a copy of all prototype properties, to prevent reference issues. - for (var p in prototype) { + for (const p in prototype) { prototype[p] = cloneObject(prototype[p]); } // Copy the properties over onto the new prototype - for (var name in prop) { + for (const name in prop) { // Check if we're overwriting an existing function prototype[name] = typeof prop[name] === "function" && typeof _super[name] === "function" && fnTest.test(prop[name]) ? (function (name, fn) { return function () { - var tmp = this._super; + const tmp = this._super; // Add a new ._super() method that is the same method // but on the super-class @@ -48,7 +48,7 @@ // The method only need to be bound temporarily, so we // remove it when we're done executing - var ret = fn.apply(this, arguments); + const ret = fn.apply(this, arguments); this._super = tmp; return ret; @@ -91,8 +91,8 @@ function cloneObject(obj) { return obj; } - var temp = obj.constructor(); // give temp the original obj's constructor - for (var key in obj) { + const temp = obj.constructor(); // give temp the original obj's constructor + for (const key in obj) { temp[key] = cloneObject(obj[key]); if (key === "lockStrings") { diff --git a/js/electron.js b/js/electron.js index b566f0ad..36b3d352 100644 --- a/js/electron.js +++ b/js/electron.js @@ -65,7 +65,7 @@ function createWindow() { if (process.argv.includes("dev")) { if (process.env.JEST_WORKER_ID !== undefined) { // if we are running with jest - var devtools = new BrowserWindow(electronOptions); + const devtools = new BrowserWindow(electronOptions); mainWindow.webContents.setDevToolsWebContents(devtools.webContents); } mainWindow.webContents.openDevTools(); diff --git a/js/module.js b/js/module.js index b754e197..84222fa3 100644 --- a/js/module.js +++ b/js/module.js @@ -7,7 +7,7 @@ * By Michael Teeuw https://michaelteeuw.nl * MIT Licensed. */ -var Module = Class.extend({ +const Module = Class.extend({ /********************************************************* * All methods (and properties) below can be subclassed. * *********************************************************/ @@ -498,8 +498,8 @@ Module.create = function (name) { Module.register = function (name, moduleDefinition) { if (moduleDefinition.requiresVersion) { - Log.log("Check MagicMirror version for module '" + name + "' - Minimum version: " + moduleDefinition.requiresVersion + " - Current version: " + window.version); - if (cmpVersions(window.version, moduleDefinition.requiresVersion) >= 0) { + Log.log("Check MagicMirror version for module '" + name + "' - Minimum version: " + moduleDefinition.requiresVersion + " - Current version: " + window.mmVersion); + if (cmpVersions(window.mmVersion, moduleDefinition.requiresVersion) >= 0) { Log.log("Version is ok!"); } else { Log.warn("Version is incorrect. Skip module: '" + name + "'"); @@ -510,6 +510,8 @@ Module.register = function (name, moduleDefinition) { Module.definitions[name] = moduleDefinition; }; +window.Module = Module; + /** * Compare two semantic version numbers and return the difference. * diff --git a/js/translator.js b/js/translator.js index 1144e899..d69a9a1a 100644 --- a/js/translator.js +++ b/js/translator.js @@ -6,7 +6,7 @@ * By Christopher Fenner https://github.com/CFenner * MIT Licensed. */ -var Translator = (function () { +const Translator = (function () { /** * Load a JSON file via XHR. * @@ -141,12 +141,7 @@ var Translator = (function () { * The first language defined in translations.js will be used. */ loadCoreTranslationsFallback: function () { - // The variable `first` will contain the first - // defined translation after the following line. - for (var first in translations) { - break; - } - + let first = Object.keys(translations)[0]; if (first) { Log.log("Loading core translation fallback file: " + translations[first]); loadJSON(translations[first], (translations) => { @@ -156,3 +151,5 @@ var Translator = (function () { } }; })(); + +window.Translator = Translator; diff --git a/modules/default/currentweather/currentweather.js b/modules/default/currentweather/currentweather.js index 0d22b40a..5a00fb99 100644 --- a/modules/default/currentweather/currentweather.js +++ b/modules/default/currentweather/currentweather.js @@ -1,3 +1,5 @@ +/* eslint-disable */ + /* Magic Mirror * Module: CurrentWeather * diff --git a/modules/default/weather/providers/envcanada.js b/modules/default/weather/providers/envcanada.js index db0773ea..0f9ef21e 100644 --- a/modules/default/weather/providers/envcanada.js +++ b/modules/default/weather/providers/envcanada.js @@ -134,7 +134,7 @@ WeatherProvider.register("envcanada", { // fetchData: function (url, method = "GET", data = null) { return new Promise(function (resolve, reject) { - var request = new XMLHttpRequest(); + const request = new XMLHttpRequest(); request.open(method, url, true); request.onreadystatechange = function () { if (this.readyState === 4) { @@ -164,7 +164,7 @@ WeatherProvider.register("envcanada", { // CORS errors when accessing EC // getUrl() { - var path = "https://thingproxy.freeboard.io/fetch/https://dd.weather.gc.ca/citypage_weather/xml/" + this.config.provCode + "/" + this.config.siteCode + "_e.xml"; + const path = "https://thingproxy.freeboard.io/fetch/https://dd.weather.gc.ca/citypage_weather/xml/" + this.config.provCode + "/" + this.config.siteCode + "_e.xml"; return path; }, @@ -232,7 +232,7 @@ WeatherProvider.register("envcanada", { // Capture the sunrise and sunset values from EC data // - var sunList = ECdoc.querySelectorAll("siteData riseSet dateTime"); + const sunList = ECdoc.querySelectorAll("siteData riseSet dateTime"); currentWeather.sunrise = moment(sunList[1].querySelector("timeStamp").textContent, "YYYYMMDDhhmmss"); currentWeather.sunset = moment(sunList[3].querySelector("timeStamp").textContent, "YYYYMMDDhhmmss"); @@ -249,14 +249,14 @@ WeatherProvider.register("envcanada", { const days = []; - var weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); + const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); - var foreBaseDates = ECdoc.querySelectorAll("siteData forecastGroup dateTime"); - var baseDate = foreBaseDates[1].querySelector("timeStamp").textContent; + const foreBaseDates = ECdoc.querySelectorAll("siteData forecastGroup dateTime"); + const baseDate = foreBaseDates[1].querySelector("timeStamp").textContent; weather.date = moment(baseDate, "YYYYMMDDhhmmss"); - var foreGroup = ECdoc.querySelectorAll("siteData forecastGroup forecast"); + const foreGroup = ECdoc.querySelectorAll("siteData forecastGroup forecast"); // For simplicity, we will only accumulate precipitation and will not try to break out // rain vs snow accumulations @@ -288,9 +288,9 @@ WeatherProvider.register("envcanada", { // where the next day's (aka Tomorrow's) forecast is located in the forecast array. // - var nextDay = 0; - var lastDay = 0; - var currentTemp = ECdoc.querySelector("siteData currentConditions temperature").textContent; + let nextDay = 0; + let lastDay = 0; + const currentTemp = ECdoc.querySelector("siteData currentConditions temperature").textContent; // // If the first Element is Current Today, look at Current Today and Current Tonight for the current day. @@ -356,10 +356,10 @@ WeatherProvider.register("envcanada", { // iteration looking at the current Element and the next Element. // - var lastDate = moment(baseDate, "YYYYMMDDhhmmss"); + let lastDate = moment(baseDate, "YYYYMMDDhhmmss"); - for (var stepDay = nextDay; stepDay < lastDay; stepDay += 2) { - var weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); + for (let stepDay = nextDay; stepDay < lastDay; stepDay += 2) { + let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); // Add 1 to the date to reflect the current forecast day we are building @@ -402,23 +402,23 @@ WeatherProvider.register("envcanada", { // Get local timezone UTC offset so that each hourly time can be calculated properly - var baseHours = ECdoc.querySelectorAll("siteData hourlyForecastGroup dateTime"); - var hourOffset = baseHours[1].getAttribute("UTCOffset"); + const baseHours = ECdoc.querySelectorAll("siteData hourlyForecastGroup dateTime"); + const hourOffset = baseHours[1].getAttribute("UTCOffset"); // // The EC hourly forecast is held in a 24-element array - Elements 0 to 23 - with Element 0 holding // the forecast for the next 'on the hour' timeslot. This means the array is a rolling 24 hours. // - var hourGroup = ECdoc.querySelectorAll("siteData hourlyForecastGroup hourlyForecast"); + const hourGroup = ECdoc.querySelectorAll("siteData hourlyForecastGroup hourlyForecast"); - for (var stepHour = 0; stepHour < 24; stepHour += 1) { - var weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); + for (let stepHour = 0; stepHour < 24; stepHour += 1) { + const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); // Determine local time by applying UTC offset to the forecast timestamp - var foreTime = moment(hourGroup[stepHour].getAttribute("dateTimeUTC"), "YYYYMMDDhhmmss"); - var currTime = foreTime.add(hourOffset, "hours"); + const foreTime = moment(hourGroup[stepHour].getAttribute("dateTimeUTC"), "YYYYMMDDhhmmss"); + const currTime = foreTime.add(hourOffset, "hours"); weather.date = moment(currTime, "X"); // Capture the temperature @@ -427,7 +427,7 @@ WeatherProvider.register("envcanada", { // Capture Likelihood of Precipitation (LOP) and unit-of-measure values - var precipLOP = hourGroup[stepHour].querySelector("lop").textContent * 1.0; + const precipLOP = hourGroup[stepHour].querySelector("lop").textContent * 1.0; if (precipLOP > 0) { weather.precipitation = precipLOP; @@ -453,9 +453,9 @@ WeatherProvider.register("envcanada", { // setMinMaxTemps(weather, foreGroup, today, fullDay, currentTemp) { - var todayTemp = foreGroup[today].querySelector("temperatures temperature").textContent; + const todayTemp = foreGroup[today].querySelector("temperatures temperature").textContent; - var todayClass = foreGroup[today].querySelector("temperatures temperature").getAttribute("class"); + const todayClass = foreGroup[today].querySelector("temperatures temperature").getAttribute("class"); // // The following logic is largely aimed at accommodating the Current day's forecast whereby we @@ -500,9 +500,9 @@ WeatherProvider.register("envcanada", { } } - var nextTemp = foreGroup[today + 1].querySelector("temperatures temperature").textContent; + const nextTemp = foreGroup[today + 1].querySelector("temperatures temperature").textContent; - var nextClass = foreGroup[today + 1].querySelector("temperatures temperature").getAttribute("class"); + const nextClass = foreGroup[today + 1].querySelector("temperatures temperature").getAttribute("class"); if (fullDay === true) { if (nextClass === "low") { @@ -577,6 +577,7 @@ WeatherProvider.register("envcanada", { return temp; } }, + // // Convert km/h to mph // diff --git a/modules/default/weather/providers/smhi.js b/modules/default/weather/providers/smhi.js index efcf8bb0..d84d9081 100644 --- a/modules/default/weather/providers/smhi.js +++ b/modules/default/weather/providers/smhi.js @@ -60,7 +60,7 @@ WeatherProvider.register("smhi", { */ setConfig(config) { this.config = config; - if (!config.precipitationValue || ["pmin", "pmean", "pmedian", "pmax"].indexOf(config.precipitationValue) == -1) { + if (!config.precipitationValue || ["pmin", "pmean", "pmedian", "pmax"].indexOf(config.precipitationValue) === -1) { console.log("invalid or not set: " + config.precipitationValue); config.precipitationValue = this.defaults.precipitationValue; } @@ -70,6 +70,7 @@ WeatherProvider.register("smhi", { * Of all the times returned find out which one is closest to the current time, should be the first if the data isn't old. * * @param times + * @returns {undefined} */ getClosestToCurrentTime(times) { let now = moment(); @@ -85,6 +86,8 @@ WeatherProvider.register("smhi", { /** * Get the forecast url for the configured coordinates + * + * @returns {string} */ getURL() { let lon = this.config.lon; @@ -99,8 +102,7 @@ WeatherProvider.register("smhi", { * * @param weatherData * @param coordinates - * @param weatherData - * @param coordinates + * @returns {WeatherObject} */ convertWeatherDataToObject(weatherData, coordinates) { let currentWeather = new WeatherObject("metric", "metric", "metric"); //Weather data is only for Sweden and nobody in Sweden would use imperial @@ -145,8 +147,7 @@ WeatherProvider.register("smhi", { * * @param allWeatherData * @param coordinates - * @param allWeatherData - * @param coordinates + * @returns {*[]} */ convertWeatherDataGroupedByDay(allWeatherData, coordinates) { let currentWeather; @@ -194,6 +195,7 @@ WeatherProvider.register("smhi", { * Resolve coordinates from the response data (probably preferably to use this if it's not matching the config values exactly) * * @param data + * @returns {{lon, lat}} */ resolveCoordinates(data) { return { lat: data.geometry.coordinates[0][1], lon: data.geometry.coordinates[0][0] }; @@ -203,6 +205,7 @@ WeatherProvider.register("smhi", { * Checks if the weatherObject is at dayTime. * * @param weatherObject + * @returns {boolean} */ isDayTime(weatherObject) { return weatherObject.date.isBetween(weatherObject.sunrise, weatherObject.sunset, undefined, "[]"); @@ -213,15 +216,16 @@ WeatherProvider.register("smhi", { * Find these gaps and fill them with the previous hours data to make the data returned a complete set. * * @param data + * @returns {*[]} */ fillInGaps(data) { let result = []; - for (const i = 1; i < data.length; i++) { + for (let i = 1; i < data.length; i++) { let to = moment(data[i].validTime); let from = moment(data[i - 1].validTime); let hours = moment.duration(to.diff(from)).asHours(); // For each hour add a datapoint but change the validTime - for (const j = 0; j < hours; j++) { + for (let j = 0; j < hours; j++) { let current = Object.assign({}, data[i]); current.validTime = from.clone().add(j, "hours").toISOString(); result.push(current); @@ -236,11 +240,10 @@ WeatherProvider.register("smhi", { * * @param currentWeatherData * @param name - * @param currentWeatherData - * @param name + * @returns {unknown} */ paramValue(currentWeatherData, name) { - return currentWeatherData.parameters.filter((p) => p.name == name).flatMap((p) => p.values)[0]; + return currentWeatherData.parameters.filter((p) => p.name === name).flatMap((p) => p.values)[0]; }, /** @@ -250,8 +253,7 @@ WeatherProvider.register("smhi", { * * @param input * @param isDayTime - * @param input - * @param isDayTime + * @returns {string|string} */ convertWeatherType(input, isDayTime) { switch (input) { diff --git a/modules/default/weather/providers/ukmetofficedatahub.js b/modules/default/weather/providers/ukmetofficedatahub.js index d096c33b..054dce78 100644 --- a/modules/default/weather/providers/ukmetofficedatahub.js +++ b/modules/default/weather/providers/ukmetofficedatahub.js @@ -1,3 +1,5 @@ +/* global WeatherProvider, WeatherObject, SunCalc */ + /* Magic Mirror * Module: Weather * @@ -91,7 +93,7 @@ WeatherProvider.register("ukmetofficedatahub", { this.fetchWeather(this.getUrl("hourly"), this.getHeaders()) .then((data) => { // Check data is useable - if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length == 0) { + if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length === 0) { // Did not receive usable new data. // Maybe this needs a better check? Log.error("Possibly bad current/hourly data?"); @@ -125,7 +127,7 @@ WeatherProvider.register("ukmetofficedatahub", { let nowUtc = moment.utc(); // Find hour that contains the current time - for (hour in forecastDataHours) { + for (let hour in forecastDataHours) { let forecastTime = moment.utc(forecastDataHours[hour].time); if (nowUtc.isSameOrAfter(forecastTime) && nowUtc.isBefore(moment(forecastTime.add(1, "h")))) { currentWeather.date = forecastTime; @@ -162,7 +164,7 @@ WeatherProvider.register("ukmetofficedatahub", { this.fetchWeather(this.getUrl("daily"), this.getHeaders()) .then((data) => { // Check data is useable - if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length == 0) { + if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length === 0) { // Did not receive usable new data. // Maybe this needs a better check? Log.error("Possibly bad forecast data?"); @@ -196,7 +198,7 @@ WeatherProvider.register("ukmetofficedatahub", { let today = moment.utc().startOf("date"); // Go through each day in the forecasts - for (day in forecastDataDays) { + for (let day in forecastDataDays) { const forecastWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh); // Get date of forecast @@ -258,11 +260,11 @@ WeatherProvider.register("ukmetofficedatahub", { // To use kilometres per hour, use "kph" // Else assumed imperial and the value is returned in miles per hour (a Met Office user is likely to be UK-based) convertWindSpeed(windInMpS) { - if (this.config.windUnits == "mps") { + if (this.config.windUnits === "mps") { return windInMpS; } - if (this.config.windUnits == "kph" || this.config.windUnits == "metric" || this.config.useKmh) { + if (this.config.windUnits === "kph" || this.config.windUnits === "metric" || this.config.useKmh) { return windInMpS * 3.6; } diff --git a/modules/default/weatherforecast/weatherforecast.js b/modules/default/weatherforecast/weatherforecast.js index 50229262..50c1f3a7 100644 --- a/modules/default/weatherforecast/weatherforecast.js +++ b/modules/default/weatherforecast/weatherforecast.js @@ -1,3 +1,5 @@ +/* eslint-disable */ + /* Magic Mirror * Module: WeatherForecast * diff --git a/package-lock.json b/package-lock.json index ef2155a3..ae8d93d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "license": "MIT", "dependencies": { "colors": "^1.4.0", - "console-stamp": "^3.0.2", + "console-stamp": "^3.0.3", "digest-fetch": "^1.2.0", "eslint": "^7.30.0", "express": "^4.17.1", @@ -23,17 +23,17 @@ "moment": "^2.29.1", "node-fetch": "^2.6.1", "node-ical": "^0.13.0", - "simple-git": "^2.40.0", - "socket.io": "^4.1.2" + "simple-git": "^2.41.1", + "socket.io": "^4.1.3" }, "devDependencies": { "eslint-config-prettier": "^8.3.0", "eslint-plugin-jest": "^24.3.6", - "eslint-plugin-jsdoc": "^35.4.1", + "eslint-plugin-jsdoc": "^35.4.3", "eslint-plugin-prettier": "^3.4.0", "express-basic-auth": "^1.2.0", - "husky": "^7.0.0", - "jest": "27.0.6", + "husky": "^7.0.1", + "jest": "^27.0.6", "jsdom": "^16.6.0", "lodash": "^4.17.21", "nyc": "^15.1.0", @@ -50,7 +50,7 @@ "node": ">=12" }, "optionalDependencies": { - "electron": "^13.1.5" + "electron": "^13.1.6" } }, "node_modules/@babel/code-frame": { @@ -703,17 +703,17 @@ } }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.8.0.tgz", - "integrity": "sha512-Xd3GzYsL2sz2pcdtYt5Q0Wz1ol/o9Nt2UQL4nFPDcaEomvPmwjJsbjkKx1SKhl2h3TgwazNBLdcNr2m0UiGiFA==", + "version": "0.9.0-alpha.1", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.9.0-alpha.1.tgz", + "integrity": "sha512-Clxxc0PwpISoYYBibA+1L2qFJ7gvFVhI2Hos87S06K+Q0cXdOhZQJNKWuaQGPAeHjZEuUB/YoWOfwjuF2wirqA==", "dev": true, "dependencies": { - "comment-parser": "^1.1.5", + "comment-parser": "1.1.6-beta.0", "esquery": "^1.4.0", "jsdoc-type-pratt-parser": "1.0.4" }, "engines": { - "node": ">=10.0.0" + "node": ">=12.0.0" } }, "node_modules/@eslint/eslintrc": { @@ -2600,9 +2600,9 @@ } }, "node_modules/comment-parser": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.1.5.tgz", - "integrity": "sha512-RePCE4leIhBlmrqiYTvaqEeGYg7qpSl4etaIabKtdOQVi+mSTIBBklGUwIr79GXYnl3LpMwmDw4KeR2stNc6FA==", + "version": "1.1.6-beta.0", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.1.6-beta.0.tgz", + "integrity": "sha512-q3cA8TSMyqW7wcPSYWzbO/rMahnXgzs4SLG/UIWXdEsnXTFPZkEkWAdNgPiHig2OzxgpPLOh4WwsmClDxndwHw==", "dev": true, "engines": { "node": ">= 10.0.0" @@ -2679,11 +2679,11 @@ } }, "node_modules/console-stamp": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/console-stamp/-/console-stamp-3.0.2.tgz", - "integrity": "sha512-nYIxVrp1Cau8wRy8RQJO1VNBTYQPnFcN7SrsLAStSavo38Y4+jcysh5n4nZNd/WkR2IOULgwr2+6qDxMUA7Hog==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/console-stamp/-/console-stamp-3.0.3.tgz", + "integrity": "sha512-6ltMcMEVDHb1bqb+qaVfCX7Vf3vEkfZEeKyReG1ny45Rv6YJynCcdv94j7whNVfxj/4/3Ji/QBHY6p4JI51Ucw==", "dependencies": { - "chalk": "^4.1.0", + "chalk": "^4.1.1", "dateformat": "^4.5.1" }, "engines": { @@ -3243,11 +3243,12 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "node_modules/electron": { - "version": "13.1.5", - "resolved": "https://registry.npmjs.org/electron/-/electron-13.1.5.tgz", - "integrity": "sha512-ZoMCcPQNs/zO/Zdb5hq5H+rwRaKrdI3/sfXEwBVMx7f5jwa9jPQB3dZ2+7t59uD9VcFAWsH/pozr8nPPlv0tyw==", + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/electron/-/electron-13.1.6.tgz", + "integrity": "sha512-XiB55/JTaQpDFQrD9pulYnOGwaWeMyRIub5ispvoE2bWBvM5zVMLptwMLb0m3KTMrfSkzhedZvOu7fwYvR7L7Q==", "devOptional": true, "hasInstallScript": true, + "license": "MIT", "dependencies": { "@electron/get": "^1.0.1", "@types/node": "^14.6.2", @@ -3646,14 +3647,14 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "35.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-35.4.1.tgz", - "integrity": "sha512-lnpu2Bj+ta2eAqwCWnb6f3Xjc78TWKo/oMCpDH5NfpPhYnePNtGZJzoAMgU5uo9BQqmXJ8pql8aiodOhg82ofw==", + "version": "35.4.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-35.4.3.tgz", + "integrity": "sha512-hBEn+VNjVX0IKoZ2OdZs0Z1fU8CqZkBSzLqD8ZpwZEamrdi2TUgKvujvETe8gXYQ/67hpRtbR5iPFTgmWpRevw==", "dev": true, "dependencies": { - "@es-joy/jsdoccomment": "^0.8.0", - "comment-parser": "1.1.5", - "debug": "^4.3.1", + "@es-joy/jsdoccomment": "^0.9.0-alpha.1", + "comment-parser": "1.1.6-beta.0", + "debug": "^4.3.2", "esquery": "^1.4.0", "jsdoc-type-pratt-parser": "^1.0.4", "lodash": "^4.17.21", @@ -4865,10 +4866,11 @@ } }, "node_modules/husky": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.0.tgz", - "integrity": "sha512-xK7lO0EtSzfFPiw+oQncQVy/XqV7UVVjxBByc+Iv5iK3yhW9boDoWgvZy3OGo48QKg/hUtZkzz0hi2HXa0kn7w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.1.tgz", + "integrity": "sha512-gceRaITVZ+cJH9sNHqx5tFwbzlLCVxtVZcusME8JYQ8Edy5mpGDOqD8QBCdMhpyo9a+JXddnujQ4rpY2Ff9SJA==", "dev": true, + "license": "MIT", "bin": { "husky": "lib/bin.js" }, @@ -8578,9 +8580,9 @@ "dev": true }, "node_modules/simple-git": { - "version": "2.40.0", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.40.0.tgz", - "integrity": "sha512-7IO/eQwrN5kvS38TTu9ljhG9tx2nn0BTqZOmqpPpp51TvE44YIvLA6fETqEVA8w/SeEfPaVv6mk7Tsk9Jns+ag==", + "version": "2.41.1", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.41.1.tgz", + "integrity": "sha512-n1STz1tfnemvYndzWakgKa0JB4s/LrUG4btXMetWB9N9ZoIAJQd0ZtWj9sBwWxIZ/X/tYdA/tq+KHfFNAGzZhQ==", "dependencies": { "@kwsites/file-exists": "^1.1.1", "@kwsites/promise-deferred": "^1.1.1", @@ -8637,19 +8639,19 @@ } }, "node_modules/socket.io": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.2.tgz", - "integrity": "sha512-xK0SD1C7hFrh9+bYoYCdVt+ncixkSLKtNLCax5aEy1o3r5PaO5yQhVb97exIe67cE7lAK+EpyMytXWTWmyZY8w==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.3.tgz", + "integrity": "sha512-tLkaY13RcO4nIRh1K2hT5iuotfTaIQw7cVIe0FUykN3SuQi0cm7ALxuyT5/CtDswOMWUzMGTibxYNx/gU7In+Q==", "dependencies": { "@types/cookie": "^0.4.0", - "@types/cors": "^2.8.8", + "@types/cors": "^2.8.10", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "~2.0.0", "debug": "~4.3.1", - "engine.io": "~5.1.0", - "socket.io-adapter": "~2.3.0", - "socket.io-parser": "~4.0.3" + "engine.io": "~5.1.1", + "socket.io-adapter": "~2.3.1", + "socket.io-parser": "~4.0.4" }, "engines": { "node": ">=10.0.0" @@ -10913,12 +10915,12 @@ "requires": {} }, "@es-joy/jsdoccomment": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.8.0.tgz", - "integrity": "sha512-Xd3GzYsL2sz2pcdtYt5Q0Wz1ol/o9Nt2UQL4nFPDcaEomvPmwjJsbjkKx1SKhl2h3TgwazNBLdcNr2m0UiGiFA==", + "version": "0.9.0-alpha.1", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.9.0-alpha.1.tgz", + "integrity": "sha512-Clxxc0PwpISoYYBibA+1L2qFJ7gvFVhI2Hos87S06K+Q0cXdOhZQJNKWuaQGPAeHjZEuUB/YoWOfwjuF2wirqA==", "dev": true, "requires": { - "comment-parser": "^1.1.5", + "comment-parser": "1.1.6-beta.0", "esquery": "^1.4.0", "jsdoc-type-pratt-parser": "1.0.4" } @@ -12396,9 +12398,9 @@ } }, "comment-parser": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.1.5.tgz", - "integrity": "sha512-RePCE4leIhBlmrqiYTvaqEeGYg7qpSl4etaIabKtdOQVi+mSTIBBklGUwIr79GXYnl3LpMwmDw4KeR2stNc6FA==", + "version": "1.1.6-beta.0", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.1.6-beta.0.tgz", + "integrity": "sha512-q3cA8TSMyqW7wcPSYWzbO/rMahnXgzs4SLG/UIWXdEsnXTFPZkEkWAdNgPiHig2OzxgpPLOh4WwsmClDxndwHw==", "dev": true }, "commondir": { @@ -12465,11 +12467,11 @@ } }, "console-stamp": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/console-stamp/-/console-stamp-3.0.2.tgz", - "integrity": "sha512-nYIxVrp1Cau8wRy8RQJO1VNBTYQPnFcN7SrsLAStSavo38Y4+jcysh5n4nZNd/WkR2IOULgwr2+6qDxMUA7Hog==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/console-stamp/-/console-stamp-3.0.3.tgz", + "integrity": "sha512-6ltMcMEVDHb1bqb+qaVfCX7Vf3vEkfZEeKyReG1ny45Rv6YJynCcdv94j7whNVfxj/4/3Ji/QBHY6p4JI51Ucw==", "requires": { - "chalk": "^4.1.0", + "chalk": "^4.1.1", "dateformat": "^4.5.1" } }, @@ -12915,9 +12917,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron": { - "version": "13.1.5", - "resolved": "https://registry.npmjs.org/electron/-/electron-13.1.5.tgz", - "integrity": "sha512-ZoMCcPQNs/zO/Zdb5hq5H+rwRaKrdI3/sfXEwBVMx7f5jwa9jPQB3dZ2+7t59uD9VcFAWsH/pozr8nPPlv0tyw==", + "version": "13.1.6", + "resolved": "https://registry.npmjs.org/electron/-/electron-13.1.6.tgz", + "integrity": "sha512-XiB55/JTaQpDFQrD9pulYnOGwaWeMyRIub5ispvoE2bWBvM5zVMLptwMLb0m3KTMrfSkzhedZvOu7fwYvR7L7Q==", "devOptional": true, "requires": { "@electron/get": "^1.0.1", @@ -13209,14 +13211,14 @@ } }, "eslint-plugin-jsdoc": { - "version": "35.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-35.4.1.tgz", - "integrity": "sha512-lnpu2Bj+ta2eAqwCWnb6f3Xjc78TWKo/oMCpDH5NfpPhYnePNtGZJzoAMgU5uo9BQqmXJ8pql8aiodOhg82ofw==", + "version": "35.4.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-35.4.3.tgz", + "integrity": "sha512-hBEn+VNjVX0IKoZ2OdZs0Z1fU8CqZkBSzLqD8ZpwZEamrdi2TUgKvujvETe8gXYQ/67hpRtbR5iPFTgmWpRevw==", "dev": true, "requires": { - "@es-joy/jsdoccomment": "^0.8.0", - "comment-parser": "1.1.5", - "debug": "^4.3.1", + "@es-joy/jsdoccomment": "^0.9.0-alpha.1", + "comment-parser": "1.1.6-beta.0", + "debug": "^4.3.2", "esquery": "^1.4.0", "jsdoc-type-pratt-parser": "^1.0.4", "lodash": "^4.17.21", @@ -14135,9 +14137,9 @@ "dev": true }, "husky": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.0.tgz", - "integrity": "sha512-xK7lO0EtSzfFPiw+oQncQVy/XqV7UVVjxBByc+Iv5iK3yhW9boDoWgvZy3OGo48QKg/hUtZkzz0hi2HXa0kn7w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.1.tgz", + "integrity": "sha512-gceRaITVZ+cJH9sNHqx5tFwbzlLCVxtVZcusME8JYQ8Edy5mpGDOqD8QBCdMhpyo9a+JXddnujQ4rpY2Ff9SJA==", "dev": true }, "iconv-lite": { @@ -16981,9 +16983,9 @@ "dev": true }, "simple-git": { - "version": "2.40.0", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.40.0.tgz", - "integrity": "sha512-7IO/eQwrN5kvS38TTu9ljhG9tx2nn0BTqZOmqpPpp51TvE44YIvLA6fETqEVA8w/SeEfPaVv6mk7Tsk9Jns+ag==", + "version": "2.41.1", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.41.1.tgz", + "integrity": "sha512-n1STz1tfnemvYndzWakgKa0JB4s/LrUG4btXMetWB9N9ZoIAJQd0ZtWj9sBwWxIZ/X/tYdA/tq+KHfFNAGzZhQ==", "requires": { "@kwsites/file-exists": "^1.1.1", "@kwsites/promise-deferred": "^1.1.1", @@ -17027,19 +17029,19 @@ } }, "socket.io": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.2.tgz", - "integrity": "sha512-xK0SD1C7hFrh9+bYoYCdVt+ncixkSLKtNLCax5aEy1o3r5PaO5yQhVb97exIe67cE7lAK+EpyMytXWTWmyZY8w==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.1.3.tgz", + "integrity": "sha512-tLkaY13RcO4nIRh1K2hT5iuotfTaIQw7cVIe0FUykN3SuQi0cm7ALxuyT5/CtDswOMWUzMGTibxYNx/gU7In+Q==", "requires": { "@types/cookie": "^0.4.0", - "@types/cors": "^2.8.8", + "@types/cors": "^2.8.10", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "~2.0.0", "debug": "~4.3.1", - "engine.io": "~5.1.0", - "socket.io-adapter": "~2.3.0", - "socket.io-parser": "~4.0.3" + "engine.io": "~5.1.1", + "socket.io-adapter": "~2.3.1", + "socket.io-parser": "~4.0.4" } }, "socket.io-adapter": { diff --git a/package.json b/package.json index 571a64c9..b0b5426d 100644 --- a/package.json +++ b/package.json @@ -15,13 +15,13 @@ "test:e2e": "NODE_ENV=test jest --selectProjects e2e -i --forceExit", "test:unit": "NODE_ENV=test jest --selectProjects unit -i --forceExit", "test:prettier": "prettier . --check", - "test:js": "eslint js/**/*.js modules/default/**/*.js clientonly/*.js serveronly/*.js translations/*.js vendor/*.js tests/**/*.js config/* --config .eslintrc.json --quiet", - "test:css": "stylelint css/main.css modules/default/**/*.css --config .stylelintrc.json", + "test:js": "eslint 'js/**/*.js' 'modules/default/**/*.js' 'clientonly/*.js' 'serveronly/*.js' 'translations/*.js' 'vendor/*.js' 'tests/**/*.js' 'config/*' --config .eslintrc.json --quiet", + "test:css": "stylelint 'css/main.css' 'fonts/*.css' 'modules/default/**/*.css' 'vendor/*.css' --config .stylelintrc.json", "test:calendar": "node ./modules/default/calendar/debug.js", "config:check": "node js/check_config.js", "lint:prettier": "prettier . --write", - "lint:js": "eslint js/**/*.js modules/default/**/*.js clientonly/*.js serveronly/*.js translations/*.js vendor/*.js tests/**/*.js config/* --config .eslintrc.json --fix", - "lint:css": "stylelint css/main.css modules/default/**/*.css --config .stylelintrc.json --fix", + "lint:js": "eslint 'js/**/*.js' 'modules/default/**/*.js' 'clientonly/*.js' 'serveronly/*.js' 'translations/*.js' 'vendor/*.js' 'tests/**/*.js' 'config/*' --config .eslintrc.json --fix", + "lint:css": "stylelint 'css/main.css' 'fonts/*.css' 'modules/default/**/*.css' 'vendor/*.css' --config .stylelintrc.json --fix", "lint:staged": "pretty-quick --staged", "prepare": "[ -f node_modules/.bin/husky ] && husky install || echo no husky installed." }, @@ -46,12 +46,12 @@ "homepage": "https://magicmirror.builders", "devDependencies": { "eslint-config-prettier": "^8.3.0", - "eslint-plugin-jsdoc": "^35.4.1", - "eslint-plugin-prettier": "^3.4.0", "eslint-plugin-jest": "^24.3.6", + "eslint-plugin-jsdoc": "^35.4.3", + "eslint-plugin-prettier": "^3.4.0", "express-basic-auth": "^1.2.0", - "husky": "^7.0.0", - "jest": "27.0.6", + "husky": "^7.0.1", + "jest": "^27.0.6", "jsdom": "^16.6.0", "lodash": "^4.17.21", "nyc": "^15.1.0", @@ -65,11 +65,11 @@ "stylelint-prettier": "^1.2.0" }, "optionalDependencies": { - "electron": "^13.1.5" + "electron": "^13.1.6" }, "dependencies": { "colors": "^1.4.0", - "console-stamp": "^3.0.2", + "console-stamp": "^3.0.3", "digest-fetch": "^1.2.0", "eslint": "^7.30.0", "express": "^4.17.1", @@ -81,8 +81,8 @@ "moment": "^2.29.1", "node-fetch": "^2.6.1", "node-ical": "^0.13.0", - "simple-git": "^2.40.0", - "socket.io": "^4.1.2" + "simple-git": "^2.41.1", + "socket.io": "^4.1.3" }, "_moduleAliases": { "node_helper": "js/node_helper.js", diff --git a/tests/e2e/modules/clock_es_spec.js b/tests/e2e/modules/clock_es_spec.js index f97fba2f..4c39a241 100644 --- a/tests/e2e/modules/clock_es_spec.js +++ b/tests/e2e/modules/clock_es_spec.js @@ -5,7 +5,7 @@ describe("Clock set to spanish language module", function () { let app = null; - testMatch = async function (element, regex) { + const testMatch = async function (element, regex) { await app.client.waitUntilWindowLoaded(); const elem = await app.client.$(element); const txt = await elem.getText(element); diff --git a/tests/e2e/modules/clock_spec.js b/tests/e2e/modules/clock_spec.js index c09f44de..473c6f6a 100644 --- a/tests/e2e/modules/clock_spec.js +++ b/tests/e2e/modules/clock_spec.js @@ -6,7 +6,7 @@ describe("Clock module", function () { let app = null; - testMatch = async function (element, regex) { + const testMatch = async function (element, regex) { await app.client.waitUntilWindowLoaded(); const elem = await app.client.$(element); const txt = await elem.getText(element); diff --git a/tests/e2e/modules/mocks/weather_current.js b/tests/e2e/modules/mocks/weather_current.js index a0c3401d..3988212c 100644 --- a/tests/e2e/modules/mocks/weather_current.js +++ b/tests/e2e/modules/mocks/weather_current.js @@ -1,5 +1,8 @@ const _ = require("lodash"); +/** + * @param extendedData + */ function generateWeather(extendedData = {}) { return JSON.stringify( _.merge( diff --git a/tests/e2e/modules/mocks/weather_forecast.js b/tests/e2e/modules/mocks/weather_forecast.js index eaf2e375..7943e073 100644 --- a/tests/e2e/modules/mocks/weather_forecast.js +++ b/tests/e2e/modules/mocks/weather_forecast.js @@ -1,5 +1,8 @@ const _ = require("lodash"); +/** + * @param extendedData + */ function generateWeatherForecast(extendedData = {}) { return JSON.stringify( _.merge( diff --git a/tests/e2e/modules/weather_spec.js b/tests/e2e/modules/weather_spec.js index 5698dc6a..a9bf2cb8 100644 --- a/tests/e2e/modules/weather_spec.js +++ b/tests/e2e/modules/weather_spec.js @@ -12,6 +12,9 @@ describe("Weather module", function () { helpers.setupTimeout(this); + /** + * @param responses + */ async function setup(responses) { app = await helpers.startApplication({ args: ["js/electron.js"], @@ -23,10 +26,17 @@ describe("Weather module", function () { app.client.setupStub(); } + /** + * @param element + */ async function getElement(element) { return await app.client.$(element); } + /** + * @param element + * @param result + */ async function getText(element, result) { const elem = await getElement(element); return await elem.getText(element).then(function (text) { diff --git a/tests/e2e/vendor_spec.js b/tests/e2e/vendor_spec.js index b5289c2f..9fcb9aeb 100644 --- a/tests/e2e/vendor_spec.js +++ b/tests/e2e/vendor_spec.js @@ -1,9 +1,6 @@ const helpers = require("./global-setup"); const fetch = require("node-fetch"); -const before = global.before; -const after = global.after; - describe("Vendors", function () { helpers.setupTimeout(this); @@ -26,6 +23,7 @@ describe("Vendors", function () { describe("Get list vendors", function () { const vendors = require(__dirname + "/../../vendor/vendor.js"); + Object.keys(vendors).forEach((vendor) => { it(`should return 200 HTTP code for vendor "${vendor}"`, function (done) { const urlVendor = "http://localhost:8080/vendor/" + vendors[vendor]; diff --git a/tests/unit/classes/translator_spec.js b/tests/unit/classes/translator_spec.js index fe942753..2756b41b 100644 --- a/tests/unit/classes/translator_spec.js +++ b/tests/unit/classes/translator_spec.js @@ -67,6 +67,9 @@ describe("Translator", function () { Fallback: "core fallback" }; + /** + * @param Translator + */ function setTranslations(Translator) { Translator.translations = translations; Translator.coreTranslations = coreTranslations; diff --git a/tests/unit/global_vars/defaults_modules_spec.js b/tests/unit/global_vars/defaults_modules_spec.js index c0a7f0e4..03879a86 100644 --- a/tests/unit/global_vars/defaults_modules_spec.js +++ b/tests/unit/global_vars/defaults_modules_spec.js @@ -4,39 +4,41 @@ const vm = require("vm"); const basedir = path.join(__dirname, "../../.."); -beforeAll(function () { - const fileName = "js/app.js"; - const filePath = path.join(basedir, fileName); - const code = fs.readFileSync(filePath); - - sandbox = { - module: {}, - __dirname: path.dirname(filePath), - global: {}, - process: { - on: function () {}, - env: {} - } - }; - - sandbox.require = function (filename) { - // This modifies the global slightly, - // but supplies vm with essential code - if (filename === "logger") { - return require("../mocks/logger.js"); - } else { - try { - return require(filename); - } catch { - // ignore - } - } - }; - - vm.runInNewContext(code, sandbox, fileName); -}); - describe("Default modules set in modules/default/defaultmodules.js", function () { + let sandbox = null; + + beforeAll(function () { + const fileName = "js/app.js"; + const filePath = path.join(basedir, fileName); + const code = fs.readFileSync(filePath); + + sandbox = { + module: {}, + __dirname: path.dirname(filePath), + global: {}, + process: { + on: function () {}, + env: {} + } + }; + + sandbox.require = function (filename) { + // This modifies the global slightly, + // but supplies vm with essential code + if (filename === "logger") { + return require("../mocks/logger.js"); + } else { + try { + return require(filename); + } catch (ignore) { + // ignore + } + } + }; + + vm.runInNewContext(code, sandbox, fileName); + }); + const expectedDefaultModules = require("../../../modules/default/defaultmodules"); for (const defaultModule of expectedDefaultModules) { diff --git a/tests/unit/global_vars/root_path_spec.js b/tests/unit/global_vars/root_path_spec.js index 591f3ddb..9dfc3f30 100644 --- a/tests/unit/global_vars/root_path_spec.js +++ b/tests/unit/global_vars/root_path_spec.js @@ -2,41 +2,43 @@ const fs = require("fs"); const path = require("path"); const vm = require("vm"); -beforeAll(function () { - const basedir = path.join(__dirname, "../../.."); - - const fileName = "js/app.js"; - const filePath = path.join(basedir, fileName); - const code = fs.readFileSync(filePath); - - sandbox = { - module: {}, - __dirname: path.dirname(filePath), - global: {}, - process: { - on: function () {}, - env: {} - } - }; - - sandbox.require = function (filename) { - // This modifies the global slightly, - // but supplies vm with essential code - if (filename === "logger") { - return require("../mocks/logger.js"); - } else { - try { - return require(filename); - } catch { - // ignore - } - } - }; - - vm.runInNewContext(code, sandbox, fileName); -}); - describe("'global.root_path' set in js/app.js", function () { + let sandbox = null; + + beforeAll(function () { + const basedir = path.join(__dirname, "../../.."); + + const fileName = "js/app.js"; + const filePath = path.join(basedir, fileName); + const code = fs.readFileSync(filePath); + + sandbox = { + module: {}, + __dirname: path.dirname(filePath), + global: {}, + process: { + on: function () {}, + env: {} + } + }; + + sandbox.require = function (filename) { + // This modifies the global slightly, + // but supplies vm with essential code + if (filename === "logger") { + return require("../mocks/logger.js"); + } else { + try { + return require(filename); + } catch (ignore) { + // ignore + } + } + }; + + vm.runInNewContext(code, sandbox, fileName); + }); + const expectedSubPaths = ["modules", "serveronly", "js", "js/app.js", "js/main.js", "js/electron.js", "config"]; expectedSubPaths.forEach((subpath) => { diff --git a/translations/translations.js b/translations/translations.js index cddd3b74..85182053 100644 --- a/translations/translations.js +++ b/translations/translations.js @@ -5,7 +5,7 @@ * MIT Licensed. */ -var translations = { +let translations = { en: "translations/en.json", // English nl: "translations/nl.json", // Dutch de: "translations/de.json", // German