Release 2.22.0 (#2983)

## [2.22.0] - 2023-01-01

Thanks to: @angeldeejay, @buxxi, @dariom, @dWoolridge,
@KristjanESPERANTO, @MagMar94, @naveensrinivasan, @retroflex, @SkySails
and @Tom.

Special thanks to @khassel, @rejas and @sdetweil for taking over most
(if not all) of the work on this release as project collaborators. This
version would not be there without their effort. Thank you!

### Added

- Added test for remoteFile option in compliments module
- Added hourlyWeather functionality to Weather.gov weather provider
- Removed weatherEndpoint definition from weathergov.js (not used)
- Added css class names "today" and "tomorrow" for default calendar
- Added Collaboration.md
- Added new github action for dependency review (#2862)
- Added a WeatherProvider for Open-Meteo
- Added Yr as a weather provider
- Added config options "ignoreXOriginHeader" and
"ignoreContentSecurityPolicy"

### Removed

- Removed usage of internal fetch function of node until it is more
stable

### Updated

- Cleaned up test directory (#2937) and jest config (#2959)
- Wait for all modules to start before declaring the system ready
(#2487)
- Updated e2e tests (moved `done()` in helper functions) and use es6
syntax in all tests
- Updated da translation
- Rework weather module
- Make sure smhi provider api only gets a maximum of 6 digits
coordinates (#2955)
  - Use fetch instead of XMLHttpRequest in weatherprovider (#2935)
  - 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)
- The `cors`-method in `server.js` now supports sending and recieving
HTTP headers
- Replace `…` by `…`
- Cleanup compliments module
- Updated dependencies including electron to v22 (#2903)

### Fixed

- Correctly show apparent temperature in SMHI weather provider
- Ensure updatenotification module isn't shown when local is _ahead_ of
remote
- Handle node_helper errors during startup (#2944)
- Possibility to change FontAwesome class in calendar, so icons like
`fab fa-facebook-square` works.
- Fix cors problems with newsfeed articles (as far as possible), allow
disabling cors per feed with option `useCorsProxy: false` (#2840)
- Tests not waiting for the application to start and stop before
starting the next test
- Fix electron tests failing sometimes in github workflow
- Fixed gap in clock module when displayed on the left side with
displayType=digital
- Fixed playwright issue by upgrading to v1.29.1 (#2969)

Signed-off-by: naveen <172697+naveensrinivasan@users.noreply.github.com>
Co-authored-by: Karsten Hassel <hassel@gmx.de>
Co-authored-by: Malte Hallström <46646495+SkySails@users.noreply.github.com>
Co-authored-by: Veeck <github@veeck.de>
Co-authored-by: veeck <michael@veeck.de>
Co-authored-by: dWoolridge <dwoolridge@charter.net>
Co-authored-by: Johan <jojjepersson@yahoo.se>
Co-authored-by: Dario Mratovich <dario_mratovich@hotmail.com>
Co-authored-by: Dario Mratovich <dario.mratovich@outlook.com>
Co-authored-by: Magnus <34011212+MagMar94@users.noreply.github.com>
Co-authored-by: Naveen <172697+naveensrinivasan@users.noreply.github.com>
Co-authored-by: buxxi <buxxi@omfilm.net>
Co-authored-by: Thomas Hirschberger <47733292+Tom-Hirschberger@users.noreply.github.com>
Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com>
Co-authored-by: Andrés Vanegas Jiménez <142350+angeldeejay@users.noreply.github.com>
This commit is contained in:
Michael Teeuw
2023-01-01 18:09:08 +01:00
committed by GitHub
parent 9e0293047f
commit 0300ce05d5
151 changed files with 5890 additions and 4949 deletions

View File

@@ -0,0 +1,29 @@
const path = require("path");
const auth = require("express-basic-auth");
const express = require("express");
const app = express();
const basicAuth = auth({
realm: "MagicMirror² Area restricted.",
users: { MagicMirror: "CallMeADog" }
});
app.use(basicAuth);
// Set available directories
const directories = ["/tests/configs", "/tests/mocks"];
const rootPath = path.resolve(__dirname + "/../../../");
for (let directory of directories) {
app.use(directory, express.static(path.resolve(rootPath + directory)));
}
let server;
exports.listen = (...args) => {
server = app.listen.apply(app, args);
};
exports.close = async () => {
await server.close();
};

View File

@@ -0,0 +1,99 @@
const jsdom = require("jsdom");
const corefetch = require("fetch");
exports.startApplication = async (configFilename, exec) => {
jest.resetModules();
if (global.app) {
await this.stopApplication();
}
// Set config sample for use in test
if (configFilename === "") {
process.env.MM_CONFIG_FILE = "config/config.js";
} else {
process.env.MM_CONFIG_FILE = configFilename;
}
if (exec) exec;
global.app = require("app.js");
return new Promise((resolve) => {
global.app.start(resolve);
});
};
exports.stopApplication = async () => {
if (global.app) {
return new Promise((resolve) => {
global.app.stop(resolve);
delete global.app;
});
}
return Promise.resolve();
};
exports.getDocument = () => {
return new Promise((resolve) => {
const url = "http://" + (config.address || "localhost") + ":" + (config.port || "8080");
jsdom.JSDOM.fromURL(url, { resources: "usable", runScripts: "dangerously" }).then((dom) => {
dom.window.name = "jsdom";
dom.window.fetch = corefetch;
dom.window.onload = () => {
global.document = dom.window.document;
resolve();
};
});
});
};
exports.waitForElement = (selector, ignoreValue = "") => {
return new Promise((resolve) => {
let oldVal = "dummy12345";
const interval = setInterval(() => {
const element = document.querySelector(selector);
if (element) {
let newVal = element.textContent;
if (newVal === oldVal) {
clearInterval(interval);
resolve(element);
} else {
if (ignoreValue === "") {
oldVal = newVal;
} else {
if (!newVal.includes(ignoreValue)) oldVal = newVal;
}
}
}
}, 100);
});
};
exports.waitForAllElements = (selector) => {
return new Promise((resolve) => {
let oldVal = 999999;
const interval = setInterval(() => {
const element = document.querySelectorAll(selector);
if (element) {
let newVal = element.length;
if (newVal === oldVal) {
clearInterval(interval);
resolve(element);
} else {
if (newVal !== 0) oldVal = newVal;
}
}
}, 100);
});
};
exports.fetch = (url) => {
return new Promise((resolve) => {
corefetch(url).then((res) => {
resolve(res);
});
});
};
exports.testMatch = async (element, regex) => {
const elem = await this.waitForElement(element);
expect(elem).not.toBe(null);
expect(elem.textContent).toMatch(regex);
};

View File

@@ -0,0 +1,21 @@
/**
* Suppresses errors concerning web server already shut down.
*
* @param {string} err The error message.
*/
const mockError = (err) => {
if (err.includes("ECONNREFUSED") || err.includes("ECONNRESET") || err.includes("socket hang up") || err.includes("exports is not defined") || err.includes("write EPIPE")) {
jest.fn();
} else {
console.dir(err);
}
};
global.console = {
log: jest.fn(),
dir: console.dir,
error: mockError,
warn: console.warn,
info: jest.fn(),
debug: console.debug
};

View File

@@ -0,0 +1,29 @@
const helpers = require("./global-setup");
const path = require("path");
const fs = require("fs");
const { generateWeather, generateWeatherForecast } = require("../../mocks/weather_test");
exports.getText = async (element, result) => {
const elem = await helpers.waitForElement(element);
expect(elem).not.toBe(null);
expect(
elem.textContent
.trim()
.replace(/(\r\n|\n|\r)/gm, "")
.replace(/[ ]+/g, " ")
).toBe(result);
};
exports.startApp = async (configFile, additionalMockData) => {
let mockWeather;
if (configFile.includes("forecast")) {
mockWeather = generateWeatherForecast(additionalMockData);
} else {
mockWeather = generateWeather(additionalMockData);
}
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("");
await helpers.getDocument();
};