From 9b278c378f401f837def06d7fbc12c09c35b8ca3 Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Thu, 28 Aug 2025 20:00:53 +0200 Subject: [PATCH 1/8] refactor: streamline and harmonize setupDOMEnvironment function --- tests/e2e/translations_spec.js | 4 +-- tests/unit/classes/translator_spec.js | 37 ++++++++++++--------------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/tests/e2e/translations_spec.js b/tests/e2e/translations_spec.js index 9ea462058d..e43c030d44 100644 --- a/tests/e2e/translations_spec.js +++ b/tests/e2e/translations_spec.js @@ -11,10 +11,10 @@ const translations = require("../../translations/translations"); * @returns {object} The JSDOM window object */ function setupDOMEnvironment () { - const dom = new JSDOM("", { runScripts: "dangerously", resources: "usable" }); + const translatorJs = fs.readFileSync(path.join(__dirname, "..", "..", "js", "translator.js"), "utf-8"); + const dom = new JSDOM("", { url: "http://localhost:3000", runScripts: "dangerously", resources: "usable" }); dom.window.Log = { log: jest.fn(), error: jest.fn() }; - const translatorJs = fs.readFileSync(path.join(__dirname, "..", "..", "js", "translator.js"), "utf-8"); dom.window.translations = translations; dom.window.eval(translatorJs); diff --git a/tests/unit/classes/translator_spec.js b/tests/unit/classes/translator_spec.js index a12c7c6a7c..fe10dac543 100644 --- a/tests/unit/classes/translator_spec.js +++ b/tests/unit/classes/translator_spec.js @@ -6,24 +6,21 @@ const express = require("express"); /** * Helper function to setup DOM environment. - * @param {string} scriptContent - The script content to evaluate * @returns {Promise} The JSDOM window object */ -async function setupDOMEnvironment (scriptContent) { - const dom = new JSDOM("", { runScripts: "outside-only" }); +function setupDOMEnvironment () { + const translatorJs = fs.readFileSync(path.join(__dirname, "..", "..", "..", "js", "translator.js"), "utf-8"); + const dom = new JSDOM("", { url: "http://localhost:3000", runScripts: "outside-only" }); - dom.window.eval(scriptContent); dom.window.Log = { log: jest.fn(), error: jest.fn() }; + dom.window.eval(translatorJs); - await new Promise((resolve) => dom.window.onload = resolve); return dom.window; } describe("Translator", () => { let server; const sockets = new Set(); - const translatorJsPath = path.join(__dirname, "..", "..", "..", "js", "translator.js"); - const translatorJsScriptContent = fs.readFileSync(translatorJsPath, "utf8"); const translationTestData = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "..", "tests", "mocks", "translation_test.json"), "utf8")); beforeAll(() => { @@ -96,7 +93,7 @@ describe("Translator", () => { }; it("should return custom module translation", async () => { - const window = await setupDOMEnvironment(translatorJsScriptContent); + const window = setupDOMEnvironment(); const { Translator } = window; setTranslations(Translator); @@ -108,7 +105,7 @@ describe("Translator", () => { }); it("should return core translation", async () => { - const window = await setupDOMEnvironment(translatorJsScriptContent); + const window = setupDOMEnvironment(); const { Translator } = window; setTranslations(Translator); let translation = Translator.translate({ name: "MMM-Module" }, "FOO"); @@ -118,7 +115,7 @@ describe("Translator", () => { }); it("should return custom module translation fallback", async () => { - const window = await setupDOMEnvironment(translatorJsScriptContent); + const window = setupDOMEnvironment(); const { Translator } = window; setTranslations(Translator); const translation = Translator.translate({ name: "MMM-Module" }, "A key"); @@ -126,7 +123,7 @@ describe("Translator", () => { }); it("should return core translation fallback", async () => { - const window = await setupDOMEnvironment(translatorJsScriptContent); + const window = setupDOMEnvironment(); const { Translator } = window; setTranslations(Translator); const translation = Translator.translate({ name: "MMM-Module" }, "Fallback"); @@ -134,7 +131,7 @@ describe("Translator", () => { }); it("should return translation with placeholder for missing variables", async () => { - const window = await setupDOMEnvironment(translatorJsScriptContent); + const window = setupDOMEnvironment(); const { Translator } = window; setTranslations(Translator); const translation = Translator.translate({ name: "MMM-Module" }, "Hello {username}"); @@ -142,7 +139,7 @@ describe("Translator", () => { }); it("should return key if no translation was found", async () => { - const window = await setupDOMEnvironment(translatorJsScriptContent); + const window = setupDOMEnvironment(); const { Translator } = window; setTranslations(Translator); const translation = Translator.translate({ name: "MMM-Module" }, "MISSING"); @@ -159,7 +156,7 @@ describe("Translator", () => { }; it("should load translations", async () => { - const window = await setupDOMEnvironment(translatorJsScriptContent); + const window = setupDOMEnvironment(); const { Translator } = window; const file = "translation_test.json"; @@ -169,7 +166,7 @@ describe("Translator", () => { }); it("should load translation fallbacks", async () => { - const window = await setupDOMEnvironment(translatorJsScriptContent); + const window = setupDOMEnvironment(); const { Translator } = window; const file = "translation_test.json"; @@ -179,7 +176,7 @@ describe("Translator", () => { }); it("should not load translations, if module fallback exists", async () => { - const window = await setupDOMEnvironment(translatorJsScriptContent); + const window = setupDOMEnvironment(); const { Translator } = window; const file = "translation_test.json"; @@ -197,7 +194,7 @@ describe("Translator", () => { describe("loadCoreTranslations", () => { it("should load core translations and fallback", async () => { - const window = await setupDOMEnvironment(translatorJsScriptContent); + const window = setupDOMEnvironment(); window.translations = { en: "http://localhost:3000/translations/translation_test.json" }; const { Translator } = window; await Translator.loadCoreTranslations("en"); @@ -211,7 +208,7 @@ describe("Translator", () => { }); it("should load core fallback if language cannot be found", async () => { - const window = await setupDOMEnvironment(translatorJsScriptContent); + const window = setupDOMEnvironment(); window.translations = { en: "http://localhost:3000/translations/translation_test.json" }; const { Translator } = window; await Translator.loadCoreTranslations("MISSINGLANG"); @@ -227,7 +224,7 @@ describe("Translator", () => { describe("loadCoreTranslationsFallback", () => { it("should load core translations fallback", async () => { - const window = await setupDOMEnvironment(translatorJsScriptContent); + const window = setupDOMEnvironment(); window.translations = { en: "http://localhost:3000/translations/translation_test.json" }; const { Translator } = window; await Translator.loadCoreTranslationsFallback(); @@ -240,7 +237,7 @@ describe("Translator", () => { }); it("should load core fallback if language cannot be found", async () => { - const window = await setupDOMEnvironment(translatorJsScriptContent); + const window = setupDOMEnvironment(); window.translations = {}; const { Translator } = window; await Translator.loadCoreTranslations(); From 919ed8b2d64cc642613f241fd907e1e5e74e72d4 Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Thu, 28 Aug 2025 20:00:54 +0200 Subject: [PATCH 2/8] refactor: replace `sinon` with `jest` for mocking in translations tests --- package-lock.json | 59 ---------------------------------- package.json | 1 - tests/e2e/translations_spec.js | 25 +++++++------- 3 files changed, 12 insertions(+), 73 deletions(-) diff --git a/package-lock.json b/package-lock.json index 148d42eaad..283bbd9cd7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,6 @@ "markdownlint-cli2": "^0.18.1", "playwright": "^1.55.0", "prettier": "^3.6.2", - "sinon": "^21.0.0", "stylelint": "^16.23.1", "stylelint-config-standard": "^39.0.0", "stylelint-prettier": "^5.0.3" @@ -2717,28 +2716,6 @@ "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@sinonjs/samsam": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", - "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "lodash.get": "^4.4.2", - "type-detect": "^4.1.0" - } - }, - "node_modules/@sinonjs/samsam/node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", @@ -9534,14 +9511,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -12791,34 +12760,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/sinon": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.0.tgz", - "integrity": "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^13.0.5", - "@sinonjs/samsam": "^8.0.1", - "diff": "^7.0.0", - "supports-color": "^7.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/sinon" - } - }, - "node_modules/sinon/node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/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 0ec1a006d7..7e2963cd3f 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,6 @@ "markdownlint-cli2": "^0.18.1", "playwright": "^1.55.0", "prettier": "^3.6.2", - "sinon": "^21.0.0", "stylelint": "^16.23.1", "stylelint-config-standard": "^39.0.0", "stylelint-prettier": "^5.0.3" diff --git a/tests/e2e/translations_spec.js b/tests/e2e/translations_spec.js index e43c030d44..eab536e003 100644 --- a/tests/e2e/translations_spec.js +++ b/tests/e2e/translations_spec.js @@ -3,7 +3,6 @@ const path = require("node:path"); const helmet = require("helmet"); const { JSDOM } = require("jsdom"); const express = require("express"); -const sinon = require("sinon"); const translations = require("../../translations/translations"); /** @@ -76,15 +75,15 @@ describe("translations", () => { const { Translator, Module, config } = dom.window; config.language = "en"; - Translator.load = sinon.stub().callsFake((_m, _f, _fb) => null); + Translator.load = jest.fn().mockImplementation((_m, _f, _fb) => null); Module.register("name", { getTranslations: () => translations }); const MMM = Module.create("name"); await MMM.loadTranslations(); - expect(Translator.load.args).toHaveLength(1); - expect(Translator.load.calledWith(MMM, "translations/en.json", false)).toBe(true); + expect(Translator.load.mock.calls).toHaveLength(1); + expect(Translator.load).toHaveBeenCalledWith(MMM, "translations/en.json", false); }); it("should load translation + fallback file", async () => { @@ -93,16 +92,16 @@ describe("translations", () => { }); const { Translator, Module } = dom.window; - Translator.load = sinon.stub().callsFake((_m, _f, _fb) => null); + Translator.load = jest.fn().mockImplementation((_m, _f, _fb) => null); Module.register("name", { getTranslations: () => translations }); const MMM = Module.create("name"); await MMM.loadTranslations(); - expect(Translator.load.args).toHaveLength(2); - expect(Translator.load.calledWith(MMM, "translations/de.json", false)).toBe(true); - expect(Translator.load.calledWith(MMM, "translations/en.json", true)).toBe(true); + expect(Translator.load.mock.calls).toHaveLength(2); + expect(Translator.load).toHaveBeenCalledWith(MMM, "translations/de.json", false); + expect(Translator.load).toHaveBeenCalledWith(MMM, "translations/en.json", true); }); it("should load translation fallback file", async () => { @@ -112,15 +111,15 @@ describe("translations", () => { const { Translator, Module, config } = dom.window; config.language = "--"; - Translator.load = sinon.stub().callsFake((_m, _f, _fb) => null); + Translator.load = jest.fn().mockImplementation((_m, _f, _fb) => null); Module.register("name", { getTranslations: () => translations }); const MMM = Module.create("name"); await MMM.loadTranslations(); - expect(Translator.load.args).toHaveLength(1); - expect(Translator.load.calledWith(MMM, "translations/en.json", true)).toBe(true); + expect(Translator.load.mock.calls).toHaveLength(1); + expect(Translator.load).toHaveBeenCalledWith(MMM, "translations/en.json", true); }); it("should load no file", async () => { @@ -129,14 +128,14 @@ describe("translations", () => { }); const { Translator, Module } = dom.window; - Translator.load = sinon.stub(); + Translator.load = jest.fn(); Module.register("name", {}); const MMM = Module.create("name"); await MMM.loadTranslations(); - expect(Translator.load.callCount).toBe(0); + expect(Translator.load.mock.calls).toHaveLength(0); }); }); From 27297ca7f3e09f7b85a5ba0409f4263bfc0ebc3b Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Thu, 28 Aug 2025 20:00:54 +0200 Subject: [PATCH 3/8] refactor: remove unnecessary onload promises in translation tests --- tests/e2e/translations_spec.js | 33 ++++----------------------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/tests/e2e/translations_spec.js b/tests/e2e/translations_spec.js index eab536e003..e40e98a6a8 100644 --- a/tests/e2e/translations_spec.js +++ b/tests/e2e/translations_spec.js @@ -69,10 +69,6 @@ describe("translations", () => { }); it("should load translation file", async () => { - await new Promise((resolve) => { - dom.window.onload = resolve; - }); - const { Translator, Module, config } = dom.window; config.language = "en"; Translator.load = jest.fn().mockImplementation((_m, _f, _fb) => null); @@ -87,10 +83,6 @@ describe("translations", () => { }); it("should load translation + fallback file", async () => { - await new Promise((resolve) => { - dom.window.onload = resolve; - }); - const { Translator, Module } = dom.window; Translator.load = jest.fn().mockImplementation((_m, _f, _fb) => null); @@ -105,10 +97,6 @@ describe("translations", () => { }); it("should load translation fallback file", async () => { - await new Promise((resolve) => { - dom.window.onload = resolve; - }); - const { Translator, Module, config } = dom.window; config.language = "--"; Translator.load = jest.fn().mockImplementation((_m, _f, _fb) => null); @@ -123,10 +111,6 @@ describe("translations", () => { }); it("should load no file", async () => { - await new Promise((resolve) => { - dom.window.onload = resolve; - }); - const { Translator, Module } = dom.window; Translator.load = jest.fn(); @@ -151,10 +135,6 @@ describe("translations", () => { it(`should parse ${language}`, async () => { const window = setupDOMEnvironment(); - await new Promise((resolve) => { - window.onload = resolve; - }); - const { Translator } = window; await Translator.load(mmm, translations[language], false); @@ -186,16 +166,11 @@ describe("translations", () => { }; // Function to initialize JSDOM and load translations - const initializeTranslationDOM = (language) => { + const initializeTranslationDOM = async (language) => { const window = setupDOMEnvironment(); - - return new Promise((resolve) => { - window.onload = async () => { - const { Translator } = window; - await Translator.load(mmm, translations[language], false); - resolve(Translator.translations[mmm.name]); - }; - }); + const { Translator } = window; + await Translator.load(mmm, translations[language], false); + return Translator.translations[mmm.name]; }; beforeAll(async () => { From 242288d0783e55f2ae57d3141b128a24b1167afc Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Thu, 28 Aug 2025 20:00:54 +0200 Subject: [PATCH 4/8] refactor: update `setupDOMEnvironment` documentation for clarity --- tests/e2e/translations_spec.js | 3 ++- tests/unit/classes/translator_spec.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/e2e/translations_spec.js b/tests/e2e/translations_spec.js index e40e98a6a8..3caa4a64a9 100644 --- a/tests/e2e/translations_spec.js +++ b/tests/e2e/translations_spec.js @@ -7,7 +7,8 @@ const translations = require("../../translations/translations"); /** * Helper function to setup DOM environment. - * @returns {object} The JSDOM window object + * Loads the Translator script into the JSDOM window. + * @returns {object} The JSDOM window object with translations and Translator loaded */ function setupDOMEnvironment () { const translatorJs = fs.readFileSync(path.join(__dirname, "..", "..", "js", "translator.js"), "utf-8"); diff --git a/tests/unit/classes/translator_spec.js b/tests/unit/classes/translator_spec.js index fe10dac543..db4137cc2a 100644 --- a/tests/unit/classes/translator_spec.js +++ b/tests/unit/classes/translator_spec.js @@ -6,7 +6,8 @@ const express = require("express"); /** * Helper function to setup DOM environment. - * @returns {Promise} The JSDOM window object + * Loads the Translator script into the JSDOM window. + * @returns {object} The JSDOM window object with Translator loaded */ function setupDOMEnvironment () { const translatorJs = fs.readFileSync(path.join(__dirname, "..", "..", "..", "js", "translator.js"), "utf-8"); From e8e07d4a2a6b0b087e473a6900d61b1b0f96a367 Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Thu, 28 Aug 2025 20:00:54 +0200 Subject: [PATCH 5/8] refactor: remove unnecessary delays in translation tests --- tests/unit/classes/translator_spec.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/unit/classes/translator_spec.js b/tests/unit/classes/translator_spec.js index db4137cc2a..1a666f5cb0 100644 --- a/tests/unit/classes/translator_spec.js +++ b/tests/unit/classes/translator_spec.js @@ -202,8 +202,6 @@ describe("Translator", () => { const en = translationTestData; - await new Promise((resolve) => setTimeout(resolve, 500)); - expect(Translator.coreTranslations).toEqual(en); expect(Translator.coreTranslationsFallback).toEqual(en); }); @@ -216,8 +214,6 @@ describe("Translator", () => { const en = translationTestData; - await new Promise((resolve) => setTimeout(resolve, 500)); - expect(Translator.coreTranslations).toEqual({}); expect(Translator.coreTranslationsFallback).toEqual(en); }); @@ -232,8 +228,6 @@ describe("Translator", () => { const en = translationTestData; - await new Promise((resolve) => setTimeout(resolve, 500)); - expect(Translator.coreTranslationsFallback).toEqual(en); }); @@ -243,8 +237,6 @@ describe("Translator", () => { const { Translator } = window; await Translator.loadCoreTranslations(); - await new Promise((resolve) => setTimeout(resolve, 500)); - expect(Translator.coreTranslationsFallback).toEqual({}); }); }); From b37781adda05234dd992254f39449a7e9a236b5c Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Thu, 28 Aug 2025 20:00:54 +0200 Subject: [PATCH 6/8] refactor: rename setupDOMEnvironment to createTranslationTestEnvironment for clarity --- tests/e2e/translations_spec.js | 42 +++++++++++------------ tests/unit/classes/translator_spec.js | 48 ++++++++++----------------- 2 files changed, 38 insertions(+), 52 deletions(-) diff --git a/tests/e2e/translations_spec.js b/tests/e2e/translations_spec.js index 3caa4a64a9..c8489aced0 100644 --- a/tests/e2e/translations_spec.js +++ b/tests/e2e/translations_spec.js @@ -6,19 +6,21 @@ const express = require("express"); const translations = require("../../translations/translations"); /** - * Helper function to setup DOM environment. - * Loads the Translator script into the JSDOM window. - * @returns {object} The JSDOM window object with translations and Translator loaded + * Helper function to create a fresh Translator instance with DOM environment. + * @returns {object} Object containing window and Translator */ -function setupDOMEnvironment () { +function createTranslationTestEnvironment () { + // Setup DOM environment with Translator const translatorJs = fs.readFileSync(path.join(__dirname, "..", "..", "js", "translator.js"), "utf-8"); - const dom = new JSDOM("", { url: "http://localhost:3000", runScripts: "dangerously", resources: "usable" }); + const dom = new JSDOM("", { url: "http://localhost:3000", runScripts: "outside-only" }); dom.window.Log = { log: jest.fn(), error: jest.fn() }; dom.window.translations = translations; dom.window.eval(translatorJs); - return dom.window; + const window = dom.window; + + return { window, Translator: window.Translator }; } describe("translations", () => { @@ -52,21 +54,22 @@ describe("translations", () => { let dom; beforeEach(() => { - // Create a new JSDOM instance for each test - const window = setupDOMEnvironment(); - dom = { window }; + // Create a new translation test environment for each test + const env = createTranslationTestEnvironment(); + const window = env.window; - // Additional setup for loadTranslations tests - dom.window.Translator = {}; - dom.window.config = { language: "de" }; - - // Load class.js and module.js content directly + // Load class.js and module.js content directly for loadTranslations tests const classJs = fs.readFileSync(path.join(__dirname, "..", "..", "js", "class.js"), "utf-8"); const moduleJs = fs.readFileSync(path.join(__dirname, "..", "..", "js", "module.js"), "utf-8"); // Execute the scripts in the JSDOM context - dom.window.eval(classJs); - dom.window.eval(moduleJs); + window.eval(classJs); + window.eval(moduleJs); + + // Additional setup for loadTranslations tests + window.config = { language: "de" }; + + dom = { window }; }); it("should load translation file", async () => { @@ -134,9 +137,7 @@ describe("translations", () => { describe("parsing language files through the Translator class", () => { for (const language in translations) { it(`should parse ${language}`, async () => { - const window = setupDOMEnvironment(); - - const { Translator } = window; + const { Translator } = createTranslationTestEnvironment(); await Translator.load(mmm, translations[language], false); expect(typeof Translator.translations[mmm.name]).toBe("object"); @@ -168,8 +169,7 @@ describe("translations", () => { // Function to initialize JSDOM and load translations const initializeTranslationDOM = async (language) => { - const window = setupDOMEnvironment(); - const { Translator } = window; + const { Translator } = createTranslationTestEnvironment(); await Translator.load(mmm, translations[language], false); return Translator.translations[mmm.name]; }; diff --git a/tests/unit/classes/translator_spec.js b/tests/unit/classes/translator_spec.js index 1a666f5cb0..9e984f52d8 100644 --- a/tests/unit/classes/translator_spec.js +++ b/tests/unit/classes/translator_spec.js @@ -5,18 +5,17 @@ const { JSDOM } = require("jsdom"); const express = require("express"); /** - * Helper function to setup DOM environment. - * Loads the Translator script into the JSDOM window. - * @returns {object} The JSDOM window object with Translator loaded + * Helper function to create a fresh Translator instance with DOM environment. + * @returns {object} Object containing window and Translator */ -function setupDOMEnvironment () { +function createTranslationTestEnvironment () { const translatorJs = fs.readFileSync(path.join(__dirname, "..", "..", "..", "js", "translator.js"), "utf-8"); const dom = new JSDOM("", { url: "http://localhost:3000", runScripts: "outside-only" }); dom.window.Log = { log: jest.fn(), error: jest.fn() }; dom.window.eval(translatorJs); - return dom.window; + return { window: dom.window, Translator: dom.window.Translator }; } describe("Translator", () => { @@ -94,8 +93,7 @@ describe("Translator", () => { }; it("should return custom module translation", async () => { - const window = setupDOMEnvironment(); - const { Translator } = window; + const { Translator } = createTranslationTestEnvironment(); setTranslations(Translator); let translation = Translator.translate({ name: "MMM-Module" }, "Hello"); @@ -106,8 +104,7 @@ describe("Translator", () => { }); it("should return core translation", async () => { - const window = setupDOMEnvironment(); - const { Translator } = window; + const { Translator } = createTranslationTestEnvironment(); setTranslations(Translator); let translation = Translator.translate({ name: "MMM-Module" }, "FOO"); expect(translation).toBe("Foo"); @@ -116,32 +113,28 @@ describe("Translator", () => { }); it("should return custom module translation fallback", async () => { - const window = setupDOMEnvironment(); - const { Translator } = window; + const { Translator } = createTranslationTestEnvironment(); setTranslations(Translator); const translation = Translator.translate({ name: "MMM-Module" }, "A key"); expect(translation).toBe("A translation"); }); it("should return core translation fallback", async () => { - const window = setupDOMEnvironment(); - const { Translator } = window; + const { Translator } = createTranslationTestEnvironment(); setTranslations(Translator); const translation = Translator.translate({ name: "MMM-Module" }, "Fallback"); expect(translation).toBe("core fallback"); }); it("should return translation with placeholder for missing variables", async () => { - const window = setupDOMEnvironment(); - const { Translator } = window; + const { Translator } = createTranslationTestEnvironment(); setTranslations(Translator); const translation = Translator.translate({ name: "MMM-Module" }, "Hello {username}"); expect(translation).toBe("Hallo {username}"); }); it("should return key if no translation was found", async () => { - const window = setupDOMEnvironment(); - const { Translator } = window; + const { Translator } = createTranslationTestEnvironment(); setTranslations(Translator); const translation = Translator.translate({ name: "MMM-Module" }, "MISSING"); expect(translation).toBe("MISSING"); @@ -157,8 +150,7 @@ describe("Translator", () => { }; it("should load translations", async () => { - const window = setupDOMEnvironment(); - const { Translator } = window; + const { Translator } = createTranslationTestEnvironment(); const file = "translation_test.json"; await Translator.load(mmm, file, false); @@ -167,8 +159,7 @@ describe("Translator", () => { }); it("should load translation fallbacks", async () => { - const window = setupDOMEnvironment(); - const { Translator } = window; + const { Translator } = createTranslationTestEnvironment(); const file = "translation_test.json"; await Translator.load(mmm, file, true); @@ -177,8 +168,7 @@ describe("Translator", () => { }); it("should not load translations, if module fallback exists", async () => { - const window = setupDOMEnvironment(); - const { Translator } = window; + const { Translator } = createTranslationTestEnvironment(); const file = "translation_test.json"; Translator.translationsFallback[mmm.name] = { @@ -195,9 +185,8 @@ describe("Translator", () => { describe("loadCoreTranslations", () => { it("should load core translations and fallback", async () => { - const window = setupDOMEnvironment(); + const { window, Translator } = createTranslationTestEnvironment(); window.translations = { en: "http://localhost:3000/translations/translation_test.json" }; - const { Translator } = window; await Translator.loadCoreTranslations("en"); const en = translationTestData; @@ -207,9 +196,8 @@ describe("Translator", () => { }); it("should load core fallback if language cannot be found", async () => { - const window = setupDOMEnvironment(); + const { window, Translator } = createTranslationTestEnvironment(); window.translations = { en: "http://localhost:3000/translations/translation_test.json" }; - const { Translator } = window; await Translator.loadCoreTranslations("MISSINGLANG"); const en = translationTestData; @@ -221,9 +209,8 @@ describe("Translator", () => { describe("loadCoreTranslationsFallback", () => { it("should load core translations fallback", async () => { - const window = setupDOMEnvironment(); + const { window, Translator } = createTranslationTestEnvironment(); window.translations = { en: "http://localhost:3000/translations/translation_test.json" }; - const { Translator } = window; await Translator.loadCoreTranslationsFallback(); const en = translationTestData; @@ -232,9 +219,8 @@ describe("Translator", () => { }); it("should load core fallback if language cannot be found", async () => { - const window = setupDOMEnvironment(); + const { window, Translator } = createTranslationTestEnvironment(); window.translations = {}; - const { Translator } = window; await Translator.loadCoreTranslations(); expect(Translator.coreTranslationsFallback).toEqual({}); From 9b769b28fe7337b9985c46ad8873e661e6294681 Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Thu, 28 Aug 2025 20:12:57 +0200 Subject: [PATCH 7/8] test: resolve port conflicts in translation tests Use port 3001 for translator_spec.js instead of 3000 to avoid conflicts with translations_spec.js when running tests simultaneously. --- tests/unit/classes/translator_spec.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/unit/classes/translator_spec.js b/tests/unit/classes/translator_spec.js index 9e984f52d8..37cb999039 100644 --- a/tests/unit/classes/translator_spec.js +++ b/tests/unit/classes/translator_spec.js @@ -10,7 +10,7 @@ const express = require("express"); */ function createTranslationTestEnvironment () { const translatorJs = fs.readFileSync(path.join(__dirname, "..", "..", "..", "js", "translator.js"), "utf-8"); - const dom = new JSDOM("", { url: "http://localhost:3000", runScripts: "outside-only" }); + const dom = new JSDOM("", { url: "http://localhost:3001", runScripts: "outside-only" }); dom.window.Log = { log: jest.fn(), error: jest.fn() }; dom.window.eval(translatorJs); @@ -32,7 +32,7 @@ describe("Translator", () => { }); app.use("/translations", express.static(path.join(__dirname, "..", "..", "..", "tests", "mocks"))); - server = app.listen(3000); + server = app.listen(3001); server.on("connection", (socket) => { sockets.add(socket); @@ -145,7 +145,7 @@ describe("Translator", () => { const mmm = { name: "TranslationTest", file (file) { - return `http://localhost:3000/translations/${file}`; + return `http://localhost:3001/translations/${file}`; } }; @@ -186,7 +186,7 @@ describe("Translator", () => { describe("loadCoreTranslations", () => { it("should load core translations and fallback", async () => { const { window, Translator } = createTranslationTestEnvironment(); - window.translations = { en: "http://localhost:3000/translations/translation_test.json" }; + window.translations = { en: "http://localhost:3001/translations/translation_test.json" }; await Translator.loadCoreTranslations("en"); const en = translationTestData; @@ -197,7 +197,7 @@ describe("Translator", () => { it("should load core fallback if language cannot be found", async () => { const { window, Translator } = createTranslationTestEnvironment(); - window.translations = { en: "http://localhost:3000/translations/translation_test.json" }; + window.translations = { en: "http://localhost:3001/translations/translation_test.json" }; await Translator.loadCoreTranslations("MISSINGLANG"); const en = translationTestData; @@ -210,7 +210,7 @@ describe("Translator", () => { describe("loadCoreTranslationsFallback", () => { it("should load core translations fallback", async () => { const { window, Translator } = createTranslationTestEnvironment(); - window.translations = { en: "http://localhost:3000/translations/translation_test.json" }; + window.translations = { en: "http://localhost:3001/translations/translation_test.json" }; await Translator.loadCoreTranslationsFallback(); const en = translationTestData; From 16225713d6377bd001b2bea1fef02190ba274246 Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Thu, 28 Aug 2025 20:46:29 +0200 Subject: [PATCH 8/8] chore: add changelog entry --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79a9ee8ffb..9da03d9c26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,13 @@ Thanks to: @dathbe. - [tests] refactor: add `setupDOMEnvironment` helper function to eliminate repetitive JSDOM setup code (#3860) - [tests] replace `console` with `Log` in calendar `debug.js` to avoid exception in eslint config (#3846) - [tests] speed up e2e tests, cleanup and stabilize weather e2e tests, update snapshot url (#3847, #3848, #3861) +- [tests] refactor translation tests (#3866) + - Remove `sinon` dependency in favor of Jest native mocking + - Unify test helper functions across translation test suites + - Rename `setupDOMEnvironment` to `createTranslationTestEnvironment` for consistency + - Simplify DOM setup by removing unnecessary Promise/async patterns + - Avoid potential port conflicts by using port 3001 for translator unit tests + - Improve test reliability and maintainability ### Updated