tests: add setupDOMEnvironment helper function to eliminate repetitive JSDOM setup code (#3860)

The helper function allows to remove a lot of repetitive code, making
the tests clearer and easier to maintain.
This commit is contained in:
Kristjan ESPERANTO
2025-08-19 22:46:59 +02:00
committed by GitHub
parent 1b31cf19e9
commit 83d15aaaaa
3 changed files with 70 additions and 102 deletions

View File

@@ -20,6 +20,7 @@ Thanks to: @dathbe.
- [refactor] Add new file `js/module_functions.js` to move code used in several modules to one place (#3837) - [refactor] Add new file `js/module_functions.js` to move code used in several modules to one place (#3837)
- [tests] refactor: simplify jest config file (#3844) - [tests] refactor: simplify jest config file (#3844)
- [tests] refactor: extract constants for weather electron tests (#3845) - [tests] refactor: extract constants for weather electron tests (#3845)
- [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] 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 (#3847, #3848) - [tests] speed up e2e tests, cleanup and stabilize weather e2e tests (#3847, #3848)

View File

@@ -6,6 +6,21 @@ const express = require("express");
const sinon = require("sinon"); const sinon = require("sinon");
const translations = require("../../translations/translations"); const translations = require("../../translations/translations");
/**
* Helper function to setup DOM environment.
* @returns {object} The JSDOM window object
*/
function setupDOMEnvironment () {
const dom = new JSDOM("", { 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);
return dom.window;
}
describe("translations", () => { describe("translations", () => {
let server; let server;
@@ -38,10 +53,10 @@ describe("translations", () => {
beforeEach(() => { beforeEach(() => {
// Create a new JSDOM instance for each test // Create a new JSDOM instance for each test
dom = new JSDOM("", { runScripts: "dangerously", resources: "usable" }); const window = setupDOMEnvironment();
dom = { window };
// Mock the necessary global objects // Additional setup for loadTranslations tests
dom.window.Log = { log: jest.fn(), error: jest.fn() };
dom.window.Translator = {}; dom.window.Translator = {};
dom.window.config = { language: "de" }; dom.window.config = { language: "de" };
@@ -132,21 +147,16 @@ describe("translations", () => {
} }
}; };
const translatorJs = fs.readFileSync(path.join(__dirname, "..", "..", "js", "translator.js"), "utf-8");
describe("parsing language files through the Translator class", () => { describe("parsing language files through the Translator class", () => {
for (const language in translations) { for (const language in translations) {
it(`should parse ${language}`, async () => { it(`should parse ${language}`, async () => {
const dom = new JSDOM("", { runScripts: "dangerously", resources: "usable" }); const window = setupDOMEnvironment();
dom.window.Log = { log: jest.fn() };
dom.window.translations = translations;
dom.window.eval(translatorJs);
await new Promise((resolve) => { await new Promise((resolve) => {
dom.window.onload = resolve; window.onload = resolve;
}); });
const { Translator } = dom.window; const { Translator } = window;
await Translator.load(mmm, translations[language], false); await Translator.load(mmm, translations[language], false);
expect(typeof Translator.translations[mmm.name]).toBe("object"); expect(typeof Translator.translations[mmm.name]).toBe("object");
@@ -178,14 +188,11 @@ describe("translations", () => {
// Function to initialize JSDOM and load translations // Function to initialize JSDOM and load translations
const initializeTranslationDOM = (language) => { const initializeTranslationDOM = (language) => {
const dom = new JSDOM("", { runScripts: "dangerously", resources: "usable" }); const window = setupDOMEnvironment();
dom.window.Log = { log: jest.fn() };
dom.window.translations = translations;
dom.window.eval(translatorJs);
return new Promise((resolve) => { return new Promise((resolve) => {
dom.window.onload = async () => { window.onload = async () => {
const { Translator } = dom.window; const { Translator } = window;
await Translator.load(mmm, translations[language], false); await Translator.load(mmm, translations[language], false);
resolve(Translator.translations[mmm.name]); resolve(Translator.translations[mmm.name]);
}; };

View File

@@ -4,6 +4,21 @@ const helmet = require("helmet");
const { JSDOM } = require("jsdom"); const { JSDOM } = require("jsdom");
const express = require("express"); const express = require("express");
/**
* Helper function to setup DOM environment.
* @param {string} scriptContent - The script content to evaluate
* @returns {Promise<object>} The JSDOM window object
*/
async function setupDOMEnvironment (scriptContent) {
const dom = new JSDOM("", { runScripts: "outside-only" });
dom.window.eval(scriptContent);
dom.window.Log = { log: jest.fn(), error: jest.fn() };
await new Promise((resolve) => dom.window.onload = resolve);
return dom.window;
}
describe("Translator", () => { describe("Translator", () => {
let server; let server;
const sockets = new Set(); const sockets = new Set();
@@ -81,12 +96,8 @@ describe("Translator", () => {
}; };
it("should return custom module translation", async () => { it("should return custom module translation", async () => {
const dom = new JSDOM("", { runScripts: "outside-only" }); const window = await setupDOMEnvironment(translatorJsScriptContent);
dom.window.eval(translatorJsScriptContent); const { Translator } = window;
await new Promise((resolve) => dom.window.onload = resolve);
const { Translator } = dom.window;
setTranslations(Translator); setTranslations(Translator);
let translation = Translator.translate({ name: "MMM-Module" }, "Hello"); let translation = Translator.translate({ name: "MMM-Module" }, "Hello");
@@ -97,12 +108,8 @@ describe("Translator", () => {
}); });
it("should return core translation", async () => { it("should return core translation", async () => {
const dom = new JSDOM("", { runScripts: "outside-only" }); const window = await setupDOMEnvironment(translatorJsScriptContent);
dom.window.eval(translatorJsScriptContent); const { Translator } = window;
await new Promise((resolve) => dom.window.onload = resolve);
const { Translator } = dom.window;
setTranslations(Translator); setTranslations(Translator);
let translation = Translator.translate({ name: "MMM-Module" }, "FOO"); let translation = Translator.translate({ name: "MMM-Module" }, "FOO");
expect(translation).toBe("Foo"); expect(translation).toBe("Foo");
@@ -111,48 +118,32 @@ describe("Translator", () => {
}); });
it("should return custom module translation fallback", async () => { it("should return custom module translation fallback", async () => {
const dom = new JSDOM("", { runScripts: "outside-only" }); const window = await setupDOMEnvironment(translatorJsScriptContent);
dom.window.eval(translatorJsScriptContent); const { Translator } = window;
await new Promise((resolve) => dom.window.onload = resolve);
const { Translator } = dom.window;
setTranslations(Translator); setTranslations(Translator);
const translation = Translator.translate({ name: "MMM-Module" }, "A key"); const translation = Translator.translate({ name: "MMM-Module" }, "A key");
expect(translation).toBe("A translation"); expect(translation).toBe("A translation");
}); });
it("should return core translation fallback", async () => { it("should return core translation fallback", async () => {
const dom = new JSDOM("", { runScripts: "outside-only" }); const window = await setupDOMEnvironment(translatorJsScriptContent);
dom.window.eval(translatorJsScriptContent); const { Translator } = window;
await new Promise((resolve) => dom.window.onload = resolve);
const { Translator } = dom.window;
setTranslations(Translator); setTranslations(Translator);
const translation = Translator.translate({ name: "MMM-Module" }, "Fallback"); const translation = Translator.translate({ name: "MMM-Module" }, "Fallback");
expect(translation).toBe("core fallback"); expect(translation).toBe("core fallback");
}); });
it("should return translation with placeholder for missing variables", async () => { it("should return translation with placeholder for missing variables", async () => {
const dom = new JSDOM("", { runScripts: "outside-only" }); const window = await setupDOMEnvironment(translatorJsScriptContent);
dom.window.eval(translatorJsScriptContent); const { Translator } = window;
await new Promise((resolve) => dom.window.onload = resolve);
const { Translator } = dom.window;
setTranslations(Translator); setTranslations(Translator);
const translation = Translator.translate({ name: "MMM-Module" }, "Hello {username}"); const translation = Translator.translate({ name: "MMM-Module" }, "Hello {username}");
expect(translation).toBe("Hallo {username}"); expect(translation).toBe("Hallo {username}");
}); });
it("should return key if no translation was found", async () => { it("should return key if no translation was found", async () => {
const dom = new JSDOM("", { runScripts: "outside-only" }); const window = await setupDOMEnvironment(translatorJsScriptContent);
dom.window.eval(translatorJsScriptContent); const { Translator } = window;
await new Promise((resolve) => dom.window.onload = resolve);
const { Translator } = dom.window;
setTranslations(Translator); setTranslations(Translator);
const translation = Translator.translate({ name: "MMM-Module" }, "MISSING"); const translation = Translator.translate({ name: "MMM-Module" }, "MISSING");
expect(translation).toBe("MISSING"); expect(translation).toBe("MISSING");
@@ -168,12 +159,8 @@ describe("Translator", () => {
}; };
it("should load translations", async () => { it("should load translations", async () => {
const dom = new JSDOM("", { runScripts: "outside-only" }); const window = await setupDOMEnvironment(translatorJsScriptContent);
dom.window.eval(translatorJsScriptContent); const { Translator } = window;
dom.window.Log = { log: jest.fn() };
await new Promise((resolve) => dom.window.onload = resolve);
const { Translator } = dom.window;
const file = "translation_test.json"; const file = "translation_test.json";
await Translator.load(mmm, file, false); await Translator.load(mmm, file, false);
@@ -182,30 +169,20 @@ describe("Translator", () => {
}); });
it("should load translation fallbacks", async () => { it("should load translation fallbacks", async () => {
const dom = new JSDOM("", { runScripts: "outside-only" }); const window = await setupDOMEnvironment(translatorJsScriptContent);
dom.window.eval(translatorJsScriptContent); const { Translator } = window;
await new Promise((resolve) => dom.window.onload = resolve);
const { Translator } = dom.window;
const file = "translation_test.json"; const file = "translation_test.json";
dom.window.Log = { log: jest.fn() };
await Translator.load(mmm, file, true); await Translator.load(mmm, file, true);
const json = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "..", "tests", "mocks", file), "utf8")); const json = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "..", "tests", "mocks", file), "utf8"));
expect(Translator.translationsFallback[mmm.name]).toEqual(json); expect(Translator.translationsFallback[mmm.name]).toEqual(json);
}); });
it("should not load translations, if module fallback exists", async () => { it("should not load translations, if module fallback exists", async () => {
const dom = new JSDOM("", { runScripts: "outside-only" }); const window = await setupDOMEnvironment(translatorJsScriptContent);
dom.window.eval(translatorJsScriptContent); const { Translator } = window;
await new Promise((resolve) => dom.window.onload = resolve);
const { Translator } = dom.window;
const file = "translation_test.json"; const file = "translation_test.json";
dom.window.Log = { log: jest.fn() };
Translator.translationsFallback[mmm.name] = { Translator.translationsFallback[mmm.name] = {
Hello: "Hallo" Hello: "Hallo"
}; };
@@ -220,13 +197,9 @@ describe("Translator", () => {
describe("loadCoreTranslations", () => { describe("loadCoreTranslations", () => {
it("should load core translations and fallback", async () => { it("should load core translations and fallback", async () => {
const dom = new JSDOM("", { runScripts: "outside-only" }); const window = await setupDOMEnvironment(translatorJsScriptContent);
dom.window.eval(translatorJsScriptContent); window.translations = { en: "http://localhost:3000/translations/translation_test.json" };
dom.window.translations = { en: "http://localhost:3000/translations/translation_test.json" }; const { Translator } = window;
dom.window.Log = { log: jest.fn() };
await new Promise((resolve) => dom.window.onload = resolve);
const { Translator } = dom.window;
await Translator.loadCoreTranslations("en"); await Translator.loadCoreTranslations("en");
const en = translationTestData; const en = translationTestData;
@@ -238,13 +211,9 @@ describe("Translator", () => {
}); });
it("should load core fallback if language cannot be found", async () => { it("should load core fallback if language cannot be found", async () => {
const dom = new JSDOM("", { runScripts: "outside-only" }); const window = await setupDOMEnvironment(translatorJsScriptContent);
dom.window.eval(translatorJsScriptContent); window.translations = { en: "http://localhost:3000/translations/translation_test.json" };
dom.window.translations = { en: "http://localhost:3000/translations/translation_test.json" }; const { Translator } = window;
dom.window.Log = { log: jest.fn() };
await new Promise((resolve) => dom.window.onload = resolve);
const { Translator } = dom.window;
await Translator.loadCoreTranslations("MISSINGLANG"); await Translator.loadCoreTranslations("MISSINGLANG");
const en = translationTestData; const en = translationTestData;
@@ -258,13 +227,9 @@ describe("Translator", () => {
describe("loadCoreTranslationsFallback", () => { describe("loadCoreTranslationsFallback", () => {
it("should load core translations fallback", async () => { it("should load core translations fallback", async () => {
const dom = new JSDOM("", { runScripts: "outside-only" }); const window = await setupDOMEnvironment(translatorJsScriptContent);
dom.window.eval(translatorJsScriptContent); window.translations = { en: "http://localhost:3000/translations/translation_test.json" };
dom.window.translations = { en: "http://localhost:3000/translations/translation_test.json" }; const { Translator } = window;
dom.window.Log = { log: jest.fn() };
await new Promise((resolve) => dom.window.onload = resolve);
const { Translator } = dom.window;
await Translator.loadCoreTranslationsFallback(); await Translator.loadCoreTranslationsFallback();
const en = translationTestData; const en = translationTestData;
@@ -275,14 +240,9 @@ describe("Translator", () => {
}); });
it("should load core fallback if language cannot be found", async () => { it("should load core fallback if language cannot be found", async () => {
const dom = new JSDOM("", { runScripts: "outside-only" }); const window = await setupDOMEnvironment(translatorJsScriptContent);
dom.window.eval(translatorJsScriptContent); window.translations = {};
dom.window.translations = {}; const { Translator } = window;
dom.window.Log = { log: jest.fn() };
await new Promise((resolve) => dom.window.onload = resolve);
const { Translator } = dom.window;
await Translator.loadCoreTranslations(); await Translator.loadCoreTranslations();
await new Promise((resolve) => setTimeout(resolve, 500)); await new Promise((resolve) => setTimeout(resolve, 500));