chore: import upstream snapshot with attribution
Enforce Pull-Request Rules / check (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:19:24 +08:00
commit dbe3ade0dc
449 changed files with 71828 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
const deprecated = require("../../../js/deprecated");
describe("Deprecated", () => {
it("should be an object", () => {
expect(typeof deprecated).toBe("object");
});
it("should contain clock array with deprecated options as strings", () => {
expect(Array.isArray(["deprecated.clock"])).toBe(true);
for (let option of deprecated.configs) {
expect(typeof option).toBe("string");
}
expect(deprecated.clock).toEqual(expect.arrayContaining(["secondsColor"]));
});
});
+299
View File
@@ -0,0 +1,299 @@
const path = require("node:path");
const { pathToFileURL } = require("node:url");
describe("File js/module (cloneObject)", () => {
describe("Test function cloneObject", () => {
let clone;
let Module;
let originalWindow;
let originalLog;
let originalConfig;
let originalMM;
let originalTranslator;
let originalNunjucks;
beforeAll(async () => {
originalWindow = global.window;
originalLog = global.Log;
originalConfig = global.config;
originalMM = global.MM;
originalTranslator = global.Translator;
originalNunjucks = global.nunjucks;
global.window = { mmVersion: "2.0.0" };
global.Log = { log: () => {}, info: () => {}, warn: () => {}, error: () => {}, debug: () => {} };
global.config = { language: "en" };
global.MM = {
hideModule: () => {},
showModule: () => {},
sendNotification: () => {},
updateDom: () => {}
};
global.Translator = {
load: () => Promise.resolve(),
translate: () => ""
};
global.nunjucks = {
Environment () {
this.addFilter = () => {};
this.renderString = () => "";
this.render = (_template, _data, callback) => callback(null, "");
},
WebLoader () {},
runtime: {
markSafe: (str) => str
}
};
const modulePath = pathToFileURL(path.join(__dirname, "..", "..", "..", "js", "module.js")).href;
const loaded = await import(`${modulePath}?test=${Date.now()}`);
clone = loaded.cloneObject;
Module = loaded.Module;
});
afterAll(() => {
global.window = originalWindow;
global.Log = originalLog;
global.config = originalConfig;
global.MM = originalMM;
global.Translator = originalTranslator;
global.nunjucks = originalNunjucks;
});
it("should clone object", () => {
const expected = { name: "Rodrigo", web: "https://rodrigoramirez.com", project: "MagicMirror" };
const obj = clone(expected);
expect(obj).toEqual(expected);
expect(expected === obj).toBe(false);
});
it("should clone array", () => {
const expected = [1, null, undefined, "TEST"];
const obj = clone(expected);
expect(obj).toEqual(expected);
expect(expected === obj).toBe(false);
});
it("should clone number", () => {
let expected = 1;
let obj = clone(expected);
expect(obj).toBe(expected);
expected = 1.23;
obj = clone(expected);
expect(obj).toBe(expected);
});
it("should clone string", () => {
const expected = "Perfect stranger";
const obj = clone(expected);
expect(obj).toBe(expected);
});
it("should clone regex", () => {
const expected = /.*Magic/;
const obj = clone(expected);
expect(obj).toEqual(expected);
expect(expected === obj).toBe(false);
});
it("should clone date", () => {
const expected = new Date("2026-05-11T20:00:00.000Z");
const obj = clone(expected);
expect(obj).toEqual(expected);
expect(expected === obj).toBe(false);
});
it("should return URL by reference", () => {
const expected = new URL("https://magicmirror.builders/path?q=1");
const obj = clone(expected);
expect(obj).toBe(expected);
});
it("should return map by reference", () => {
const mapValue = { nested: [1, 2, 3] };
const expected = new Map([["module", mapValue]]);
const obj = clone(expected);
expect(obj).toBe(expected);
});
it("should return set by reference", () => {
const setValue = { nested: true };
const expected = new Set([setValue]);
const obj = clone(expected);
expect(obj).toBe(expected);
});
it("should return class instances by reference", () => {
class ModuleDefaults {
constructor () {
this.enabled = true;
}
}
const expected = new ModuleDefaults();
const obj = clone(expected);
expect(obj).toBe(expected);
});
it("should clone undefined", () => {
const expected = undefined;
const obj = clone(expected);
expect(obj).toBe(expected);
});
it("should clone null", () => {
const expected = null;
const obj = clone(expected);
expect(obj).toBe(expected);
});
it("should clone nested object", () => {
const expected = {
name: "fewieden",
link: "https://github.com/fewieden",
versions: ["2.0", "2.1", "2.2"],
answerForAllQuestions: 42,
properties: {
items: [{ foo: "bar" }, { lorem: "ipsum" }],
invalid: undefined,
nothing: null
}
};
const obj = clone(expected);
expect(obj).toEqual(expected);
expect(expected === obj).toBe(false);
expect(expected.versions === obj.versions).toBe(false);
expect(expected.properties === obj.properties).toBe(false);
expect(expected.properties.items === obj.properties.items).toBe(false);
expect(expected.properties.items[0] === obj.properties.items[0]).toBe(false);
expect(expected.properties.items[1] === obj.properties.items[1]).toBe(false);
});
describe("Test Module.create", () => {
let info;
beforeEach(() => {
info = global.Log.info;
});
afterEach(() => {
global.Log.info = info;
Module.definitions = {};
});
it("should create module instance with dynamic class name", () => {
const moduleName = "MMM-TestModule";
Module.register(moduleName, {
defaults: {}
});
const moduleInstance = Module.create(moduleName);
expect(moduleInstance.constructor.name).toBe(moduleName);
});
it("should use fallback class name for empty module name", () => {
const moduleName = "";
Module.register(moduleName, {
defaults: {}
});
const moduleInstance = Module.create(moduleName);
expect(moduleInstance.constructor.name).toBe("AnonymousModule");
});
it("should not throw when init is not a function", () => {
const moduleName = "MMM-TestModuleNoInitFunction";
Module.register(moduleName, {
init: null,
defaults: {}
});
expect(() => Module.create(moduleName)).not.toThrow();
});
it("should support lifecycle super call pattern", () => {
const moduleName = "MMM-TestSuperCall";
let loggedMessage;
global.Log.info = (message) => {
loggedMessage = message;
};
Module.register(moduleName, {
defaults: {},
start () {
this.didStart = true;
Module.prototype.start.call(this);
}
});
const moduleInstance = Module.create(moduleName);
moduleInstance.name = moduleName;
moduleInstance.start();
expect(moduleInstance.didStart).toBe(true);
expect(loggedMessage).toBe(`Starting module: ${moduleName}`);
});
it("should set config when defaults are undefined", () => {
const moduleName = "MMM-TestNoDefaults";
Module.register(moduleName, {});
const moduleInstance = Module.create(moduleName);
moduleInstance.setConfig({ foo: "bar" }, false);
expect(moduleInstance.config).toEqual({ foo: "bar" });
moduleInstance.setConfig({ nested: { value: 1 } }, true);
expect(moduleInstance.config).toEqual({ nested: { value: 1 } });
});
it("should initialize lifecycle fields in setData", () => {
const moduleName = "MMM-TestSetData";
Module.register(moduleName, {
defaults: { fromDefaults: true }
});
const moduleInstance = Module.create(moduleName);
moduleInstance.setData({
name: moduleName,
identifier: "module_1",
config: { fromConfig: true },
configDeepMerge: false
});
expect(moduleInstance.name).toBe(moduleName);
expect(moduleInstance.identifier).toBe("module_1");
expect(moduleInstance.hidden).toBe(false);
expect(moduleInstance.hasAnimateIn).toBe(false);
expect(moduleInstance.hasAnimateOut).toBe(false);
expect(moduleInstance.config).toEqual({ fromDefaults: true, fromConfig: true });
});
it("should not share defaults object across module instances", () => {
const moduleName = "MMM-TestDefaultsIsolation";
Module.register(moduleName, {
defaults: {
nested: { value: 1 },
list: [1]
}
});
const firstModuleInstance = Module.create(moduleName);
const secondModuleInstance = Module.create(moduleName);
firstModuleInstance.defaults.nested.value = 42;
firstModuleInstance.defaults.list.push(2);
expect(secondModuleInstance.defaults).toEqual({
nested: { value: 1 },
list: [1]
});
});
});
});
});
@@ -0,0 +1,7 @@
const SystemInformation = require("../../../js/systeminformation");
describe("SystemInformation", () => {
it("should output system information", async () => {
await expect(SystemInformation()).resolves.toContain("platform: linux");
});
});
+230
View File
@@ -0,0 +1,230 @@
const fs = require("node:fs");
const path = require("node:path");
const helmet = require("helmet");
const { JSDOM } = require("jsdom");
const express = require("express");
/**
* Helper function to create a fresh Translator instance with DOM environment.
* @returns {object} Object containing window and Translator
*/
function createTranslationTestEnvironment () {
const translatorJs = fs.readFileSync(path.join(__dirname, "..", "..", "..", "js", "translator.js"), "utf-8");
const dom = new JSDOM("", { url: "http://localhost:3001", runScripts: "outside-only" });
dom.window.Log = { log: vi.fn(), error: vi.fn() };
dom.window.fetch = fetch;
dom.window.eval(translatorJs);
return { window: dom.window, Translator: dom.window.Translator };
}
describe("Translator", () => {
let server;
const sockets = new Set();
const translationTestData = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "..", "tests", "mocks", "translation_test.json"), "utf8"));
beforeAll(() => {
const app = express();
app.use(helmet());
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
next();
});
app.use("/translations", express.static(path.join(__dirname, "..", "..", "..", "tests", "mocks")));
server = app.listen(3001);
server.on("connection", (socket) => {
sockets.add(socket);
});
});
afterAll(async () => {
for (const socket of sockets) {
socket.destroy();
sockets.delete(socket);
}
await server.close();
});
describe("translate", () => {
const translations = {
"MMM-Module": {
Hello: "Hallo",
"Hello {username}": "Hallo {username}"
}
};
const coreTranslations = {
Hello: "XXX",
"Hello {username}": "XXX",
FOO: "Foo",
"BAR {something}": "Bar {something}"
};
const translationsFallback = {
"MMM-Module": {
Hello: "XXX",
"Hello {username}": "XXX",
FOO: "XXX",
"BAR {something}": "XXX",
"A key": "A translation"
}
};
const coreTranslationsFallback = {
FOO: "XXX",
"BAR {something}": "XXX",
Hello: "XXX",
"Hello {username}": "XXX",
"A key": "XXX",
Fallback: "core fallback"
};
/**
* @param {object} Translator the global Translator object
*/
const setTranslations = (Translator) => {
Translator.translations = translations;
Translator.coreTranslations = coreTranslations;
Translator.translationsFallback = translationsFallback;
Translator.coreTranslationsFallback = coreTranslationsFallback;
};
it("should return custom module translation", () => {
const { Translator } = createTranslationTestEnvironment();
setTranslations(Translator);
let translation = Translator.translate({ name: "MMM-Module" }, "Hello");
expect(translation).toBe("Hallo");
translation = Translator.translate({ name: "MMM-Module" }, "Hello {username}", { username: "fewieden" });
expect(translation).toBe("Hallo fewieden");
});
it("should return core translation", () => {
const { Translator } = createTranslationTestEnvironment();
setTranslations(Translator);
let translation = Translator.translate({ name: "MMM-Module" }, "FOO");
expect(translation).toBe("Foo");
translation = Translator.translate({ name: "MMM-Module" }, "BAR {something}", { something: "Lorem Ipsum" });
expect(translation).toBe("Bar Lorem Ipsum");
});
it("should return custom module translation fallback", () => {
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", () => {
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", () => {
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", () => {
const { Translator } = createTranslationTestEnvironment();
setTranslations(Translator);
const translation = Translator.translate({ name: "MMM-Module" }, "MISSING");
expect(translation).toBe("MISSING");
});
});
describe("load", () => {
const mmm = {
name: "TranslationTest",
file (file) {
return `http://localhost:3001/translations/${file}`;
}
};
it("should load translations", async () => {
const { Translator } = createTranslationTestEnvironment();
const file = "translation_test.json";
await Translator.load(mmm, file, false);
const json = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "..", "tests", "mocks", file), "utf8"));
expect(Translator.translations[mmm.name]).toEqual(json);
});
it("should load translation fallbacks", async () => {
const { Translator } = createTranslationTestEnvironment();
const file = "translation_test.json";
await Translator.load(mmm, file, true);
const json = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "..", "..", "tests", "mocks", file), "utf8"));
expect(Translator.translationsFallback[mmm.name]).toEqual(json);
});
it("should not load translations, if module fallback exists", async () => {
const { Translator } = createTranslationTestEnvironment();
const file = "translation_test.json";
Translator.translationsFallback[mmm.name] = {
Hello: "Hallo"
};
await Translator.load(mmm, file, false);
expect(Translator.translations[mmm.name]).toBeUndefined();
expect(Translator.translationsFallback[mmm.name]).toEqual({
Hello: "Hallo"
});
});
});
describe("loadCoreTranslations", () => {
it("should load core translations and fallback", async () => {
const { window, Translator } = createTranslationTestEnvironment();
window.translations = { en: "http://localhost:3001/translations/translation_test.json" };
await Translator.loadCoreTranslations("en");
const en = translationTestData;
expect(Translator.coreTranslations).toEqual(en);
expect(Translator.coreTranslationsFallback).toEqual(en);
});
it("should load core fallback if language cannot be found", async () => {
const { window, Translator } = createTranslationTestEnvironment();
window.translations = { en: "http://localhost:3001/translations/translation_test.json" };
await Translator.loadCoreTranslations("MISSINGLANG");
const en = translationTestData;
expect(Translator.coreTranslations).toEqual({});
expect(Translator.coreTranslationsFallback).toEqual(en);
});
});
describe("loadCoreTranslationsFallback", () => {
it("should load core translations fallback", async () => {
const { window, Translator } = createTranslationTestEnvironment();
window.translations = { en: "http://localhost:3001/translations/translation_test.json" };
await Translator.loadCoreTranslationsFallback();
const en = translationTestData;
expect(Translator.coreTranslationsFallback).toEqual(en);
});
it("should load core fallback if language cannot be found", async () => {
const { window, Translator } = createTranslationTestEnvironment();
window.translations = {};
await Translator.loadCoreTranslations();
expect(Translator.coreTranslationsFallback).toEqual({});
});
});
});
+83
View File
@@ -0,0 +1,83 @@
const fs = require("node:fs");
const Log = require("../../../js/logger");
const { checkConfigFile, ConfigError } = require("../../../js/utils");
const createConfigObject = (modules) => ({
configFilename: "config.js",
configContentFull: "module.exports = { modules: [] };",
fullConf: { modules }
});
const runCheck = (modules) => {
checkConfigFile(createConfigObject(modules));
};
const expectExitForModules = (modules) => {
vi.spyOn(process, "exit").mockImplementation(() => {
throw new ConfigError("");
});
expect(() => {
runCheck(modules);
}).toThrow(ConfigError);
};
describe("utils", () => {
let originalReadFileSync;
beforeEach(() => {
originalReadFileSync = fs.readFileSync;
vi.spyOn(fs, "readFileSync").mockImplementation((fileName, ...args) => {
if (fileName === "index.html") {
return "<div class=\"region top_bar\"></div>\n<div class=\"region lower_third\"></div>";
}
return originalReadFileSync.call(fs, fileName, ...args);
});
vi.spyOn(fs, "writeFileSync").mockImplementation(() => {});
vi.spyOn(Log, "info").mockImplementation(() => {});
vi.spyOn(Log, "warn").mockImplementation(() => {});
vi.spyOn(Log, "error").mockImplementation(() => {});
});
afterEach(() => {
vi.restoreAllMocks();
});
it("accepts valid module entries", () => {
expect(() => {
runCheck([
{ module: "clock", position: "top_bar" },
{ module: "newsfeed" }
]);
}).not.toThrow();
expect(Log.error).not.toHaveBeenCalled();
});
it("exits when modules is not an array", () => {
expectExitForModules("not-an-array");
expect(Log.error).toHaveBeenCalledWith("This module configuration contains errors:\nmodules must be an array");
});
it("exits when module field is missing or not a string", () => {
expectExitForModules([{ module: 123, position: "top_bar" }]);
expect(Log.error).toHaveBeenCalled();
expect(Log.error.mock.calls[0][0]).toContain("module: must be a string");
});
it("warns for unknown positions without exiting", () => {
const exitSpy = vi.spyOn(process, "exit").mockImplementation(() => {
throw new ConfigError("");
});
expect(() => {
runCheck([{ module: "clock", position: "made_up_region" }]);
}).not.toThrow();
expect(exitSpy).not.toHaveBeenCalled();
expect(Log.warn).toHaveBeenCalled();
expect(Log.warn.mock.calls[0][0]).toContain("uses unknown position");
});
});