From 94bb8e6c030428e504469938250146c0c185c731 Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Fri, 29 Jan 2021 22:13:44 +0100 Subject: [PATCH 1/4] added sinon, tests for module.loadTranslations --- package-lock.json | 108 +++++++++++++++++++++++++++++++++ package.json | 1 + tests/e2e/translations_spec.js | 74 ++++++++++++++++++++++ 3 files changed, 183 insertions(+) diff --git a/package-lock.json b/package-lock.json index 9f19b17f..095c35e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -376,6 +376,41 @@ "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", "optional": true }, + "@sinonjs/commons": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", + "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/samsam": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", + "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@stylelint/postcss-css-in-js": { "version": "0.37.2", "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz", @@ -3943,6 +3978,12 @@ "verror": "1.10.0" } }, + "just-extend": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", + "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", + "dev": true + }, "keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", @@ -4021,6 +4062,12 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -4527,6 +4574,36 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "nise": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", + "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, "node-ical": { "version": "0.12.7", "resolved": "https://registry.npmjs.org/node-ical/-/node-ical-0.12.7.tgz", @@ -6149,6 +6226,37 @@ } } }, + "sinon": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.1", + "@sinonjs/fake-timers": "^6.0.1", + "@sinonjs/samsam": "^5.3.1", + "diff": "^4.0.2", + "nise": "^4.0.4", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", diff --git a/package.json b/package.json index 56910da6..5473d04d 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "nyc": "^15.1.0", "prettier": "^2.2.1", "pretty-quick": "^3.1.0", + "sinon": "^9.2.4", "spectron": "^10.0.1", "stylelint": "^13.8.0", "stylelint-config-prettier": "^8.0.2", diff --git a/tests/e2e/translations_spec.js b/tests/e2e/translations_spec.js index a66d0ea4..639f8f12 100644 --- a/tests/e2e/translations_spec.js +++ b/tests/e2e/translations_spec.js @@ -7,6 +7,7 @@ const translations = require("../../translations/translations.js"); const helmet = require("helmet"); const { JSDOM } = require("jsdom"); const express = require("express"); +const sinon = require("sinon"); describe("Translations", function () { let server; @@ -34,6 +35,79 @@ describe("Translations", function () { } }); + describe("loadTranslations", () => { + let dom; + + beforeEach(() => { + dom = new JSDOM( + `\ + \ + `, + { runScripts: "dangerously", resources: "usable" } + ); + }); + + it("should load translation file", (done) => { + dom.window.onload = async function () { + const { Translator, Module, config } = dom.window; + config.language = "en"; + Translator.load = sinon.stub().callsFake((_m, _f, _fb, callback) => callback()); + + Module.register("name", { getTranslations: () => translations }); + const MMM = Module.create("name"); + + const loaded = sinon.stub(); + MMM.loadTranslations(loaded); + + expect(loaded.callCount).to.equal(1); + expect(Translator.load.args.length).to.equal(1); + expect(Translator.load.calledWith(MMM, "translations/en.json", false, sinon.match.func)).to.be.true; + + done(); + }; + }); + + it("should load translation + fallback file", (done) => { + dom.window.onload = async function () { + const { Translator, Module } = dom.window; + Translator.load = sinon.stub().callsFake((_m, _f, _fb, callback) => callback()); + + Module.register("name", { getTranslations: () => translations }); + const MMM = Module.create("name"); + + const loaded = sinon.stub(); + MMM.loadTranslations(loaded); + + expect(loaded.callCount).to.equal(1); + expect(Translator.load.args.length).to.equal(2); + expect(Translator.load.calledWith(MMM, "translations/de.json", false, sinon.match.func)).to.be.true; + expect(Translator.load.calledWith(MMM, "translations/en.json", true, sinon.match.func)).to.be.true; + + done(); + }; + }); + + it("should load translation fallback file", (done) => { + dom.window.onload = async function () { + const { Translator, Module, config } = dom.window; + config.language = "--"; + Translator.load = sinon.stub().callsFake((_m, _f, _fb, callback) => callback()); + + Module.register("name", { getTranslations: () => translations }); + const MMM = Module.create("name"); + + const loaded = sinon.stub(); + MMM.loadTranslations(loaded); + + expect(loaded.callCount).to.equal(1); + expect(Translator.load.args.length).to.equal(1); + expect(Translator.load.calledWith(MMM, "translations/en.json", true, sinon.match.func)).to.be.true; + + done(); + }; + }); + }); + const mmm = { name: "TranslationTest", file(file) { From db24f20289cbaeb2fe51eeb43a46689599483233 Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Fri, 29 Jan 2021 22:25:49 +0100 Subject: [PATCH 2/4] cleaned up function and added test in case no file should be loaded --- js/module.js | 34 ++++++++++++++++------------------ tests/e2e/translations_spec.js | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/js/module.js b/js/module.js index 14458f8a..a6e574fc 100644 --- a/js/module.js +++ b/js/module.js @@ -311,29 +311,27 @@ var Module = Class.extend({ * * @param {Function} callback Function called when done. */ - loadTranslations: function (callback) { - var self = this; - var translations = this.getTranslations(); - var lang = config.language.toLowerCase(); + loadTranslations(callback) { + const translations = this.getTranslations() || {}; + const language = config.language.toLowerCase(); - // The variable `first` will contain the first - // defined translation after the following line. - for (var first in translations) { - break; - } + const languages = Object.keys(translations); + const fallbackLanguage = languages[0]; - if (translations) { - var translationFile = translations[lang] || undefined; - var translationsFallbackFile = translations[first]; + if (languages.length > 0) { + const translationFile = translations[language]; + const translationsFallbackFile = translations[fallbackLanguage]; - // If a translation file is set, load it and then also load the fallback translation file. - // Otherwise only load the fallback translation file. - if (translationFile !== undefined && translationFile !== translationsFallbackFile) { - Translator.load(self, translationFile, false, function () { - Translator.load(self, translationsFallbackFile, true, callback); + if (translationFile) { + Translator.load(this, translationFile, false, () => { + if (translationFile !== translationsFallbackFile) { + Translator.load(this, translationsFallbackFile, true, callback); + } else { + callback(); + } }); } else { - Translator.load(self, translationsFallbackFile, true, callback); + Translator.load(this, translationsFallbackFile, true, callback); } } else { callback(); diff --git a/tests/e2e/translations_spec.js b/tests/e2e/translations_spec.js index 639f8f12..5f0d6053 100644 --- a/tests/e2e/translations_spec.js +++ b/tests/e2e/translations_spec.js @@ -106,6 +106,24 @@ describe("Translations", function () { done(); }; }); + + it("should load no file", (done) => { + dom.window.onload = async function () { + const { Translator, Module } = dom.window; + Translator.load = sinon.stub(); + + Module.register("name", {}); + const MMM = Module.create("name"); + + const loaded = sinon.stub(); + MMM.loadTranslations(loaded); + + expect(loaded.callCount).to.equal(1); + expect(Translator.load.callCount).to.equal(0); + + done(); + }; + }); }); const mmm = { From 308774c2a6193540a228de02e52563d1c61299c5 Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Fri, 29 Jan 2021 22:34:12 +0100 Subject: [PATCH 3/4] remove callback hell --- js/module.js | 34 +++++++++++++++++----------------- js/translator.js | 26 +++++++++----------------- 2 files changed, 26 insertions(+), 34 deletions(-) diff --git a/js/module.js b/js/module.js index a6e574fc..60c7200d 100644 --- a/js/module.js +++ b/js/module.js @@ -318,24 +318,24 @@ var Module = Class.extend({ const languages = Object.keys(translations); const fallbackLanguage = languages[0]; - if (languages.length > 0) { - const translationFile = translations[language]; - const translationsFallbackFile = translations[fallbackLanguage]; - - if (translationFile) { - Translator.load(this, translationFile, false, () => { - if (translationFile !== translationsFallbackFile) { - Translator.load(this, translationsFallbackFile, true, callback); - } else { - callback(); - } - }); - } else { - Translator.load(this, translationsFallbackFile, true, callback); - } - } else { - callback(); + if (languages.length === 0) { + return callback(); } + + const translationFile = translations[language]; + const translationsFallbackFile = translations[fallbackLanguage]; + + if (!translationFile) { + return Translator.load(this, translationsFallbackFile, true, callback); + } + + Translator.load(this, translationFile, false, () => { + if (translationFile !== translationsFallbackFile) { + Translator.load(this, translationsFallbackFile, true, callback); + } else { + callback(); + } + }); }, /** diff --git a/js/translator.js b/js/translator.js index 82eec53a..6cddfae1 100644 --- a/js/translator.js +++ b/js/translator.js @@ -103,26 +103,18 @@ var Translator = (function () { * @param {boolean} isFallback Flag to indicate fallback translations. * @param {Function} callback Function called when done. */ - load: function (module, file, isFallback, callback) { - if (!isFallback) { - Log.log(module.name + " - Load translation: " + file); - } else { - Log.log(module.name + " - Load translation fallback: " + file); + load(module, file, isFallback, callback) { + Log.log(`${module.name} - Load translation${isFallback && " fallback"}: ${file}`); + + if (this.translationsFallback[module.name]) { + return callback(); } - var self = this; - if (!this.translationsFallback[module.name]) { - loadJSON(module.file(file), function (json) { - if (!isFallback) { - self.translations[module.name] = json; - } else { - self.translationsFallback[module.name] = json; - } - callback(); - }); - } else { + loadJSON(module.file(file), (json) => { + const property = isFallback ? "translationsFallback" : "translations"; + this[property][module.name] = json; callback(); - } + }); }, /** From e76fe5e25aef04eb0ae740f6b63587a5db3317fb Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Fri, 29 Jan 2021 22:42:57 +0100 Subject: [PATCH 4/4] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ee2ca4e..54ade1fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ _This release is scheduled to be released on 2021-04-01._ - Fix Issue with weather forecast icons unit tests with different timezones (#2221) - Fix issue with unencoded characters in translated strings when using nunjuck template (`Loading …` as an example) - Fix socket.io backward compatibility with socket v2 clients +- 3rd party module language loading if language is English ## [2.14.0] - 2021-01-01