From 75a57829c2546c7dc30f9284721a78e01f72da4e Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Sat, 18 Sep 2021 03:50:53 +0200 Subject: [PATCH 1/9] fixed git helper async/await issues, template strings, clean up --- .../default/updatenotification/git_helper.js | 94 ++++++++++++------- 1 file changed, 58 insertions(+), 36 deletions(-) diff --git a/modules/default/updatenotification/git_helper.js b/modules/default/updatenotification/git_helper.js index b165e7bc..992a055b 100644 --- a/modules/default/updatenotification/git_helper.js +++ b/modules/default/updatenotification/git_helper.js @@ -4,10 +4,11 @@ const fs = require("fs"); const path = require("path"); const Log = require("logger"); -class gitHelper { +const BASE_DIR = path.normalize(`${__dirname}/../../../`); + +class GitHelper { constructor() { this.gitRepos = []; - this.baseDir = path.normalize(__dirname + "/../../../"); } getRefRegex(branch) { @@ -15,37 +16,41 @@ class gitHelper { } async execShell(command) { - let res = { stdout: "", stderr: "" }; - const { stdout, stderr } = await exec(command); + const { stdout = "", stderr = "" } = await exec(command); - res.stdout = stdout; - res.stderr = stderr; - return res; + return { stdout, stderr }; } async isGitRepo(moduleFolder) { - let res = await this.execShell("cd " + moduleFolder + " && git remote -v"); + const res = await this.execShell(`cd ${moduleFolder} && git remote -v`); + if (res.stderr) { - Log.error("Failed to fetch git data for " + moduleFolder + ": " + res.stderr); + Log.error(`Failed to fetch git data for ${moduleFolder}: ${res.stderr}`); + return false; - } else { - return true; } + + return true; } - add(moduleName) { - let moduleFolder = this.baseDir; + async add(moduleName) { + let moduleFolder = BASE_DIR; + if (moduleName !== "default") { - moduleFolder = moduleFolder + "modules/" + moduleName; + moduleFolder = `${moduleFolder}modules/${moduleName}`; } + try { - Log.info("Checking git for module: " + moduleName); + Log.info(`Checking git for module: ${moduleName}`); // Throws error if file doesn't exist fs.statSync(path.join(moduleFolder, ".git")); + // Fetch the git or throw error if no remotes - if (this.isGitRepo(moduleFolder)) { + const isGitRepo = await this.isGitRepo(moduleFolder); + + if (isGitRepo) { // Folder has .git and has at least one git remote, watch this folder - this.gitRepos.unshift({ module: moduleName, folder: moduleFolder }); + this.gitRepos.push({ module: moduleName, folder: moduleFolder }); } } catch (err) { // Error when directory .git doesn't exist or doesn't have any remotes @@ -56,36 +61,38 @@ class gitHelper { async getStatusInfo(repo) { let gitInfo = { module: repo.module, - // commits behind: - behind: 0, - // branch name: - current: "", - // current hash: - hash: "", - // remote branch: - tracking: "", + behind: 0, // commits behind + current: "", // branch name + hash: "", // current hash + tracking: "", // remote branch isBehindInStatus: false }; + let res; if (repo.module === "default") { // the hash is only needed for the mm repo - res = await this.execShell("cd " + repo.folder + " && git rev-parse HEAD"); + res = await this.execShell(`cd ${repo.folder} && git rev-parse HEAD`); + if (res.stderr) { - Log.error("Failed to get current commit hash for " + repo.module + ": " + res.stderr); + Log.error(`Failed to get current commit hash for ${repo.module}: ${res.stderr}`); } + gitInfo.hash = res.stdout; } + if (repo.res) { // mocking res = repo.res; } else { - res = await this.execShell("cd " + repo.folder + " && git status -sb"); + res = await this.execShell(`cd ${repo.folder} && git status -sb`); } + if (res.stderr) { - Log.error("Failed to get git status for " + repo.module + ": " + res.stderr); + Log.error(`Failed to get git status for ${repo.module}: ${res.stderr}`); // exit without git status info return; } + // only the first line of stdout is evaluated let status = res.stdout.split("\n")[0]; // examples for status: @@ -101,60 +108,73 @@ class gitHelper { // [ 'origin/develop' ] // [ 'origin/master', '[behind', '8]' ] gitInfo.tracking = status[0].trim(); + if (status[2]) { // git fetch was already called before so `git status -sb` delivers already the behind number gitInfo.behind = parseInt(status[2].substring(0, status[2].length - 1)); gitInfo.isBehindInStatus = true; } + return gitInfo; } async getRepoInfo(repo) { let gitInfo; + if (repo.gitInfo) { // mocking gitInfo = repo.gitInfo; } else { gitInfo = await this.getStatusInfo(repo); } + if (!gitInfo) { return; } + if (gitInfo.isBehindInStatus) { return gitInfo; } + let res; + if (repo.res) { // mocking res = repo.res; } else { - res = await this.execShell("cd " + repo.folder + " && git fetch --dry-run"); + res = await this.execShell(`cd ${repo.folder} && git fetch --dry-run`); } + // example output: // From https://github.com/MichMich/MagicMirror // e40ddd4..06389e3 develop -> origin/develop // here the result is in stderr (this is a git default, don't ask why ...) const matches = res.stderr.match(this.getRefRegex(gitInfo.current)); + if (!matches || !matches[0]) { // no refs found, nothing to do return; } + // get behind with refs try { - res = await this.execShell("cd " + repo.folder + " && git rev-list --ancestry-path --count " + matches[0]); + res = await this.execShell(`cd ${repo.folder} && git rev-list --ancestry-path --count ${matches[0]}`); gitInfo.behind = parseInt(res.stdout); + return gitInfo; } catch (err) { - Log.error("Failed to get git revisions for " + repo.module + ": " + err); + Log.error(`Failed to get git revisions for ${repo.module}: ${err}`); } } async getStatus() { const gitResultList = []; + for (let repo of this.gitRepos) { const gitInfo = await this.getStatusInfo(repo); + if (gitInfo) { - gitResultList.unshift(gitInfo); + gitResultList.push(gitInfo); } } @@ -163,10 +183,12 @@ class gitHelper { async getRepos() { const gitResultList = []; - for (let repo of this.gitRepos) { + + for (const repo of this.gitRepos) { const gitInfo = await this.getRepoInfo(repo); + if (gitInfo) { - gitResultList.unshift(gitInfo); + gitResultList.push(gitInfo); } } @@ -174,4 +196,4 @@ class gitHelper { } } -module.exports.gitHelper = gitHelper; +module.exports = GitHelper; From c2d2c278e0ed10716cdabad32d22eb9da737bddf Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Sat, 18 Sep 2021 03:53:40 +0200 Subject: [PATCH 2/9] fixed updatenotification async/await issues and more es6 --- .../default/updatenotification/node_helper.js | 56 +++++++++---------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/modules/default/updatenotification/node_helper.js b/modules/default/updatenotification/node_helper.js index c24e190a..a0e68305 100644 --- a/modules/default/updatenotification/node_helper.js +++ b/modules/default/updatenotification/node_helper.js @@ -1,70 +1,66 @@ -const GitHelper = require(__dirname + "/git_helper.js"); -const defaultModules = require(__dirname + "/../defaultmodules.js"); +const GitHelper = require("./git_helper"); +const defaultModules = require("../defaultmodules"); const NodeHelper = require("node_helper"); +const ONE_MINUTE = 60 * 1000; + module.exports = NodeHelper.create({ config: {}, updateTimer: null, updateProcessStarted: false, - gitHelper: new GitHelper.gitHelper(), + gitHelper: new GitHelper(), - start: function () {}, - - configureModules: async function (modules) { - // Push MagicMirror itself , biggest chance it'll show up last in UI and isn't overwritten - // others will be added in front - // this method returns promises so we can't wait for every one to resolve before continuing - this.gitHelper.add("default"); - - for (let moduleName in modules) { + async configureModules(modules) { + for (const moduleName of modules) { if (!this.ignoreUpdateChecking(moduleName)) { - this.gitHelper.add(moduleName); + await this.gitHelper.add(moduleName); } } + + await this.gitHelper.add("default"); }, - socketNotificationReceived: function (notification, payload) { + async socketNotificationReceived(notification, payload) { if (notification === "CONFIG") { this.config = payload; } else if (notification === "MODULES") { // if this is the 1st time thru the update check process if (!this.updateProcessStarted) { this.updateProcessStarted = true; - this.configureModules(payload).then(() => this.performFetch()); + await this.configureModules(payload); + await this.performFetch(); } } }, - performFetch: async function () { - for (let gitInfo of await this.gitHelper.getRepos()) { - this.sendSocketNotification("STATUS", gitInfo); + async performFetch() { + const repos = await this.gitHelper.getRepos(); + + for (const repo of repos) { + this.sendSocketNotification("STATUS", repo); } this.scheduleNextFetch(this.config.updateInterval); }, - scheduleNextFetch: function (delay) { - if (delay < 60 * 1000) { - delay = 60 * 1000; - } - - let self = this; + scheduleNextFetch(delay) { clearTimeout(this.updateTimer); - this.updateTimer = setTimeout(function () { - self.performFetch(); - }, delay); + + this.updateTimer = setTimeout(() => { + this.performFetch(); + }, Math.max(delay, ONE_MINUTE)); }, - ignoreUpdateChecking: function (moduleName) { + ignoreUpdateChecking(moduleName) { // Should not check for updates for default modules - if (defaultModules.indexOf(moduleName) >= 0) { + if (defaultModules.includes(moduleName)) { return true; } // Should not check for updates for ignored modules - if (this.config.ignoreModules.indexOf(moduleName) >= 0) { + if (this.config.ignoreModules.includes(moduleName)) { return true; } From 1dfa5d383c541885cfd21a00a4eb8e53424ed464 Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Sat, 18 Sep 2021 03:55:32 +0200 Subject: [PATCH 3/9] nunjuck template for updatenotification --- .../updatenotification/updatenotification.css | 3 +++ .../updatenotification/updatenotification.njk | 15 +++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 modules/default/updatenotification/updatenotification.css create mode 100644 modules/default/updatenotification/updatenotification.njk diff --git a/modules/default/updatenotification/updatenotification.css b/modules/default/updatenotification/updatenotification.css new file mode 100644 index 00000000..deed9e64 --- /dev/null +++ b/modules/default/updatenotification/updatenotification.css @@ -0,0 +1,3 @@ +.module.updatenotification a.difflink { + text-decoration: none; +} diff --git a/modules/default/updatenotification/updatenotification.njk b/modules/default/updatenotification/updatenotification.njk new file mode 100644 index 00000000..aa4a5250 --- /dev/null +++ b/modules/default/updatenotification/updatenotification.njk @@ -0,0 +1,15 @@ +{% if not suspended %} + {% for name, status in moduleList %} +
+ + + {% set mainTextLabel = "UPDATE_NOTIFICATION" if name === "default" else "UPDATE_NOTIFICATION_MODULE" %} + {{ mainTextLabel | translate({MODULE_NAME: name}) }} + +
+
+ {% set subTextLabel = "UPDATE_INFO_SINGLE" if status.behind === 1 else "UPDATE_INFO_MULTIPLE" %} + {{ subTextLabel | translate({COMMIT_COUNT: status.behind, BRANCH_NAME: status.current}) | diffLink(status) | safe }} +
+ {% endfor %} +{% endif %} From 7be25c21ed7e61facaa7aacc7f5bb74d5f9408a6 Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Sat, 18 Sep 2021 03:57:45 +0200 Subject: [PATCH 4/9] use nunjuck template, es6, removed unused config option --- .../updatenotification/updatenotification.js | 105 ++++++------------ 1 file changed, 37 insertions(+), 68 deletions(-) diff --git a/modules/default/updatenotification/updatenotification.js b/modules/default/updatenotification/updatenotification.js index 7d416852..063958a2 100644 --- a/modules/default/updatenotification/updatenotification.js +++ b/modules/default/updatenotification/updatenotification.js @@ -5,42 +5,59 @@ * MIT Licensed. */ Module.register("updatenotification", { - // Define module defaults defaults: { updateInterval: 10 * 60 * 1000, // every 10 minutes refreshInterval: 24 * 60 * 60 * 1000, // one day - ignoreModules: [], - timeout: 5000 + ignoreModules: [] }, suspended: false, moduleList: {}, - // Override start method. - start: function () { - Log.info("Starting module: " + this.name); + start() { + Log.info(`Starting module: ${this.name}`); + this.addFilters(); setInterval(() => { this.moduleList = {}; this.updateDom(2); }, this.config.refreshInterval); }, - notificationReceived: function (notification, payload, sender) { + suspend: function () { + this.suspended = true; + }, + + resume: function () { + this.suspended = false; + this.updateDom(2); + }, + + notificationReceived(notification) { if (notification === "DOM_OBJECTS_CREATED") { this.sendSocketNotification("CONFIG", this.config); - this.sendSocketNotification("MODULES", Module.definitions); - //this.hide(0, { lockString: this.identifier }); + this.sendSocketNotification("MODULES", Object.keys(Module.definitions)); } }, - // Override socket notification handler. - socketNotificationReceived: function (notification, payload) { + socketNotificationReceived(notification, payload) { if (notification === "STATUS") { this.updateUI(payload); } }, - updateUI: function (payload) { + getStyles() { + return [`${this.name}.css`]; + }, + + getTemplate() { + return `${this.name}.njk`; + }, + + getTemplateData() { + return { moduleList: this.moduleList, suspended: this.suspended }; + }, + + updateUI(payload) { if (payload && payload.behind > 0) { // if we haven't seen info for this module if (this.moduleList[payload.module] === undefined) { @@ -48,7 +65,6 @@ Module.register("updatenotification", { this.moduleList[payload.module] = payload; this.updateDom(2); } - //self.show(1000, { lockString: self.identifier }); } else if (payload && payload.behind === 0) { // if the module WAS in the list, but shouldn't be if (this.moduleList[payload.module] !== undefined) { @@ -59,62 +75,15 @@ Module.register("updatenotification", { } }, - diffLink: function (module, text) { - const localRef = module.hash; - const remoteRef = module.tracking.replace(/.*\//, ""); - return '' + text + ""; - }, - - // Override dom generator. - getDom: function () { - const wrapper = document.createElement("div"); - if (this.suspended === false) { - // process the hash of module info found - for (const key of Object.keys(this.moduleList)) { - let m = this.moduleList[key]; - - const message = document.createElement("div"); - message.className = "small bright"; - - const icon = document.createElement("i"); - icon.className = "fa fa-exclamation-circle"; - icon.innerHTML = " "; - message.appendChild(icon); - - const updateInfoKeyName = m.behind === 1 ? "UPDATE_INFO_SINGLE" : "UPDATE_INFO_MULTIPLE"; - - let subtextHtml = this.translate(updateInfoKeyName, { - COMMIT_COUNT: m.behind, - BRANCH_NAME: m.current - }); - - const text = document.createElement("span"); - if (m.module === "default") { - text.innerHTML = this.translate("UPDATE_NOTIFICATION"); - subtextHtml = this.diffLink(m, subtextHtml); - } else { - text.innerHTML = this.translate("UPDATE_NOTIFICATION_MODULE", { - MODULE_NAME: m.module - }); - } - message.appendChild(text); - - wrapper.appendChild(message); - - const subtext = document.createElement("div"); - subtext.innerHTML = subtextHtml; - subtext.className = "xsmall dimmed"; - wrapper.appendChild(subtext); + addFilters() { + this.nunjucksEnvironment().addFilter("diffLink", (text, status) => { + if (status.module !== "default") { + return text; } - } - return wrapper; - }, - suspend: function () { - this.suspended = true; - }, - resume: function () { - this.suspended = false; - this.updateDom(2); + const localRef = status.hash; + const remoteRef = status.tracking.replace(/.*\//, ""); + return `${text}`; + }); } }); From 332e429a41f1a2339afd4f0ae96dd125da6beada Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Sat, 18 Sep 2021 04:02:07 +0200 Subject: [PATCH 5/9] updated CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea963274..3eb00227 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ _This release is scheduled to be released on 2021-10-01._ - Refactored methods from weatherproviders into weatherobject (isDaytime, updateSunTime). - Use of `logger.js` in jest tests. - Run prettier over all relevant files. +- Cleaned up updatenotification module and switched to nunjuck rendering. ### Fixed From b094707324ea7c555e79465c6d5a020c159eafa6 Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Sat, 2 Oct 2021 14:08:16 +0200 Subject: [PATCH 6/9] remove mocking from implementation and use jest to mock git cli instead --- .../default/updatenotification/git_helper.js | 78 ++---- .../updatenotification_spec.js.snap | 45 ++++ .../unit/functions/updatenotification_spec.js | 231 +++++++++--------- 3 files changed, 191 insertions(+), 163 deletions(-) create mode 100644 tests/unit/functions/__snapshots__/updatenotification_spec.js.snap diff --git a/modules/default/updatenotification/git_helper.js b/modules/default/updatenotification/git_helper.js index 992a055b..b42b7d5c 100644 --- a/modules/default/updatenotification/git_helper.js +++ b/modules/default/updatenotification/git_helper.js @@ -12,7 +12,7 @@ class GitHelper { } getRefRegex(branch) { - return new RegExp("s*([a-z,0-9]+[.][.][a-z,0-9]+) " + branch, "g"); + return new RegExp(`s*([a-z,0-9]+[.][.][a-z,0-9]+) ${branch}`, "g"); } async execShell(command) { @@ -22,10 +22,10 @@ class GitHelper { } async isGitRepo(moduleFolder) { - const res = await this.execShell(`cd ${moduleFolder} && git remote -v`); + const { stderr } = await this.execShell(`cd ${moduleFolder} && git remote -v`); - if (res.stderr) { - Log.error(`Failed to fetch git data for ${moduleFolder}: ${res.stderr}`); + if (stderr) { + Log.error(`Failed to fetch git data for ${moduleFolder}: ${stderr}`); return false; } @@ -68,33 +68,27 @@ class GitHelper { isBehindInStatus: false }; - let res; if (repo.module === "default") { // the hash is only needed for the mm repo - res = await this.execShell(`cd ${repo.folder} && git rev-parse HEAD`); + const { stderr, stdout } = await this.execShell(`cd ${repo.folder} && git rev-parse HEAD`); - if (res.stderr) { - Log.error(`Failed to get current commit hash for ${repo.module}: ${res.stderr}`); + if (stderr) { + Log.error(`Failed to get current commit hash for ${repo.module}: ${stderr}`); } - gitInfo.hash = res.stdout; + gitInfo.hash = stdout; } - if (repo.res) { - // mocking - res = repo.res; - } else { - res = await this.execShell(`cd ${repo.folder} && git status -sb`); - } + const { stderr, stdout } = await this.execShell(`cd ${repo.folder} && git status -sb`); - if (res.stderr) { - Log.error(`Failed to get git status for ${repo.module}: ${res.stderr}`); + if (stderr) { + Log.error(`Failed to get git status for ${repo.module}: ${stderr}`); // exit without git status info return; } // only the first line of stdout is evaluated - let status = res.stdout.split("\n")[0]; + let status = stdout.split("\n")[0]; // examples for status: // ## develop...origin/develop // ## master...origin/master [behind 8] @@ -119,14 +113,7 @@ class GitHelper { } async getRepoInfo(repo) { - let gitInfo; - - if (repo.gitInfo) { - // mocking - gitInfo = repo.gitInfo; - } else { - gitInfo = await this.getStatusInfo(repo); - } + const gitInfo = await this.getStatusInfo(repo); if (!gitInfo) { return; @@ -136,20 +123,13 @@ class GitHelper { return gitInfo; } - let res; - - if (repo.res) { - // mocking - res = repo.res; - } else { - res = await this.execShell(`cd ${repo.folder} && git fetch --dry-run`); - } + const { stderr } = await this.execShell(`cd ${repo.folder} && git fetch --dry-run`); // example output: // From https://github.com/MichMich/MagicMirror // e40ddd4..06389e3 develop -> origin/develop // here the result is in stderr (this is a git default, don't ask why ...) - const matches = res.stderr.match(this.getRefRegex(gitInfo.current)); + const matches = stderr.match(this.getRefRegex(gitInfo.current)); if (!matches || !matches[0]) { // no refs found, nothing to do @@ -158,8 +138,8 @@ class GitHelper { // get behind with refs try { - res = await this.execShell(`cd ${repo.folder} && git rev-list --ancestry-path --count ${matches[0]}`); - gitInfo.behind = parseInt(res.stdout); + const { stdout } = await this.execShell(`cd ${repo.folder} && git rev-list --ancestry-path --count ${matches[0]}`); + gitInfo.behind = parseInt(stdout); return gitInfo; } catch (err) { @@ -167,28 +147,18 @@ class GitHelper { } } - async getStatus() { - const gitResultList = []; - - for (let repo of this.gitRepos) { - const gitInfo = await this.getStatusInfo(repo); - - if (gitInfo) { - gitResultList.push(gitInfo); - } - } - - return gitResultList; - } - async getRepos() { const gitResultList = []; for (const repo of this.gitRepos) { - const gitInfo = await this.getRepoInfo(repo); + try { + const gitInfo = await this.getRepoInfo(repo); - if (gitInfo) { - gitResultList.push(gitInfo); + if (gitInfo) { + gitResultList.push(gitInfo); + } + } catch (e) { + Log.error(`Failed to retrieve repo info for ${repo.module}: ${e}`); } } diff --git a/tests/unit/functions/__snapshots__/updatenotification_spec.js.snap b/tests/unit/functions/__snapshots__/updatenotification_spec.js.snap new file mode 100644 index 00000000..fa5a000d --- /dev/null +++ b/tests/unit/functions/__snapshots__/updatenotification_spec.js.snap @@ -0,0 +1,45 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Updatenotification custom module returns status information 1`] = ` +Object { + "behind": 7, + "current": "master", + "hash": "", + "isBehindInStatus": false, + "module": "MMM-Fuel", + "tracking": "origin/master", +} +`; + +exports[`Updatenotification custom module returns status information without hash 1`] = ` +Object { + "behind": 7, + "current": "master", + "hash": "", + "isBehindInStatus": false, + "module": "MMM-Fuel", + "tracking": "origin/master", +} +`; + +exports[`Updatenotification default returns status information 1`] = ` +Object { + "behind": 5, + "current": "develop", + "hash": "332e429a41f1a2339afd4f0ae96dd125da6beada", + "isBehindInStatus": false, + "module": "default", + "tracking": "origin/develop", +} +`; + +exports[`Updatenotification default returns status information early if isBehindInStatus 1`] = ` +Object { + "behind": 5, + "current": "develop", + "hash": "332e429a41f1a2339afd4f0ae96dd125da6beada", + "isBehindInStatus": true, + "module": "default", + "tracking": "origin/develop", +} +`; diff --git a/tests/unit/functions/updatenotification_spec.js b/tests/unit/functions/updatenotification_spec.js index f6214c1a..f5c5f396 100644 --- a/tests/unit/functions/updatenotification_spec.js +++ b/tests/unit/functions/updatenotification_spec.js @@ -1,126 +1,139 @@ -const path = require("path"); -const git_Helper = require("../../../modules/default/updatenotification/git_helper.js"); -const gitHelper = new git_Helper.gitHelper(); -gitHelper.add("default"); +jest.mock("util", () => ({ + ...jest.requireActual("util"), + promisify: jest.fn() +})); -const test1 = { - module: "test1", - folder: "", - res: { - stdout: "## master...origin/master [behind 8]", - stderr: "" - }, - gitInfo: { - module: "default", - // commits behind: - behind: 0, - // branch name: - current: "develop", - // current hash: - hash: "", - // remote branch: - tracking: "", - isBehindInStatus: false - } -}; +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), + statSync: jest.fn() +})); -const test2 = { - module: "test2", - folder: "", - res: { - stdout: "## develop...origin/develop", - stderr: "" - } -}; - -const test3 = { - module: "test3", - folder: "", - res: { - stdout: "", - stderr: "error" - }, - gitInfo: { - module: "default", - // commits behind: - behind: 2, - // branch name: - current: "develop", - // current hash: - hash: "", - // remote branch: - tracking: "", - isBehindInStatus: true - } -}; - -const test4 = { - module: "default", - folder: path.join(__dirname, "../../.."), - res: { - stdout: "", - stderr: " e40ddd4..06389e3 develop -> origin/develop" - }, - gitInfo: { - module: "default", - // commits behind: - behind: 0, - // branch name: - current: "develop", - // current hash: - hash: "", - // remote branch: - tracking: "", - isBehindInStatus: false - } -}; +jest.mock("logger", () => ({ + ...jest.requireActual("logger"), + error: jest.fn(), + info: jest.fn() +})); describe("Updatenotification", function () { - it("should return valid output for git status", async function () { - const arr = await gitHelper.getStatus(); - expect(arr.length).toBe(1); - const gitInfo = arr[0]; - expect(gitInfo.current).not.toBe(""); - expect(gitInfo.hash).not.toBe(""); - }, 15000); + const execMock = jest.fn(); - it("should return behind=8 for test1", async function () { - const gitInfo = await gitHelper.getStatusInfo(test1); - expect(gitInfo.behind).toBe(8); - expect(gitInfo.isBehindInStatus).toBe(true); + let gitHelper; + + let gitRemoteOut; + let gitRevParseOut; + let gitStatusOut; + let gitFetchOut; + let gitRevListOut; + let gitRemoteErr; + let gitRevParseErr; + let gitStatusErr; + let gitFetchErr; + let gitRevListErr; + + beforeAll(async function () { + const { promisify } = require("util"); + promisify.mockReturnValue(execMock); + + const GitHelper = require(`../../../modules/default/updatenotification/git_helper`); + gitHelper = new GitHelper(); }); - it("should return behind=0 for test2", async function () { - const gitInfo = await gitHelper.getStatusInfo(test2); - expect(gitInfo.behind).toBe(0); - expect(gitInfo.isBehindInStatus).toBe(false); + beforeEach(function () { + gitRemoteOut = ""; + gitRevParseOut = ""; + gitStatusOut = ""; + gitFetchOut = ""; + gitRevListOut = ""; + gitRemoteErr = ""; + gitRevParseErr = ""; + gitStatusErr = ""; + gitFetchErr = ""; + gitRevListErr = ""; + + execMock.mockImplementation(function (command) { + if (command.includes("git remote -v")) { + return { stdout: gitRemoteOut, stderr: gitRemoteErr }; + } else if (command.includes("git rev-parse HEAD")) { + return { stdout: gitRevParseOut, stderr: gitRevParseErr }; + } else if (command.includes("git status -sb")) { + return { stdout: gitStatusOut, stderr: gitStatusErr }; + } else if (command.includes("git fetch --dry-run")) { + return { stdout: gitFetchOut, stderr: gitFetchErr }; + } else if (command.includes("git rev-list --ancestry-path --count")) { + return { stdout: gitRevListOut, stderr: gitRevListErr }; + } + }); }); - it("should return empty status object for test3", async function () { - const gitInfo = await gitHelper.getStatusInfo(test3); - expect(gitInfo).toBe(undefined); + afterEach(async function () { + gitHelper.gitRepos = []; + + jest.clearAllMocks(); }); - it("should return empty repo object for test2", async function () { - // no gitInfo provided in res, so returns undefined - const gitInfo = await gitHelper.getRepoInfo(test2); - expect(gitInfo).toBe(undefined); + describe("default", () => { + const moduleName = "default"; + + beforeEach(async function () { + gitRemoteOut = "origin\tgit@github.com:MichMich/MagicMirror.git (fetch)\norigin\tgit@github.com:MichMich/MagicMirror.git (push)\n"; + gitRevParseOut = "332e429a41f1a2339afd4f0ae96dd125da6beada"; + gitStatusOut = "## develop...origin/develop\n M tests/unit/functions/updatenotification_spec.js\n"; + gitFetchErr = "From github.com:MichMich/MagicMirror\n60e0377..332e429 develop -> origin/develop\n"; + gitRevListOut = "5"; + + await gitHelper.add(moduleName); + }); + + it("returns status information", async function () { + const repos = await gitHelper.getRepos(); + expect(repos[0]).toMatchSnapshot(); + expect(execMock).toHaveBeenCalledTimes(5); + }); + + it("returns status information early if isBehindInStatus", async function () { + gitStatusOut = "## develop...origin/develop [behind 5]"; + + const repos = await gitHelper.getRepos(); + expect(repos[0]).toMatchSnapshot(); + expect(execMock).toHaveBeenCalledTimes(3); + }); + + it("excludes repo if status can't be retrieved", async function () { + const errorMessage = "Failed to retrieve status"; + execMock.mockRejectedValueOnce(errorMessage); + + const repos = await gitHelper.getRepos(); + expect(repos.length).toBe(0); + + const { error } = require("logger"); + expect(error).toHaveBeenCalledWith(`Failed to retrieve repo info for ${moduleName}: Failed to retrieve status`); + }); + + it("excludes repo if refs don't match regex", async function () { + gitFetchErr = ""; + + const repos = await gitHelper.getRepos(); + expect(repos.length).toBe(0); + }); }); - it("should return empty repo object for test1", async function () { - // no regex match for refs in empty string, so returns undefined - const gitInfo = await gitHelper.getRepoInfo(test1); - expect(gitInfo).toBe(undefined); - }); + describe("custom module", () => { + const moduleName = "MMM-Fuel"; - it("should return empty repo object for test4", async function () { - // git ref list throws error, so returns undefined - const gitInfo = await gitHelper.getRepoInfo(test4); - expect(gitInfo).toBe(undefined); - }); + beforeEach(async function () { + gitRemoteOut = `origin\thttps://github.com/fewieden/${moduleName}.git (fetch)\norigin\thttps://github.com/fewieden/${moduleName}.git (push)\n`; + gitRevParseOut = "9d8310163da94441073a93cead711ba43e8888d0"; + gitStatusOut = "## master...origin/master"; + gitFetchErr = `From https://github.com/fewieden/${moduleName}\n19f7faf..9d83101 master -> origin/master`; + gitRevListOut = "7"; - it("should return behind=2 for test3", async function () { - const gitInfo = await gitHelper.getRepoInfo(test3); - expect(gitInfo.behind).toBe(2); + await gitHelper.add(moduleName); + }); + + it("returns status information without hash", async function () { + const repos = await gitHelper.getRepos(); + expect(repos[0]).toMatchSnapshot(); + expect(execMock).toHaveBeenCalledTimes(4); + }); }); }); From 51967ed9f50ee67cbed35e53ed461056aae64545 Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Sat, 2 Oct 2021 14:18:58 +0200 Subject: [PATCH 7/9] shorthand function --- modules/default/updatenotification/updatenotification.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/default/updatenotification/updatenotification.js b/modules/default/updatenotification/updatenotification.js index 063958a2..8c3bb6af 100644 --- a/modules/default/updatenotification/updatenotification.js +++ b/modules/default/updatenotification/updatenotification.js @@ -23,11 +23,11 @@ Module.register("updatenotification", { }, this.config.refreshInterval); }, - suspend: function () { + suspend() { this.suspended = true; }, - resume: function () { + resume() { this.suspended = false; this.updateDom(2); }, From b0e3b6414ae2a1460315b4c975a9e9145b9d0e5e Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Sat, 2 Oct 2021 14:42:49 +0200 Subject: [PATCH 8/9] bump ecmascript to 2018 for eslint --- .eslintrc.json | 2 +- CHANGELOG.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 637b2eb7..751bf56d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -16,7 +16,7 @@ }, "parserOptions": { "sourceType": "module", - "ecmaVersion": 2017, + "ecmaVersion": 2018, "ecmaFeatures": { "globalReturn": true } diff --git a/CHANGELOG.md b/CHANGELOG.md index 31bcb06a..0124cb31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ _This release is scheduled to be released on 2022-01-01._ ### Updated +- ESLint version supports now ECMAScript 2018 - Cleaned up `updatenotification` module and switched to nunjuck template. ### Fixed From 1751cabb9de6315f5db8cfdcc8d48dd337c75ba2 Mon Sep 17 00:00:00 2001 From: Felix Wiedenbach Date: Sat, 2 Oct 2021 14:56:13 +0200 Subject: [PATCH 9/9] remove obsolete snapshot --- .../__snapshots__/updatenotification_spec.js.snap | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/unit/functions/__snapshots__/updatenotification_spec.js.snap b/tests/unit/functions/__snapshots__/updatenotification_spec.js.snap index fa5a000d..c5dd59a1 100644 --- a/tests/unit/functions/__snapshots__/updatenotification_spec.js.snap +++ b/tests/unit/functions/__snapshots__/updatenotification_spec.js.snap @@ -1,16 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Updatenotification custom module returns status information 1`] = ` -Object { - "behind": 7, - "current": "master", - "hash": "", - "isBehindInStatus": false, - "module": "MMM-Fuel", - "tracking": "origin/master", -} -`; - exports[`Updatenotification custom module returns status information without hash 1`] = ` Object { "behind": 7,