From a79f6548ee6c6123c846449d6126253777c38601 Mon Sep 17 00:00:00 2001 From: buxxi Date: Sun, 30 Oct 2022 16:51:14 +0100 Subject: [PATCH 1/7] Using async/await when starting the app --- js/app.js | 173 +++++++++++++++--------------- js/electron.js | 30 +++--- serveronly/index.js | 2 +- tests/e2e/helpers/global-setup.js | 11 +- 4 files changed, 109 insertions(+), 107 deletions(-) diff --git a/js/app.js b/js/app.js index ae2d76f4df..017dabc8e3 100644 --- a/js/app.js +++ b/js/app.js @@ -8,7 +8,7 @@ // Alias modules mentioned in package.js under _moduleAliases. require("module-alias/register"); -const fs = require("fs"); +const fs = require("fs/promises"); const path = require("path"); const Log = require("logger"); const Server = require(`${__dirname}/server`); @@ -54,9 +54,9 @@ function App() { * Loads the config file. Combines it with the defaults, and runs the * callback with the found config as argument. * - * @param {Function} callback Function to be called after loading the config + * @returns {Promise} A promise with the config that should be used */ - function loadConfig(callback) { + async function loadConfig() { Log.log("Loading config ..."); const defaults = require(`${__dirname}/defaults`); @@ -65,11 +65,11 @@ function App() { const configFilename = path.resolve(global.configuration_file || `${global.root_path}/config/config.js`); try { - fs.accessSync(configFilename, fs.F_OK); + await fs.access(configFilename, fs.F_OK); const c = require(configFilename); checkDeprecatedOptions(c); const config = Object.assign(defaults, c); - callback(config); + return config; } catch (e) { if (e.code === "ENOENT") { Log.error(Utils.colors.error("WARNING! Could not find config file. Please create one. Starting with default configuration.")); @@ -78,7 +78,7 @@ function App() { } else { Log.error(Utils.colors.error(`WARNING! Could not load config file. Starting with default configuration. Error found: ${e}`)); } - callback(defaults); + return defaults; } } @@ -102,9 +102,9 @@ function App() { * Loads a specific module. * * @param {string} module The name of the module (including subpath). - * @param {Function} callback Function to be called after loading + * @returns {Promise} A promise that resolves as soon as the module is loaded. */ - function loadModule(module, callback) { + async function loadModule(module) { const elements = module.split("/"); const moduleName = elements[elements.length - 1]; let moduleFolder = `${__dirname}/../modules/${module}`; @@ -113,17 +113,9 @@ function App() { moduleFolder = `${__dirname}/../modules/default/${module}`; } - const helperPath = `${moduleFolder}/node_helper.js`; + const helperPath = await resolveHelperPath(moduleFolder); - let loadHelper = true; - try { - fs.accessSync(helperPath, fs.R_OK); - } catch (e) { - loadHelper = false; - Log.log(`No helper found for module: ${moduleName}.`); - } - - if (loadHelper) { + if (helperPath) { const Module = require(helperPath); let m = new Module(); @@ -141,9 +133,12 @@ function App() { m.setPath(path.resolve(moduleFolder)); nodeHelpers.push(m); - m.loaded(callback); + return new Promise((resolve, reject) => { + m.loaded(resolve); + }); } else { - callback(); + Log.log(`No helper found for module: ${moduleName}.`); + return Promise.resolve(); } } @@ -151,29 +146,16 @@ function App() { * Loads all modules. * * @param {Module[]} modules All modules to be loaded - * @param {Function} callback Function to be called after loading + * @returns {Promise} A promise that is resolved when all modules been loaded */ - function loadModules(modules, callback) { + async function loadModules(modules) { Log.log("Loading module helpers ..."); - /** - * - */ - function loadNextModule() { - if (modules.length > 0) { - const nextModule = modules[0]; - loadModule(nextModule, function () { - modules = modules.slice(1); - loadNextModule(); - }); - } else { - // All modules are loaded - Log.log("All module helpers loaded."); - callback(); - } + for (let module of modules) { + await loadModule(module); } - loadNextModule(); + Log.log("All module helpers loaded."); } /** @@ -200,61 +182,74 @@ function App() { return segmentsA.length - segmentsB.length; } + /** + * Resolves the path to the node_helper + * + * @param {string} moduleFolder the folder that should contain the node_helper + * @returns {Promise} A promise with the path to the node_helper that should be used, or undefined if none exists + */ + async function resolveHelperPath(moduleFolder) { + const helperPath = `${moduleFolder}/node_helper.js`; + + try { + await fs.access(helperPath, fs.R_OK); + return helperPath; + } catch (e) { + // The current extension may not have been found, try the next instead + return undefined; + } + } + /** * Start the core app. * * It loads the config, then it loads all modules. When it's done it * executes the callback with the config as argument. * - * @param {Function} callback Function to be called after start + * @returns {Promise} A promise containing the config, it is resolved when the server has loaded all modules and are listening for requests */ - this.start = function (callback) { - loadConfig(function (c) { - config = c; + this.start = async function () { + config = await loadConfig(); - Log.setLogLevel(config.logLevel); + Log.setLogLevel(config.logLevel); - let modules = []; + let modules = []; - for (const module of config.modules) { - if (!modules.includes(module.module) && !module.disabled) { - modules.push(module.module); - } + for (const module of config.modules) { + if (!modules.includes(module.module) && !module.disabled) { + modules.push(module.module); } + } - loadModules(modules, async function () { - httpServer = new Server(config); - const { app, io } = await httpServer.open(); - Log.log("Server started ..."); - - const nodePromises = []; - for (let nodeHelper of nodeHelpers) { - nodeHelper.setExpressApp(app); - nodeHelper.setSocketIO(io); - - try { - nodePromises.push(nodeHelper.start()); - } catch (error) { - Log.error(`Error when starting node_helper for module ${nodeHelper.name}:`); - Log.error(error); - } - } + await loadModules(modules); - Promise.allSettled(nodePromises).then((results) => { - // Log errors that happened during async node_helper startup - results.forEach((result) => { - if (result.status === "rejected") { - Log.error(result.reason); - } - }); - - Log.log("Sockets connected & modules started ..."); - if (typeof callback === "function") { - callback(config); - } - }); - }); + httpServer = new Server(config); + const { app, io } = await httpServer.open(); + Log.log("Server started ..."); + + const nodePromises = []; + for (let nodeHelper of nodeHelpers) { + nodeHelper.setExpressApp(app); + nodeHelper.setSocketIO(io); + + try { + nodePromises.push(nodeHelper.start()); + } catch (error) { + Log.error(`Error when starting node_helper for module ${nodeHelper.name}:`); + Log.error(error); + } + } + + let results = await Promise.allSettled(nodePromises); + // Log errors that happened during async node_helper startup + results.forEach((result) => { + if (result.status === "rejected") { + Log.error(result.reason); + } }); + + Log.log("Sockets connected & modules started ..."); + return config; }; /** @@ -263,15 +258,21 @@ function App() { * * Added to fix #1056 * - * @param {Function} callback Function to be called after the app has stopped + * @returns {Promise} A promise that is resolved when all node_helpers and the http server has been closed */ - this.stop = function (callback) { + this.stop = async function () { for (const nodeHelper of nodeHelpers) { if (typeof nodeHelper.stop === "function") { nodeHelper.stop(); } } - httpServer.close().then(callback); + + // To be able to stop the app even if it hasn't been started (when running with Electron against another server) + if (!httpServer) { + return Promise.resolve(); + } + + return httpServer.close(); }; /** @@ -281,12 +282,12 @@ function App() { * Note: this is only used if running `server-only`. Otherwise * this.stop() is called by app.on("before-quit"... in `electron.js` */ - process.on("SIGINT", () => { + process.on("SIGINT", async () => { Log.log("[SIGINT] Received. Shutting down server..."); setTimeout(() => { process.exit(0); }, 3000); // Force quit after 3 seconds - this.stop(); + await this.stop(); process.exit(0); }); @@ -294,12 +295,12 @@ function App() { * Listen to SIGTERM signals so we can stop everything when we * are asked to stop by the OS. */ - process.on("SIGTERM", () => { + process.on("SIGTERM", async () => { Log.log("[SIGTERM] Received. Shutting down server..."); setTimeout(() => { process.exit(0); }, 3000); // Force quit after 3 seconds - this.stop(); + await this.stop(); process.exit(0); }); } diff --git a/js/electron.js b/js/electron.js index ffa48e6a74..8d5b7870af 100644 --- a/js/electron.js +++ b/js/electron.js @@ -5,7 +5,7 @@ const core = require("./app.js"); const Log = require("logger"); // Config -let config = process.env.config ? JSON.parse(process.env.config) : {}; +let config; // Module to control application life. const app = electron.app; // If ELECTRON_DISABLE_GPU is set electron is started with --disable-gpu flag. @@ -21,6 +21,19 @@ const BrowserWindow = electron.BrowserWindow; // be closed automatically when the JavaScript object is garbage collected. let mainWindow; +/** + * Start the core application if server is run on localhost + * This starts all node helpers and starts the webserver. + * + * @returns {Promise} A promise that is resolved when the server has started + */ +async function startAppIfNeeded() { + let localConfig = process.env.config ? JSON.parse(process.env.config) : {}; + if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].includes(localConfig.address)) { + config = await core.start(); + } +} + /** * */ @@ -107,7 +120,8 @@ function createWindow() { // This method will be called when Electron has finished // initialization and is ready to create browser windows. -app.on("ready", function () { +app.on("ready", async function () { + await startAppIfNeeded(); Log.log("Launching application."); createWindow(); }); @@ -136,13 +150,13 @@ app.on("activate", function () { * Note: this is only used if running Electron. Otherwise * core.stop() is called by process.on("SIGINT"... in `app.js` */ -app.on("before-quit", (event) => { +app.on("before-quit", async (event) => { Log.log("Shutting down server..."); event.preventDefault(); setTimeout(() => { process.exit(0); }, 3000); // Force-quit after 3 seconds. - core.stop(); + await core.stop(); process.exit(0); }); @@ -152,11 +166,3 @@ app.on("certificate-error", (event, webContents, url, error, certificate, callba event.preventDefault(); callback(true); }); - -// Start the core application if server is run on localhost -// This starts all node helpers and starts the webserver. -if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].includes(config.address)) { - core.start(function (c) { - config = c; - }); -} diff --git a/serveronly/index.js b/serveronly/index.js index 00d6b64be3..42cfd0496f 100644 --- a/serveronly/index.js +++ b/serveronly/index.js @@ -1,7 +1,7 @@ const app = require("../js/app.js"); const Log = require("logger"); -app.start((config) => { +app.start().then((config) => { const bindAddress = config.address ? config.address : "localhost"; const httpType = config.useHttps ? "https" : "http"; Log.log("\nReady to go! Please point your browser to: " + httpType + "://" + bindAddress + ":" + config.port); diff --git a/tests/e2e/helpers/global-setup.js b/tests/e2e/helpers/global-setup.js index ee8fb83ef2..495eb75c51 100644 --- a/tests/e2e/helpers/global-setup.js +++ b/tests/e2e/helpers/global-setup.js @@ -15,19 +15,14 @@ exports.startApplication = async (configFilename, exec) => { if (exec) exec; global.app = require("app.js"); - return new Promise((resolve) => { - global.app.start(resolve); - }); + await global.app.start(); }; exports.stopApplication = async () => { if (global.app) { - return new Promise((resolve) => { - global.app.stop(resolve); - delete global.app; - }); + await global.app.stop(); + delete global.app; } - return Promise.resolve(); }; exports.getDocument = () => { From 797e375caddd0a21e2554bc3ff553e6becd8fc5c Mon Sep 17 00:00:00 2001 From: buxxi Date: Sun, 30 Oct 2022 16:53:26 +0100 Subject: [PATCH 2/7] adding changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e606bfcdf..d07b194f44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Special thanks to: @rejas, @sdetweil - Reworked how weatherproviders handle units (#2849) - Use unix() method for parsing times, fix suntimes on the way (#2950) - Refactor conversion functions into utils class (#2958) +- Use async/await for startup of the application instead of callbacks ### Fixed From c84563a5101f7d7b5b360928e4dec3cbcf07a098 Mon Sep 17 00:00:00 2001 From: buxxi Date: Sun, 30 Oct 2022 17:06:42 +0100 Subject: [PATCH 3/7] moving timeout handling for closing of the app into the close method --- js/app.js | 26 ++++++++++++++++---------- js/electron.js | 5 +---- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/js/app.js b/js/app.js index 017dabc8e3..a7f9566a77 100644 --- a/js/app.js +++ b/js/app.js @@ -258,9 +258,10 @@ function App() { * * Added to fix #1056 * + * @param {number} timeout the amount of milliseconds before the returned promise should be automatically resolved * @returns {Promise} A promise that is resolved when all node_helpers and the http server has been closed */ - this.stop = async function () { + this.stop = async function (timeout) { for (const nodeHelper of nodeHelpers) { if (typeof nodeHelper.stop === "function") { nodeHelper.stop(); @@ -272,7 +273,18 @@ function App() { return Promise.resolve(); } - return httpServer.close(); + let serverClosePromise = httpServer.close(); + + // If a timeout is set, resolve when the server is closed or the timeout has been reached + if (timeout) { + let timeoutPromise = new Promise((resolve) => { + setTimeout(resolve, timeout); + }); + + return Promise.race([serverClosePromise, timeoutPromise]); + } else { + return serverClosePromise; + } }; /** @@ -284,10 +296,7 @@ function App() { */ process.on("SIGINT", async () => { Log.log("[SIGINT] Received. Shutting down server..."); - setTimeout(() => { - process.exit(0); - }, 3000); // Force quit after 3 seconds - await this.stop(); + await this.stop(3000); // Force quit after 3 seconds process.exit(0); }); @@ -297,10 +306,7 @@ function App() { */ process.on("SIGTERM", async () => { Log.log("[SIGTERM] Received. Shutting down server..."); - setTimeout(() => { - process.exit(0); - }, 3000); // Force quit after 3 seconds - await this.stop(); + await this.stop(3000); // Force quit after 3 seconds process.exit(0); }); } diff --git a/js/electron.js b/js/electron.js index 8d5b7870af..17b5d29867 100644 --- a/js/electron.js +++ b/js/electron.js @@ -153,10 +153,7 @@ app.on("activate", function () { app.on("before-quit", async (event) => { Log.log("Shutting down server..."); event.preventDefault(); - setTimeout(() => { - process.exit(0); - }, 3000); // Force-quit after 3 seconds. - await core.stop(); + await core.stop(3000); // Force-quit after 3 seconds. process.exit(0); }); From 74db8f4fcd6d73e48e7018531ba16eb69a3f9bc3 Mon Sep 17 00:00:00 2001 From: buxxi Date: Sun, 30 Oct 2022 21:11:09 +0100 Subject: [PATCH 4/7] trying iif removing the windows count make the tests behave better --- tests/electron/helpers/global-setup.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/tests/electron/helpers/global-setup.js b/tests/electron/helpers/global-setup.js index a8af02932c..a99edcee8a 100644 --- a/tests/electron/helpers/global-setup.js +++ b/tests/electron/helpers/global-setup.js @@ -12,18 +12,16 @@ exports.startApplication = async (configFilename, systemDate = null, electronPar global.electronApp = await electron.launch({ args: electronParams }); expect(global.electronApp); - if ((await global.electronApp.windows().length) === 1) { - global.page = await global.electronApp.firstWindow(); - if (systemDate) { - await global.page.evaluate((systemDate) => { - Date.now = () => { - return new Date(systemDate); - }; - }, systemDate); - } - expect(await global.page.title()).toBe("MagicMirror²"); - expect(await global.page.isVisible("body")).toBe(true); + global.page = await global.electronApp.firstWindow(); + if (systemDate) { + await global.page.evaluate((systemDate) => { + Date.now = () => { + return new Date(systemDate); + }; + }, systemDate); } + expect(await global.page.title()).toBe("MagicMirror²"); + expect(await global.page.isVisible("body")).toBe(true); }; exports.stopApplication = async () => { From 0991ee2218a0bcb6794db5bb9e267b31fbbb044a Mon Sep 17 00:00:00 2001 From: buxxi Date: Mon, 31 Oct 2022 06:27:33 +0100 Subject: [PATCH 5/7] refactoring electron tests regarding waiting for window creation --- tests/electron/env_spec.js | 10 +++++---- tests/electron/helpers/global-setup.js | 26 ++++++++++++---------- tests/electron/helpers/weather-setup.js | 3 ++- tests/electron/modules/calendar_spec.js | 6 +++-- tests/electron/modules/compliments_spec.js | 12 ++++++---- 5 files changed, 34 insertions(+), 23 deletions(-) diff --git a/tests/electron/env_spec.js b/tests/electron/env_spec.js index ce25268888..c703bc1c7f 100644 --- a/tests/electron/env_spec.js +++ b/tests/electron/env_spec.js @@ -18,7 +18,7 @@ describe("Electron app environment", () => { describe("Development console tests", () => { beforeEach(async () => { - await helpers.startApplication("tests/configs/modules/display.js", null, ["js/electron.js", "dev"]); + await helpers.startApplication("tests/configs/modules/display.js", ["js/electron.js", "dev"]); }); afterEach(async () => { @@ -26,9 +26,11 @@ describe("Development console tests", () => { }); it("should open browserwindow and dev console", async () => { - const pageArray = await global.electronApp.windows(); - expect(pageArray.length).toBe(2); - for (const page of pageArray) { + while (global.electronApp.windows().length < 2) { + await global.electronApp.waitForEvent("window"); + } + + for (let page of global.electronApp.windows()) { expect(["MagicMirror²", "DevTools"]).toContain(await page.title()); } }); diff --git a/tests/electron/helpers/global-setup.js b/tests/electron/helpers/global-setup.js index a99edcee8a..3c47f7235b 100644 --- a/tests/electron/helpers/global-setup.js +++ b/tests/electron/helpers/global-setup.js @@ -3,25 +3,19 @@ // https://www.anycodings.com/1questions/958135/can-i-set-the-date-for-playwright-browser const { _electron: electron } = require("playwright"); -exports.startApplication = async (configFilename, systemDate = null, electronParams = ["js/electron.js"]) => { +exports.startApplication = async (configFilename, electronParams = ["js/electron.js"]) => { global.electronApp = null; global.page = null; process.env.MM_CONFIG_FILE = configFilename; process.env.TZ = "GMT"; jest.retryTimes(3); global.electronApp = await electron.launch({ args: electronParams }); - expect(global.electronApp); + //We only need the first window for the majority of the tests global.page = await global.electronApp.firstWindow(); - if (systemDate) { - await global.page.evaluate((systemDate) => { - Date.now = () => { - return new Date(systemDate); - }; - }, systemDate); - } - expect(await global.page.title()).toBe("MagicMirror²"); - expect(await global.page.isVisible("body")).toBe(true); + + //Wait for the body element to be visible + await global.page.waitForSelector("body"); }; exports.stopApplication = async () => { @@ -33,9 +27,17 @@ exports.stopApplication = async () => { }; exports.getElement = async (selector) => { - expect(global.page); + expect(global.page).not.toBe(null); let elem = global.page.locator(selector); await elem.waitFor(); expect(elem).not.toBe(null); return elem; }; + +exports.mockSystemDate = async (systemDate) => { + await global.page.evaluate((systemDate) => { + Date.now = () => { + return new Date(systemDate); + }; + }, systemDate); +}; diff --git a/tests/electron/helpers/weather-setup.js b/tests/electron/helpers/weather-setup.js index 2c9b9ad1f5..1696a16d24 100644 --- a/tests/electron/helpers/weather-setup.js +++ b/tests/electron/helpers/weather-setup.js @@ -25,5 +25,6 @@ exports.startApp = async (configFile, systemDate) => { let content = fs.readFileSync(path.resolve(__dirname + "../../../../" + configFile)).toString(); content = content.replace("#####WEATHERDATA#####", mockWeather); fs.writeFileSync(path.resolve(__dirname + "../../../../config/config.js"), content); - await helpers.startApplication("", systemDate); + await helpers.startApplication(""); + await helpers.mockSystemDate(systemDate); }; diff --git a/tests/electron/modules/calendar_spec.js b/tests/electron/modules/calendar_spec.js index e2941d44d6..b6e5928d6a 100644 --- a/tests/electron/modules/calendar_spec.js +++ b/tests/electron/modules/calendar_spec.js @@ -20,12 +20,14 @@ describe("Calendar module", () => { describe("Test css classes", () => { it("has css class today", async () => { - await helpers.startApplication("tests/configs/modules/calendar/custom.js", "01 Jan 2030 12:30:00 GMT"); + await helpers.startApplication("tests/configs/modules/calendar/custom.js"); + await helpers.mockSystemDate("01 Jan 2030 12:30:00 GMT"); await doTest(".today"); }); it("has css class tomorrow", async () => { - await helpers.startApplication("tests/configs/modules/calendar/custom.js", "31 Dez 2029 12:30:00 GMT"); + await helpers.startApplication("tests/configs/modules/calendar/custom.js"); + await helpers.mockSystemDate("31 Dez 2029 12:30:00 GMT"); await doTest(".tomorrow"); }); }); diff --git a/tests/electron/modules/compliments_spec.js b/tests/electron/modules/compliments_spec.js index 3afa83de83..b16d2057e2 100644 --- a/tests/electron/modules/compliments_spec.js +++ b/tests/electron/modules/compliments_spec.js @@ -19,17 +19,20 @@ describe("Compliments module", () => { describe("parts of days", () => { it("Morning compliments for that part of day", async () => { - await helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js", "01 Oct 2022 10:00:00 GMT"); + await helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js"); + await helpers.mockSystemDate("01 Oct 2022 10:00:00 GMT"); await doTest(["Hi", "Good Morning", "Morning test"]); }); it("Afternoon show Compliments for that part of day", async () => { - await helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js", "01 Oct 2022 15:00:00 GMT"); + await helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js"); + await helpers.mockSystemDate("01 Oct 2022 15:00:00 GMT"); await doTest(["Hello", "Good Afternoon", "Afternoon test"]); }); it("Evening show Compliments for that part of day", async () => { - await helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js", "01 Oct 2022 20:00:00 GMT"); + await helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js"); + await helpers.mockSystemDate("01 Oct 2022 20:00:00 GMT"); await doTest(["Hello There", "Good Evening", "Evening test"]); }); }); @@ -37,7 +40,8 @@ describe("Compliments module", () => { describe("Feature date in compliments module", () => { describe("Set date and empty compliments for anytime, morning, evening and afternoon", () => { it("Show happy new year compliment on new years day", async () => { - await helpers.startApplication("tests/configs/modules/compliments/compliments_date.js", "01 Jan 2022 10:00:00 GMT"); + await helpers.startApplication("tests/configs/modules/compliments/compliments_date.js"); + await helpers.mockSystemDate("01 Jan 2022 10:00:00 GMT"); await doTest(["Happy new year!"]); }); }); From 3373ac8112931ad9c3a5a816fd0f0568befdb980 Mon Sep 17 00:00:00 2001 From: buxxi Date: Mon, 31 Oct 2022 08:23:49 +0100 Subject: [PATCH 6/7] Apply the mocking of the system date on all windows for electron tests --- tests/electron/helpers/global-setup.js | 42 ++++++++++++++++------ tests/electron/helpers/weather-setup.js | 2 +- tests/electron/modules/calendar_spec.js | 4 +-- tests/electron/modules/compliments_spec.js | 8 ++--- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/tests/electron/helpers/global-setup.js b/tests/electron/helpers/global-setup.js index 3c47f7235b..67e649b7c2 100644 --- a/tests/electron/helpers/global-setup.js +++ b/tests/electron/helpers/global-setup.js @@ -3,14 +3,29 @@ // https://www.anycodings.com/1questions/958135/can-i-set-the-date-for-playwright-browser const { _electron: electron } = require("playwright"); +function applyMocks(page) { + if (global.mocks) { + for (let mock of global.mocks) { + mock(page); + } + } +} + exports.startApplication = async (configFilename, electronParams = ["js/electron.js"]) => { - global.electronApp = null; - global.page = null; + await this.stopApplication(); process.env.MM_CONFIG_FILE = configFilename; process.env.TZ = "GMT"; jest.retryTimes(3); global.electronApp = await electron.launch({ args: electronParams }); + //Make sure new open windows gets mocked too + global.electronApp.on("window", applyMocks); + + //Apply mocks for all existing pages + for (let page of global.electronApp.windows()) { + applyMocks(page); + } + //We only need the first window for the majority of the tests global.page = await global.electronApp.firstWindow(); @@ -21,9 +36,10 @@ exports.startApplication = async (configFilename, electronParams = ["js/electron exports.stopApplication = async () => { if (global.electronApp) { await global.electronApp.close(); + global.electronApp = null; + global.page = null; + global.mocks = null; } - global.electronApp = null; - global.page = null; }; exports.getElement = async (selector) => { @@ -34,10 +50,16 @@ exports.getElement = async (selector) => { return elem; }; -exports.mockSystemDate = async (systemDate) => { - await global.page.evaluate((systemDate) => { - Date.now = () => { - return new Date(systemDate); - }; - }, systemDate); +exports.mockSystemDate = (mockedSystemDate) => { + if (!global.mocks) { + global.mocks = []; + } + + global.mocks.push(async (page) => { + await page.evaluate((systemDate) => { + Date.now = () => { + return new Date(systemDate); + }; + }, mockedSystemDate); + }); }; diff --git a/tests/electron/helpers/weather-setup.js b/tests/electron/helpers/weather-setup.js index 1696a16d24..9c274ca91f 100644 --- a/tests/electron/helpers/weather-setup.js +++ b/tests/electron/helpers/weather-setup.js @@ -25,6 +25,6 @@ exports.startApp = async (configFile, systemDate) => { let content = fs.readFileSync(path.resolve(__dirname + "../../../../" + configFile)).toString(); content = content.replace("#####WEATHERDATA#####", mockWeather); fs.writeFileSync(path.resolve(__dirname + "../../../../config/config.js"), content); + helpers.mockSystemDate(systemDate); await helpers.startApplication(""); - await helpers.mockSystemDate(systemDate); }; diff --git a/tests/electron/modules/calendar_spec.js b/tests/electron/modules/calendar_spec.js index b6e5928d6a..4c07f4d9db 100644 --- a/tests/electron/modules/calendar_spec.js +++ b/tests/electron/modules/calendar_spec.js @@ -20,14 +20,14 @@ describe("Calendar module", () => { describe("Test css classes", () => { it("has css class today", async () => { + helpers.mockSystemDate("01 Jan 2030 12:30:00 GMT"); await helpers.startApplication("tests/configs/modules/calendar/custom.js"); - await helpers.mockSystemDate("01 Jan 2030 12:30:00 GMT"); await doTest(".today"); }); it("has css class tomorrow", async () => { + helpers.mockSystemDate("31 Dez 2029 12:30:00 GMT"); await helpers.startApplication("tests/configs/modules/calendar/custom.js"); - await helpers.mockSystemDate("31 Dez 2029 12:30:00 GMT"); await doTest(".tomorrow"); }); }); diff --git a/tests/electron/modules/compliments_spec.js b/tests/electron/modules/compliments_spec.js index b16d2057e2..4a661c6d86 100644 --- a/tests/electron/modules/compliments_spec.js +++ b/tests/electron/modules/compliments_spec.js @@ -19,20 +19,20 @@ describe("Compliments module", () => { describe("parts of days", () => { it("Morning compliments for that part of day", async () => { + helpers.mockSystemDate("01 Oct 2022 10:00:00 GMT"); await helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js"); - await helpers.mockSystemDate("01 Oct 2022 10:00:00 GMT"); await doTest(["Hi", "Good Morning", "Morning test"]); }); it("Afternoon show Compliments for that part of day", async () => { + helpers.mockSystemDate("01 Oct 2022 15:00:00 GMT"); await helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js"); - await helpers.mockSystemDate("01 Oct 2022 15:00:00 GMT"); await doTest(["Hello", "Good Afternoon", "Afternoon test"]); }); it("Evening show Compliments for that part of day", async () => { + helpers.mockSystemDate("01 Oct 2022 20:00:00 GMT"); await helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js"); - await helpers.mockSystemDate("01 Oct 2022 20:00:00 GMT"); await doTest(["Hello There", "Good Evening", "Evening test"]); }); }); @@ -40,8 +40,8 @@ describe("Compliments module", () => { describe("Feature date in compliments module", () => { describe("Set date and empty compliments for anytime, morning, evening and afternoon", () => { it("Show happy new year compliment on new years day", async () => { + helpers.mockSystemDate("01 Jan 2022 10:00:00 GMT"); await helpers.startApplication("tests/configs/modules/compliments/compliments_date.js"); - await helpers.mockSystemDate("01 Jan 2022 10:00:00 GMT"); await doTest(["Happy new year!"]); }); }); From 37c6808e625940d915cfd46f78287442f74e69c5 Mon Sep 17 00:00:00 2001 From: buxxi Date: Mon, 31 Oct 2022 13:06:26 +0100 Subject: [PATCH 7/7] fixing calendar test not failing if changing date --- tests/electron/modules/calendar_spec.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/electron/modules/calendar_spec.js b/tests/electron/modules/calendar_spec.js index 4c07f4d9db..ef3f229e2a 100644 --- a/tests/electron/modules/calendar_spec.js +++ b/tests/electron/modules/calendar_spec.js @@ -7,11 +7,8 @@ describe("Calendar module", () => { * @param {string} cssClass css selector */ const doTest = async (cssClass) => { - await helpers.getElement(".calendar"); - await helpers.getElement(".module-content"); - const events = await global.page.locator(".event"); - const elem = await events.locator(cssClass); - expect(elem).not.toBe(null); + let elem = await helpers.getElement(".calendar .module-content .event" + cssClass); + expect(await elem.isVisible()).toBe(true); }; afterEach(async () => { @@ -26,7 +23,7 @@ describe("Calendar module", () => { }); it("has css class tomorrow", async () => { - helpers.mockSystemDate("31 Dez 2029 12:30:00 GMT"); + helpers.mockSystemDate("31 Dec 2029 12:30:00 GMT"); await helpers.startApplication("tests/configs/modules/calendar/custom.js"); await doTest(".tomorrow"); });