Merge pull request #2672 from MichMich/develop

Release 2.17.0
This commit is contained in:
Michael Teeuw
2021-10-01 15:17:49 +02:00
committed by GitHub
123 changed files with 3934 additions and 4261 deletions

View File

@@ -25,6 +25,7 @@
"prettier/prettier": "error", "prettier/prettier": "error",
"eqeqeq": "error", "eqeqeq": "error",
"no-prototype-builtins": "off", "no-prototype-builtins": "off",
"no-unused-vars": "off" "no-unused-vars": "off",
"no-useless-return": "error"
} }
} }

View File

@@ -18,7 +18,7 @@ To run ESLint, use `npm run lint:js`.
We use [StyleLint](https://stylelint.io) to lint our CSS. Our configuration is in our .stylelintrc file. We use [StyleLint](https://stylelint.io) to lint our CSS. Our configuration is in our .stylelintrc file.
To run StyleLint, use `npm run lint:style`. To run StyleLint, use `npm run lint:css`.
### Submitting Issues ### Submitting Issues
@@ -32,9 +32,9 @@ When submitting a new issue, please supply the following information:
**Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3/4, Windows, Mac, Linux, System V UNIX). **Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3/4, Windows, Mac, Linux, System V UNIX).
**Node Version**: Make sure it's version 10 or later. **Node Version**: Make sure it's version 12 or later (recommended is 14).
**MagicMirror Version**: Now that the versions have split, tell us if you are using the PHP version (v1) or the newer JavaScript version (v2). **MagicMirror Version**: Please let us know which version of MagicMirror you are running. It can be found in the `package.json` file.
**Description**: Provide a detailed description about the issue and include specific details to help us understand the problem. Adding screenshots will help describing the problem. **Description**: Provide a detailed description about the issue and include specific details to help us understand the problem. Adding screenshots will help describing the problem.

2
.github/FUNDING.yml vendored
View File

@@ -1,2 +1,2 @@
github: MichMich github: MichMich
custom: ['https://magicmirror.builders/#donate'] custom: ["https://magicmirror.builders/#donate"]

View File

@@ -19,8 +19,10 @@ If you are facing an issue or found a bug while trying to install MagicMirror vi
## I found a bug in the MagicMirror Docker image ## I found a bug in the MagicMirror Docker image
If you are facing an issue or found a bug while running MagicMirror inside a Docker container please create an issue in the GitHub repository of the MagicMirror Docker image: If you are facing an issue or found a bug while running MagicMirror inside a Docker container please create an issue in the corresponding repository:
[https://github.com/bastilimbach/docker-MagicMirror](https://github.com/bastilimbach/docker-MagicMirror)
- karsten13/magicmirror: [https://gitlab.com/khassel/magicmirror](https://gitlab.com/khassel/magicmirror)
- (deprecated) bastilimbach/docker-magicmirror: [https://github.com/bastilimbach/docker-MagicMirror](https://github.com/bastilimbach/docker-MagicMirror)
--- ---
@@ -31,9 +33,9 @@ When submitting a new issue, please supply the following information:
**Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3/4, Windows, Mac, Linux, System V UNIX). **Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3/4, Windows, Mac, Linux, System V UNIX).
**Node Version**: Make sure it's version 10 or later. **Node Version**: Make sure it's version 12 or later (recommended is 14).
**MagicMirror Version**: Please let us now which version of MagicMirror you are running. It can be found in the `package.log` file. **MagicMirror Version**: Please let us know which version of MagicMirror you are running. It can be found in the `package.json` file.
**Description**: Provide a detailed description about the issue and include specific details to help us understand the problem. Adding screenshots will help describing the problem. **Description**: Provide a detailed description about the issue and include specific details to help us understand the problem. Adding screenshots will help describing the problem.

View File

@@ -2,23 +2,20 @@ Hello and thank you for wanting to contribute to the MagicMirror project
**Please make sure that you have followed these 4 rules before submitting your Pull Request:** **Please make sure that you have followed these 4 rules before submitting your Pull Request:**
> 1) Base your pull requests against the `develop` branch. > 1. Base your pull requests against the `develop` branch.
> >
> 2. Include these infos in the description:
> >
> 2) Include these infos in the description: > - Does the pull request solve a **related** issue?
> * Does the pull request solve a **related** issue? > - If so, can you reference the issue like this `Fixes #<issue_number>`?
> * If so, can you reference the issue like this `Fixes #<issue_number>`? > - What does the pull request accomplish? Use a list if needed.
> * What does the pull request accomplish? Use a list if needed. > - If it includes major visual changes please add screenshots.
> * If it includes major visual changes please add screenshots.
> >
> 3. Please run `npm run lint:prettier` before submitting so that
> style issues are fixed.
> >
> 3) Please run `npm run lint:prettier` before submitting so that > 4. Don't forget to add an entry about your changes to
> style issues are fixed. > the CHANGELOG.md file.
>
>
> 4) Don't forget to add an entry about your changes to
> the CHANGELOG.md file.
**Note**: Sometimes the development moves very fast. It is highly **Note**: Sometimes the development moves very fast. It is highly
recommended that you update your branch of `develop` before creating a recommended that you update your branch of `develop` before creating a

BIN
.github/header.psd vendored

Binary file not shown.

37
.github/workflows/automated-tests.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: "Run Automated Tests"
on:
push:
branches: [master, develop]
pull_request:
branches: [master, develop]
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
node-version: [12.x, 14.x, 16.x]
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies and run tests
run: |
Xvfb :99 -screen 0 1024x768x16 &
export DISPLAY=:99
npm install
touch css/custom.css
npm run test:prettier
npm run test:js
npm run test:css
npm run test:unit
npm run test:e2e
npm run test:electron

View File

@@ -4,22 +4,26 @@ name: "Run Codecov Tests"
on: on:
push: push:
branches: [ master, develop ] branches: [master, develop]
pull_request: pull_request:
branches: [ master, develop ] branches: [master, develop]
jobs: jobs:
run-and-upload-coverage-report: run-and-upload-coverage-report:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 30 timeout-minutes: 30
steps: steps:
- uses: actions/checkout@v2 - name: Checkout code
- run: | uses: actions/checkout@v2
- name: Install dependencies and run coverage
run: |
Xvfb :99 -screen 0 1024x768x16 & Xvfb :99 -screen 0 1024x768x16 &
export DISPLAY=:99 export DISPLAY=:99
npm ci npm ci
touch css/custom.css
npm run test:coverage npm run test:coverage
- uses: codecov/codecov-action@v1 - name: Upload coverage results to codecov
uses: codecov/codecov-action@v1
with: with:
file: ./coverage/lcov.info file: ./coverage/lcov.info
fail_ci_if_error: true fail_ci_if_error: true

View File

@@ -4,15 +4,17 @@ name: "Enforce Changelog"
on: on:
pull_request: pull_request:
types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled]
jobs: jobs:
check: check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v2 - name: Checkout code
- uses: dangoslen/changelog-enforcer@v1.6.1 uses: actions/checkout@v2
with: - name: Enforce changelog
changeLogPath: 'CHANGELOG.md' uses: dangoslen/changelog-enforcer@v1.6.1
skipLabels: 'Skip Changelog' with:
changeLogPath: "CHANGELOG.md"
skipLabels: "Skip Changelog"

View File

@@ -1,33 +0,0 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: "Run Automated Tests"
on:
push:
branches: [ master, develop ]
pull_request:
branches: [ master, develop ]
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
node-version: [12.x, 14.x, 16.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: |
Xvfb :99 -screen 0 1024x768x16 &
export DISPLAY=:99
npm install
npm run test:prettier
npm run test:js
npm run test:css
npm run test:unit
npm run test:e2e

1
.husky/.gitignore vendored
View File

@@ -1 +0,0 @@
_

View File

@@ -1,8 +1,4 @@
/config /config
/coverage /coverage
/vendor
!/vendor/vendor.js
.github
.nyc_output .nyc_output
package-lock.json package-lock.json
*.ts

View File

@@ -5,6 +5,38 @@ This project adheres to [Semantic Versioning](https://semver.org/).
❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror² ❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror²
## [2.17.0] - 2021-10-01
Special thanks to the following contributors: @apiontek, @eouia, @jupadin, @khassel and @rejas.
### Added
- Added showTime parameter to clock module for enabling/disabling time display in analog clock.
- Added custom electron switches from user config (`config.electronSwitches`).
- Added unit tests for updatenotification module.
### Updated
- Bump electron to v13 (and spectron to v15) and update other dependencies in package.json.
- Refactor test configs, use default test config for all tests.
- Updated github templates.
- Actually test all js and css files when lint script is run.
- Update jsdocs and print warnings during testing too.
- Update weathergov provider to try fetching not just current, but also foreacst, when API URLs available.
- Refactored clock layout.
- Refactored methods from weatherproviders into weatherobject (isDaytime, updateSunTime).
- Use of `logger.js` in jest tests.
- Run prettier over all relevant files.
- Move tests needing electron in new category `electron`, use `server only` mode in `e2e` tests.
- Update dependencies in package.json.
### Fixed
- Fix undefined error with ignoreToday option in weather module (#2620).
- Fix time zone correction in calendar module when the date hour is equal to the time zone correction value (#2632).
- Fix black cursor on startup when using electron.
- Fix update notification not working for own repository (#2644).
## [2.16.0] - 2021-07-01 ## [2.16.0] - 2021-07-01
Special thanks to the following contributors: @210954, @B1gG, @codac, @Crazylegstoo, @daniel, @earlman, @ezeholz, @FrancoisRmn, @jupadin, @khassel, @KristjanESPERANTO, @njwilliams, @oemel09, @r3wald, @rejas, @rico24, Faizan Ahmed. Special thanks to the following contributors: @210954, @B1gG, @codac, @Crazylegstoo, @daniel, @earlman, @ezeholz, @FrancoisRmn, @jupadin, @khassel, @KristjanESPERANTO, @njwilliams, @oemel09, @r3wald, @rejas, @rico24, Faizan Ahmed.
@@ -412,6 +444,7 @@ Special thanks to @sdetweil for all his great contributions!
- Update `ical.js` to solve various calendar issues. - Update `ical.js` to solve various calendar issues.
- Update weather city list url [#1676](https://github.com/MichMich/MagicMirror/issues/1676) - Update weather city list url [#1676](https://github.com/MichMich/MagicMirror/issues/1676)
- Only update clock once per minute when seconds aren't shown - Only update clock once per minute when seconds aren't shown
- Update weatherprovider documentation.
### Fixed ### Fixed

View File

@@ -57,7 +57,8 @@ let config = {
calendars: [ calendars: [
{ {
symbol: "calendar-check", symbol: "calendar-check",
url: "webcal://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics" } url: "webcal://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics"
}
] ]
} }
}, },

View File

@@ -16,7 +16,7 @@
<!-- custom.css is loaded by the loader.js to make sure it's loaded after the module css files. --> <!-- custom.css is loaded by the loader.js to make sure it's loaded after the module css files. -->
<script type="text/javascript"> <script type="text/javascript">
var version = "#VERSION#"; window.mmVersion = "#VERSION#";
</script> </script>
</head> </head>
<body> <body>

View File

@@ -48,6 +48,7 @@ process.on("uncaughtException", function (err) {
*/ */
function App() { function App() {
let nodeHelpers = []; let nodeHelpers = [];
let httpServer;
/** /**
* Loads the config file. Combines it with the defaults, and runs the * Loads the config file. Combines it with the defaults, and runs the
@@ -222,7 +223,7 @@ function App() {
} }
loadModules(modules, function () { loadModules(modules, function () {
const server = new Server(config, function (app, io) { httpServer = new Server(config, function (app, io) {
Log.log("Server started ..."); Log.log("Server started ...");
for (let nodeHelper of nodeHelpers) { for (let nodeHelper of nodeHelpers) {
@@ -253,6 +254,7 @@ function App() {
nodeHelper.stop(); nodeHelper.stop();
} }
} }
httpServer.close();
}; };
/** /**

View File

@@ -8,8 +8,8 @@
* MIT Licensed. * MIT Licensed.
*/ */
(function () { (function () {
var initializing = false; let initializing = false;
var fnTest = /xyz/.test(function () { const fnTest = /xyz/.test(function () {
xyz; xyz;
}) })
? /\b_super\b/ ? /\b_super\b/
@@ -20,27 +20,27 @@
// Create a new Class that inherits from this class // Create a new Class that inherits from this class
Class.extend = function (prop) { Class.extend = function (prop) {
var _super = this.prototype; let _super = this.prototype;
// Instantiate a base class (but only create the instance, // Instantiate a base class (but only create the instance,
// don't run the init constructor) // don't run the init constructor)
initializing = true; initializing = true;
var prototype = new this(); const prototype = new this();
initializing = false; initializing = false;
// Make a copy of all prototype properties, to prevent reference issues. // Make a copy of all prototype properties, to prevent reference issues.
for (var p in prototype) { for (const p in prototype) {
prototype[p] = cloneObject(prototype[p]); prototype[p] = cloneObject(prototype[p]);
} }
// Copy the properties over onto the new prototype // Copy the properties over onto the new prototype
for (var name in prop) { for (const name in prop) {
// Check if we're overwriting an existing function // Check if we're overwriting an existing function
prototype[name] = prototype[name] =
typeof prop[name] === "function" && typeof _super[name] === "function" && fnTest.test(prop[name]) typeof prop[name] === "function" && typeof _super[name] === "function" && fnTest.test(prop[name])
? (function (name, fn) { ? (function (name, fn) {
return function () { return function () {
var tmp = this._super; const tmp = this._super;
// Add a new ._super() method that is the same method // Add a new ._super() method that is the same method
// but on the super-class // but on the super-class
@@ -48,7 +48,7 @@
// The method only need to be bound temporarily, so we // The method only need to be bound temporarily, so we
// remove it when we're done executing // remove it when we're done executing
var ret = fn.apply(this, arguments); const ret = fn.apply(this, arguments);
this._super = tmp; this._super = tmp;
return ret; return ret;
@@ -91,8 +91,8 @@ function cloneObject(obj) {
return obj; return obj;
} }
var temp = obj.constructor(); // give temp the original obj's constructor const temp = obj.constructor(); // give temp the original obj's constructor
for (var key in obj) { for (const key in obj) {
temp[key] = cloneObject(obj[key]); temp[key] = cloneObject(obj[key]);
if (key === "lockStrings") { if (key === "lockStrings") {

View File

@@ -19,7 +19,8 @@ let mainWindow;
* *
*/ */
function createWindow() { function createWindow() {
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required"); let electronSwitchesDefaults = ["autoplay-policy", "no-user-gesture-required"];
app.commandLine.appendSwitch(...new Set(electronSwitchesDefaults, config.electronSwitches));
let electronOptionsDefaults = { let electronOptionsDefaults = {
width: 800, width: 800,
height: 600, height: 600,
@@ -65,12 +66,17 @@ function createWindow() {
if (process.argv.includes("dev")) { if (process.argv.includes("dev")) {
if (process.env.JEST_WORKER_ID !== undefined) { if (process.env.JEST_WORKER_ID !== undefined) {
// if we are running with jest // if we are running with jest
var devtools = new BrowserWindow(electronOptions); const devtools = new BrowserWindow(electronOptions);
mainWindow.webContents.setDevToolsWebContents(devtools.webContents); mainWindow.webContents.setDevToolsWebContents(devtools.webContents);
} }
mainWindow.webContents.openDevTools(); mainWindow.webContents.openDevTools();
} }
// simulate mouse move to hide black cursor on start
mainWindow.webContents.on("dom-ready", (event) => {
mainWindow.webContents.sendInputEvent({ type: "mouseMove", x: 0, y: 0 });
});
// Set responders for window events. // Set responders for window events.
mainWindow.on("closed", function () { mainWindow.on("closed", function () {
mainWindow = null; mainWindow = null;
@@ -102,7 +108,12 @@ app.on("ready", function () {
// Quit when all windows are closed. // Quit when all windows are closed.
app.on("window-all-closed", function () { app.on("window-all-closed", function () {
createWindow(); if (process.env.JEST_WORKER_ID !== undefined) {
// if we are running with jest
app.quit();
} else {
createWindow();
}
}); });
app.on("activate", function () { app.on("activate", function () {

View File

@@ -9,12 +9,13 @@
*/ */
(function (root, factory) { (function (root, factory) {
if (typeof exports === "object") { if (typeof exports === "object") {
// add timestamps in front of log messages if (process.env.JEST_WORKER_ID === undefined) {
require("console-stamp")(console, { // add timestamps in front of log messages
pattern: "yyyy-mm-dd HH:MM:ss.l", require("console-stamp")(console, {
include: ["debug", "log", "info", "warn", "error"] pattern: "yyyy-mm-dd HH:MM:ss.l",
}); include: ["debug", "log", "info", "warn", "error"]
});
}
// Node, CommonJS-like // Node, CommonJS-like
module.exports = factory(root.config); module.exports = factory(root.config);
} else { } else {
@@ -22,29 +23,57 @@
root.Log = factory(root.config); root.Log = factory(root.config);
} }
})(this, function (config) { })(this, function (config) {
const logLevel = { let logLevel;
debug: Function.prototype.bind.call(console.debug, console), let enableLog;
log: Function.prototype.bind.call(console.log, console), if (typeof exports === "object") {
info: Function.prototype.bind.call(console.info, console), // in nodejs and not running with jest
warn: Function.prototype.bind.call(console.warn, console), enableLog = process.env.JEST_WORKER_ID === undefined;
error: Function.prototype.bind.call(console.error, console), } else {
group: Function.prototype.bind.call(console.group, console), // in browser and not running with jsdom
groupCollapsed: Function.prototype.bind.call(console.groupCollapsed, console), enableLog = typeof window === "object" && window.name !== "jsdom";
groupEnd: Function.prototype.bind.call(console.groupEnd, console), }
time: Function.prototype.bind.call(console.time, console),
timeEnd: Function.prototype.bind.call(console.timeEnd, console),
timeStamp: Function.prototype.bind.call(console.timeStamp, console)
};
logLevel.setLogLevel = function (newLevel) { if (enableLog) {
if (newLevel) { logLevel = {
Object.keys(logLevel).forEach(function (key, index) { debug: Function.prototype.bind.call(console.debug, console),
if (!newLevel.includes(key.toLocaleUpperCase())) { log: Function.prototype.bind.call(console.log, console),
logLevel[key] = function () {}; info: Function.prototype.bind.call(console.info, console),
} warn: Function.prototype.bind.call(console.warn, console),
}); error: Function.prototype.bind.call(console.error, console),
} group: Function.prototype.bind.call(console.group, console),
}; groupCollapsed: Function.prototype.bind.call(console.groupCollapsed, console),
groupEnd: Function.prototype.bind.call(console.groupEnd, console),
time: Function.prototype.bind.call(console.time, console),
timeEnd: Function.prototype.bind.call(console.timeEnd, console),
timeStamp: Function.prototype.bind.call(console.timeStamp, console)
};
logLevel.setLogLevel = function (newLevel) {
if (newLevel) {
Object.keys(logLevel).forEach(function (key, index) {
if (!newLevel.includes(key.toLocaleUpperCase())) {
logLevel[key] = function () {};
}
});
}
};
} else {
logLevel = {
debug: function () {},
log: function () {},
info: function () {},
warn: function () {},
error: function () {},
group: function () {},
groupCollapsed: function () {},
groupEnd: function () {},
time: function () {},
timeEnd: function () {},
timeStamp: function () {}
};
logLevel.setLogLevel = function () {};
}
return logLevel; return logLevel;
}); });

View File

@@ -7,7 +7,7 @@
* By Michael Teeuw https://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
var Module = Class.extend({ const Module = Class.extend({
/********************************************************* /*********************************************************
* All methods (and properties) below can be subclassed. * * All methods (and properties) below can be subclassed. *
*********************************************************/ *********************************************************/
@@ -498,8 +498,8 @@ Module.create = function (name) {
Module.register = function (name, moduleDefinition) { Module.register = function (name, moduleDefinition) {
if (moduleDefinition.requiresVersion) { if (moduleDefinition.requiresVersion) {
Log.log("Check MagicMirror version for module '" + name + "' - Minimum version: " + moduleDefinition.requiresVersion + " - Current version: " + window.version); Log.log("Check MagicMirror version for module '" + name + "' - Minimum version: " + moduleDefinition.requiresVersion + " - Current version: " + window.mmVersion);
if (cmpVersions(window.version, moduleDefinition.requiresVersion) >= 0) { if (cmpVersions(window.mmVersion, moduleDefinition.requiresVersion) >= 0) {
Log.log("Version is ok!"); Log.log("Version is ok!");
} else { } else {
Log.warn("Version is incorrect. Skip module: '" + name + "'"); Log.warn("Version is incorrect. Skip module: '" + name + "'");
@@ -510,6 +510,8 @@ Module.register = function (name, moduleDefinition) {
Module.definitions[name] = moduleDefinition; Module.definitions[name] = moduleDefinition;
}; };
window.Module = Module;
/** /**
* Compare two semantic version numbers and return the difference. * Compare two semantic version numbers and return the difference.
* *

View File

@@ -23,6 +23,7 @@ const Utils = require("./utils.js");
*/ */
function Server(config, callback) { function Server(config, callback) {
const port = process.env.MM_PORT || config.port; const port = process.env.MM_PORT || config.port;
const serverSockets = new Set();
let server = null; let server = null;
if (config.useHttps) { if (config.useHttps) {
@@ -42,6 +43,13 @@ function Server(config, callback) {
allowEIO3: true allowEIO3: true
}); });
server.on("connection", (socket) => {
serverSockets.add(socket);
socket.on("close", () => {
serverSockets.delete(socket);
});
});
Log.log(`Starting server on port ${port} ... `); Log.log(`Starting server on port ${port} ... `);
server.listen(port, config.address || "localhost"); server.listen(port, config.address || "localhost");
@@ -92,6 +100,13 @@ function Server(config, callback) {
if (typeof callback === "function") { if (typeof callback === "function") {
callback(app, io); callback(app, io);
} }
this.close = function () {
for (const socket of serverSockets.values()) {
socket.destroy();
}
server.close();
};
} }
module.exports = Server; module.exports = Server;

View File

@@ -6,7 +6,7 @@
* By Christopher Fenner https://github.com/CFenner * By Christopher Fenner https://github.com/CFenner
* MIT Licensed. * MIT Licensed.
*/ */
var Translator = (function () { const Translator = (function () {
/** /**
* Load a JSON file via XHR. * Load a JSON file via XHR.
* *
@@ -141,12 +141,7 @@ var Translator = (function () {
* The first language defined in translations.js will be used. * The first language defined in translations.js will be used.
*/ */
loadCoreTranslationsFallback: function () { loadCoreTranslationsFallback: function () {
// The variable `first` will contain the first let first = Object.keys(translations)[0];
// defined translation after the following line.
for (var first in translations) {
break;
}
if (first) { if (first) {
Log.log("Loading core translation fallback file: " + translations[first]); Log.log("Loading core translation fallback file: " + translations[first]);
loadJSON(translations[first], (translations) => { loadJSON(translations[first], (translations) => {
@@ -156,3 +151,5 @@ var Translator = (function () {
} }
}; };
})(); })();
window.Translator = Translator;

View File

@@ -1,16 +1,16 @@
type ModuleProperties = { type ModuleProperties = {
defaults?: object, defaults?: object;
start?(): void, start?(): void;
getHeader?(): string, getHeader?(): string;
getTemplate?(): string, getTemplate?(): string;
getTemplateData?(): object, getTemplateData?(): object;
notificationReceived?(notification: string, payload: any, sender: object): void, notificationReceived?(notification: string, payload: any, sender: object): void;
socketNotificationReceived?(notification: string, payload: any): void, socketNotificationReceived?(notification: string, payload: any): void;
suspend?(): void, suspend?(): void;
resume?(): void, resume?(): void;
getDom?(): HTMLElement, getDom?(): HTMLElement;
getStyles?(): string[], getStyles?(): string[];
[key: string]: any, [key: string]: any;
}; };
export declare const Module: { export declare const Module: {
@@ -18,14 +18,14 @@ export declare const Module: {
}; };
export declare const Log: { export declare const Log: {
info(message?: any, ...optionalParams: any[]): void, info(message?: any, ...optionalParams: any[]): void;
log(message?: any, ...optionalParams: any[]): void, log(message?: any, ...optionalParams: any[]): void;
error(message?: any, ...optionalParams: any[]): void, error(message?: any, ...optionalParams: any[]): void;
warn(message?: any, ...optionalParams: any[]): void, warn(message?: any, ...optionalParams: any[]): void;
group(groupTitle?: string, ...optionalParams: any[]): void, group(groupTitle?: string, ...optionalParams: any[]): void;
groupCollapsed(groupTitle?: string, ...optionalParams: any[]): void, groupCollapsed(groupTitle?: string, ...optionalParams: any[]): void;
groupEnd(): void, groupEnd(): void;
time(timerName?: string): void, time(timerName?: string): void;
timeEnd(timerName?: string): void, timeEnd(timerName?: string): void;
timeStamp(timerName?: string): void, timeStamp(timerName?: string): void;
}; };

View File

@@ -138,13 +138,14 @@ const CalendarUtils = {
return CalendarUtils.isFullDayEvent(event) ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time])); return CalendarUtils.isFullDayEvent(event) ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time]));
}; };
Log.debug("there are " + Object.entries(data).length + " calendar entries"); Log.debug("There are " + Object.entries(data).length + " calendar entries.");
Object.entries(data).forEach(([key, event]) => { Object.entries(data).forEach(([key, event]) => {
Log.debug("Processing entry...");
const now = new Date(); const now = new Date();
const today = moment().startOf("day").toDate(); const today = moment().startOf("day").toDate();
const future = moment().startOf("day").add(config.maximumNumberOfDays, "days").subtract(1, "seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat. const future = moment().startOf("day").add(config.maximumNumberOfDays, "days").subtract(1, "seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat.
let past = today; let past = today;
Log.debug("have entries ");
if (config.includePastEvents) { if (config.includePastEvents) {
past = moment().startOf("day").subtract(config.maximumNumberOfDays, "days").toDate(); past = moment().startOf("day").subtract(config.maximumNumberOfDays, "days").toDate();
} }
@@ -159,10 +160,10 @@ const CalendarUtils = {
} }
if (event.type === "VEVENT") { if (event.type === "VEVENT") {
Log.debug("\nEvent: " + JSON.stringify(event));
let startDate = eventDate(event, "start"); let startDate = eventDate(event, "start");
let endDate; let endDate;
Log.debug("\nevent=" + JSON.stringify(event));
if (typeof event.end !== "undefined") { if (typeof event.end !== "undefined") {
endDate = eventDate(event, "end"); endDate = eventDate(event, "end");
} else if (typeof event.duration !== "undefined") { } else if (typeof event.duration !== "undefined") {
@@ -176,16 +177,21 @@ const CalendarUtils = {
} }
} }
Log.debug(" start=" + startDate.toDate() + " end=" + endDate.toDate()); Log.debug("startDate (local): " + startDate.toDate());
Log.debug("endDate (local): " + endDate.toDate());
// calculate the duration of the event for use with recurring events. // Calculate the duration of the event for use with recurring events.
let duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x")); let duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x"));
Log.debug("duration: " + duration);
// FIXME: Since the parsed json object from node-ical comes with time information
// this check could be removed (?)
if (event.start.length === 8) { if (event.start.length === 8) {
startDate = startDate.startOf("day"); startDate = startDate.startOf("day");
} }
const title = CalendarUtils.getTitleFromEvent(event); const title = CalendarUtils.getTitleFromEvent(event);
Log.debug("title: " + title);
let excluded = false, let excluded = false,
dateFilter = null; dateFilter = null;
@@ -260,9 +266,13 @@ const CalendarUtils = {
let pastLocal = 0; let pastLocal = 0;
let futureLocal = 0; let futureLocal = 0;
if (CalendarUtils.isFullDayEvent(event)) { if (CalendarUtils.isFullDayEvent(event)) {
Log.debug("fullday");
// if full day event, only use the date part of the ranges // if full day event, only use the date part of the ranges
pastLocal = pastMoment.toDate(); pastLocal = pastMoment.toDate();
futureLocal = futureMoment.toDate(); futureLocal = futureMoment.toDate();
Log.debug("pastLocal: " + pastLocal);
Log.debug("futureLocal: " + futureLocal);
} else { } else {
// if we want past events // if we want past events
if (config.includePastEvents) { if (config.includePastEvents) {
@@ -274,9 +284,9 @@ const CalendarUtils = {
} }
futureLocal = futureMoment.toDate(); // future futureLocal = futureMoment.toDate(); // future
} }
Log.debug(" between=" + pastLocal + " to " + futureLocal); Log.debug("Search for recurring events between: " + pastLocal + " and " + futureLocal);
const dates = rule.between(pastLocal, futureLocal, true, limitFunction); const dates = rule.between(pastLocal, futureLocal, true, limitFunction);
Log.debug("title=" + event.summary + " dates=" + JSON.stringify(dates)); Log.debug("Title: " + event.summary + ", with dates: " + JSON.stringify(dates));
// The "dates" array contains the set of dates within our desired date range range that are valid // The "dates" array contains the set of dates within our desired date range range that are valid
// for the recurrence rule. *However*, it's possible for us to have a specific recurrence that // for the recurrence rule. *However*, it's possible for us to have a specific recurrence that
// had its date changed from outside the range to inside the range. For the time being, // had its date changed from outside the range to inside the range. For the time being,
@@ -284,6 +294,7 @@ const CalendarUtils = {
// because the logic below will filter out any recurrences that don't actually belong within // because the logic below will filter out any recurrences that don't actually belong within
// our display range. // our display range.
// Would be great if there was a better way to handle this. // Would be great if there was a better way to handle this.
Log.debug("event.recurrences: " + event.recurrences);
if (event.recurrences !== undefined) { if (event.recurrences !== undefined) {
for (let r in event.recurrences) { for (let r in event.recurrences) {
// Only add dates that weren't already in the range we added from the rrule so that // Only add dates that weren't already in the range we added from the rrule so that
@@ -296,38 +307,42 @@ const CalendarUtils = {
// Loop through the set of date entries to see which recurrences should be added to our event list. // Loop through the set of date entries to see which recurrences should be added to our event list.
for (let d in dates) { for (let d in dates) {
let date = dates[d]; let date = dates[d];
// ical.js started returning recurrences and exdates as ISOStrings without time information. // Remove the time information of each date by using its substring, using the following method:
// .toISOString().substring(0,10) is the method they use to calculate keys, so we'll do the same // .toISOString().substring(0,10).
// (see https://github.com/peterbraden/ical.js/pull/84 ) // since the date is given as ISOString with YYYY-MM-DDTHH:MM:SS.SSSZ
// (see https://momentjs.com/docs/#/displaying/as-iso-string/).
const dateKey = date.toISOString().substring(0, 10); const dateKey = date.toISOString().substring(0, 10);
let curEvent = event; let curEvent = event;
let showRecurrence = true; let showRecurrence = true;
// get the offset of today where we are processing // Get the offset of today where we are processing
// this will be the correction we need to apply // This will be the correction, we need to apply.
let nowOffset = new Date().getTimezoneOffset(); let nowOffset = new Date().getTimezoneOffset();
// for full day events, the time might be off from RRULE/Luxon problem // For full day events, the time might be off from RRULE/Luxon problem
// get time zone offset of the rule calculated event // Get time zone offset of the rule calculated event
let dateoffset = date.getTimezoneOffset(); let dateoffset = date.getTimezoneOffset();
// reduce the time by the offset
// Reduce the time by the following offset.
Log.debug(" recurring date is " + date + " offset is " + dateoffset); Log.debug(" recurring date is " + date + " offset is " + dateoffset);
let dh = moment(date).format("HH"); let dh = moment(date).format("HH");
Log.debug(" recurring date is " + date + " offset is " + dateoffset / 60 + " Hour is " + dh); Log.debug(" recurring date is " + date + " offset is " + dateoffset / 60 + " Hour is " + dh);
if (CalendarUtils.isFullDayEvent(event)) { if (CalendarUtils.isFullDayEvent(event)) {
Log.debug("fullday"); Log.debug("Fullday");
// if the offset is negative, east of GMT where the problem is // If the offset is negative (east of GMT), where the problem is
if (dateoffset < 0) { if (dateoffset < 0) {
// if the date hour is less than the offset // Remove the offset, independently of the comparison between the date hour and the offset,
if (dh < Math.abs(dateoffset / 60)) { // since in the case that *date houre < offset*, the *new Date* command will handle this by
// reduce the time by the offset // representing the day before.
Log.debug(" recurring date is " + date + " offset is " + dateoffset);
// apply the correction to the date/time to get it UTC relative // Reduce the time by the offset:
date = new Date(date.getTime() - Math.abs(nowOffset) * 60000); // Apply the correction to the date/time to get it UTC relative
// the duration was calculated way back at the top before we could correct the start time.. date = new Date(date.getTime() - Math.abs(nowOffset) * 60000);
// fix it for this event entry // the duration was calculated way back at the top before we could correct the start time..
//duration = 24 * 60 * 60 * 1000; // fix it for this event entry
Log.debug("new recurring date1 is " + date); //duration = 24 * 60 * 60 * 1000;
} Log.debug("new recurring date1 is " + date);
} else { } else {
// if the timezones are the same, correct date if needed // if the timezones are the same, correct date if needed
if (event.start.tz === moment.tz.guess()) { if (event.start.tz === moment.tz.guess()) {
@@ -348,9 +363,8 @@ const CalendarUtils = {
if (dateoffset < 0) { if (dateoffset < 0) {
// if the date hour is less than the offset // if the date hour is less than the offset
if (dh < Math.abs(dateoffset / 60)) { if (dh < Math.abs(dateoffset / 60)) {
// reduce the time by the offset // Reduce the time by the offset:
Log.debug(" recurring date is " + date + " offset is " + dateoffset); // Apply the correction to the date/time to get it UTC relative
// apply the correction to the date/time to get it UTC relative
date = new Date(date.getTime() - Math.abs(nowOffset) * 60000); date = new Date(date.getTime() - Math.abs(nowOffset) * 60000);
// the duration was calculated way back at the top before we could correct the start time.. // the duration was calculated way back at the top before we could correct the start time..
// fix it for this event entry // fix it for this event entry
@@ -373,6 +387,7 @@ const CalendarUtils = {
} }
} }
startDate = moment(date); startDate = moment(date);
Log.debug("Corrected startDate (local): " + startDate.toDate());
let adjustDays = CalendarUtils.calculateTimezoneAdjustment(event, date); let adjustDays = CalendarUtils.calculateTimezoneAdjustment(event, date);
@@ -388,7 +403,7 @@ const CalendarUtils = {
// This date is an exception date, which means we should skip it in the recurrence pattern. // This date is an exception date, which means we should skip it in the recurrence pattern.
showRecurrence = false; showRecurrence = false;
} }
Log.debug("duration=" + duration); Log.debug("duration: " + duration);
endDate = moment(parseInt(startDate.format("x")) + duration, "x"); endDate = moment(parseInt(startDate.format("x")) + duration, "x");
if (startDate.format("x") === endDate.format("x")) { if (startDate.format("x") === endDate.format("x")) {
@@ -408,7 +423,7 @@ const CalendarUtils = {
} }
if (showRecurrence === true) { if (showRecurrence === true) {
Log.debug("saving event =" + description); Log.debug("saving event: " + description);
addedEvents++; addedEvents++;
newEvents.push({ newEvents.push({
title: recurrenceTitle, title: recurrenceTitle,
@@ -424,7 +439,7 @@ const CalendarUtils = {
}); });
} }
} }
// end recurring event parsing // End recurring event parsing.
} else { } else {
// Single event. // Single event.
const fullDayEvent = isFacebookBirthday ? true : CalendarUtils.isFullDayEvent(event); const fullDayEvent = isFacebookBirthday ? true : CalendarUtils.isFullDayEvent(event);

View File

@@ -12,11 +12,14 @@ Module.register("clock", {
displayType: "digital", // options: digital, analog, both displayType: "digital", // options: digital, analog, both
timeFormat: config.timeFormat, timeFormat: config.timeFormat,
timezone: null,
displaySeconds: true, displaySeconds: true,
showPeriod: true, showPeriod: true,
showPeriodUpper: false, showPeriodUpper: false,
clockBold: false, clockBold: false,
showDate: true, showDate: true,
showTime: true,
showWeek: false, showWeek: false,
dateFormat: "dddd, LL", dateFormat: "dddd, LL",
@@ -24,9 +27,8 @@ Module.register("clock", {
analogSize: "200px", analogSize: "200px",
analogFace: "simple", // options: 'none', 'simple', 'face-###' (where ### is 001 to 012 inclusive) analogFace: "simple", // options: 'none', 'simple', 'face-###' (where ### is 001 to 012 inclusive)
analogPlacement: "bottom", // options: 'top', 'bottom', 'left', 'right' analogPlacement: "bottom", // options: 'top', 'bottom', 'left', 'right'
analogShowDate: "top", // options: false, 'top', or 'bottom' analogShowDate: "top", // OBSOLETE, can be replaced with analogPlacement and showTime, options: false, 'top', or 'bottom'
secondsColor: "#888888", secondsColor: "#888888",
timezone: null,
showSunTimes: false, showSunTimes: false,
showMoonTimes: false, showMoonTimes: false,
@@ -89,11 +91,20 @@ Module.register("clock", {
// Override dom generator. // Override dom generator.
getDom: function () { getDom: function () {
const wrapper = document.createElement("div"); const wrapper = document.createElement("div");
wrapper.classList.add("clockGrid");
/************************************
* Create wrappers for analog and digital clock
*/
const analogWrapper = document.createElement("div");
analogWrapper.className = "clockCircle";
const digitalWrapper = document.createElement("div");
digitalWrapper.className = "digital";
digitalWrapper.style.gridArea = "center";
/************************************ /************************************
* Create wrappers for DIGITAL clock * Create wrappers for DIGITAL clock
*/ */
const dateWrapper = document.createElement("div"); const dateWrapper = document.createElement("div");
const timeWrapper = document.createElement("div"); const timeWrapper = document.createElement("div");
const secondsWrapper = document.createElement("sup"); const secondsWrapper = document.createElement("sup");
@@ -101,10 +112,11 @@ Module.register("clock", {
const sunWrapper = document.createElement("div"); const sunWrapper = document.createElement("div");
const moonWrapper = document.createElement("div"); const moonWrapper = document.createElement("div");
const weekWrapper = document.createElement("div"); const weekWrapper = document.createElement("div");
// Style Wrappers // Style Wrappers
dateWrapper.className = "date normal medium"; dateWrapper.className = "date normal medium";
timeWrapper.className = "time bright large light"; timeWrapper.className = "time bright large light";
secondsWrapper.className = "dimmed"; secondsWrapper.className = "seconds dimmed";
sunWrapper.className = "sun dimmed small"; sunWrapper.className = "sun dimmed small";
moonWrapper.className = "moon dimmed small"; moonWrapper.className = "moon dimmed small";
weekWrapper.className = "week dimmed medium"; weekWrapper.className = "week dimmed medium";
@@ -124,7 +136,7 @@ Module.register("clock", {
hourSymbol = "h"; hourSymbol = "h";
} }
if (this.config.clockBold === true) { if (this.config.clockBold) {
timeString = now.format(hourSymbol + '[<span class="bold">]mm[</span>]'); timeString = now.format(hourSymbol + '[<span class="bold">]mm[</span>]');
} else { } else {
timeString = now.format(hourSymbol + ":mm"); timeString = now.format(hourSymbol + ":mm");
@@ -132,22 +144,24 @@ Module.register("clock", {
if (this.config.showDate) { if (this.config.showDate) {
dateWrapper.innerHTML = now.format(this.config.dateFormat); dateWrapper.innerHTML = now.format(this.config.dateFormat);
digitalWrapper.appendChild(dateWrapper);
} }
if (this.config.showWeek) {
weekWrapper.innerHTML = this.translate("WEEK", { weekNumber: now.week() }); if (this.config.displayType !== "analog" && this.config.showTime) {
} timeWrapper.innerHTML = timeString;
timeWrapper.innerHTML = timeString; secondsWrapper.innerHTML = now.format("ss");
secondsWrapper.innerHTML = now.format("ss"); if (this.config.showPeriodUpper) {
if (this.config.showPeriodUpper) { periodWrapper.innerHTML = now.format("A");
periodWrapper.innerHTML = now.format("A"); } else {
} else { periodWrapper.innerHTML = now.format("a");
periodWrapper.innerHTML = now.format("a"); }
} if (this.config.displaySeconds) {
if (this.config.displaySeconds) { timeWrapper.appendChild(secondsWrapper);
timeWrapper.appendChild(secondsWrapper); }
} if (this.config.showPeriod && this.config.timeFormat !== 24) {
if (this.config.showPeriod && this.config.timeFormat !== 24) { timeWrapper.appendChild(periodWrapper);
timeWrapper.appendChild(periodWrapper); }
digitalWrapper.appendChild(timeWrapper);
} }
/** /**
@@ -165,6 +179,9 @@ Module.register("clock", {
return moment(time).format(formatString); return moment(time).format(formatString);
} }
/****************************************************************
* Create wrappers for Sun Times, only if specified in config
*/
if (this.config.showSunTimes) { if (this.config.showSunTimes) {
const sunTimes = SunCalc.getTimes(now, this.config.lat, this.config.lon); const sunTimes = SunCalc.getTimes(now, this.config.lat, this.config.lon);
const isVisible = now.isBetween(sunTimes.sunrise, sunTimes.sunset); const isVisible = now.isBetween(sunTimes.sunrise, sunTimes.sunset);
@@ -191,7 +208,12 @@ Module.register("clock", {
'<span><i class="fa fa-arrow-down" aria-hidden="true"></i> ' + '<span><i class="fa fa-arrow-down" aria-hidden="true"></i> ' +
formatTime(this.config, sunTimes.sunset) + formatTime(this.config, sunTimes.sunset) +
"</span>"; "</span>";
digitalWrapper.appendChild(sunWrapper);
} }
/****************************************************************
* Create wrappers for Moon Times, only if specified in config
*/
if (this.config.showMoonTimes) { if (this.config.showMoonTimes) {
const moonIllumination = SunCalc.getMoonIllumination(now.toDate()); const moonIllumination = SunCalc.getMoonIllumination(now.toDate());
const moonTimes = SunCalc.getMoonTimes(now, this.config.lat, this.config.lon); const moonTimes = SunCalc.getMoonTimes(now, this.config.lat, this.config.lon);
@@ -217,13 +239,17 @@ Module.register("clock", {
'<span><i class="fa fa-arrow-down" aria-hidden="true"></i> ' + '<span><i class="fa fa-arrow-down" aria-hidden="true"></i> ' +
(moonSet ? formatTime(this.config, moonSet) : "...") + (moonSet ? formatTime(this.config, moonSet) : "...") +
"</span>"; "</span>";
digitalWrapper.appendChild(moonWrapper);
}
if (this.config.showWeek) {
weekWrapper.innerHTML = this.translate("WEEK", { weekNumber: now.week() });
digitalWrapper.appendChild(weekWrapper);
} }
/**************************************************************** /****************************************************************
* Create wrappers for ANALOG clock, only if specified in config * Create wrappers for ANALOG clock, only if specified in config
*/ */
const clockCircle = document.createElement("div");
if (this.config.displayType !== "digital") { if (this.config.displayType !== "digital") {
// If it isn't 'digital', then an 'analog' clock was also requested // If it isn't 'digital', then an 'analog' clock was also requested
@@ -236,19 +262,18 @@ Module.register("clock", {
hour = ((now.hours() % 12) / 12) * 360 + 90 + minute / 12; hour = ((now.hours() % 12) / 12) * 360 + 90 + minute / 12;
// Create wrappers // Create wrappers
clockCircle.className = "clockCircle"; analogWrapper.style.width = this.config.analogSize;
clockCircle.style.width = this.config.analogSize; analogWrapper.style.height = this.config.analogSize;
clockCircle.style.height = this.config.analogSize;
if (this.config.analogFace !== "" && this.config.analogFace !== "simple" && this.config.analogFace !== "none") { if (this.config.analogFace !== "" && this.config.analogFace !== "simple" && this.config.analogFace !== "none") {
clockCircle.style.background = "url(" + this.data.path + "faces/" + this.config.analogFace + ".svg)"; analogWrapper.style.background = "url(" + this.data.path + "faces/" + this.config.analogFace + ".svg)";
clockCircle.style.backgroundSize = "100%"; analogWrapper.style.backgroundSize = "100%";
// The following line solves issue: https://github.com/MichMich/MagicMirror/issues/611 // The following line solves issue: https://github.com/MichMich/MagicMirror/issues/611
// clockCircle.style.border = "1px solid black"; // analogWrapper.style.border = "1px solid black";
clockCircle.style.border = "rgba(0, 0, 0, 0.1)"; //Updated fix for Issue 611 where non-black backgrounds are used analogWrapper.style.border = "rgba(0, 0, 0, 0.1)"; //Updated fix for Issue 611 where non-black backgrounds are used
} else if (this.config.analogFace !== "none") { } else if (this.config.analogFace !== "none") {
clockCircle.style.border = "2px solid white"; analogWrapper.style.border = "2px solid white";
} }
const clockFace = document.createElement("div"); const clockFace = document.createElement("div");
clockFace.className = "clockFace"; clockFace.className = "clockFace";
@@ -274,84 +299,28 @@ Module.register("clock", {
clockSecond.style.backgroundColor = this.config.secondsColor; clockSecond.style.backgroundColor = this.config.secondsColor;
clockFace.appendChild(clockSecond); clockFace.appendChild(clockSecond);
} }
clockCircle.appendChild(clockFace); analogWrapper.appendChild(clockFace);
} }
/******************************************* /*******************************************
* Combine wrappers, check for .displayType * Update placement, respect old analogShowDate even if its not needed anymore
*/ */
if (this.config.displayType === "analog") {
if (this.config.displayType === "digital") {
// Display only a digital clock
wrapper.appendChild(dateWrapper);
wrapper.appendChild(timeWrapper);
wrapper.appendChild(sunWrapper);
wrapper.appendChild(moonWrapper);
wrapper.appendChild(weekWrapper);
} else if (this.config.displayType === "analog") {
// Display only an analog clock // Display only an analog clock
if (this.config.showWeek) {
weekWrapper.style.paddingBottom = "15px";
} else {
dateWrapper.style.paddingBottom = "15px";
}
if (this.config.analogShowDate === "top") { if (this.config.analogShowDate === "top") {
wrapper.appendChild(dateWrapper); wrapper.classList.add("clockGrid--bottom");
wrapper.appendChild(weekWrapper);
wrapper.appendChild(clockCircle);
} else if (this.config.analogShowDate === "bottom") { } else if (this.config.analogShowDate === "bottom") {
wrapper.appendChild(clockCircle); wrapper.classList.add("clockGrid--top");
wrapper.appendChild(dateWrapper);
wrapper.appendChild(weekWrapper);
} else { } else {
wrapper.appendChild(clockCircle); //analogWrapper.style.gridArea = "center";
}
} else {
// Both clocks have been configured, check position
const placement = this.config.analogPlacement;
const analogWrapper = document.createElement("div");
analogWrapper.id = "analog";
analogWrapper.style.cssFloat = "none";
analogWrapper.appendChild(clockCircle);
const digitalWrapper = document.createElement("div");
digitalWrapper.id = "digital";
digitalWrapper.style.cssFloat = "none";
digitalWrapper.appendChild(dateWrapper);
digitalWrapper.appendChild(timeWrapper);
digitalWrapper.appendChild(sunWrapper);
digitalWrapper.appendChild(moonWrapper);
digitalWrapper.appendChild(weekWrapper);
const appendClocks = (condition, pos1, pos2) => {
const padding = [0, 0, 0, 0];
padding[placement === condition ? pos1 : pos2] = "20px";
analogWrapper.style.padding = padding.join(" ");
if (placement === condition) {
wrapper.appendChild(analogWrapper);
wrapper.appendChild(digitalWrapper);
} else {
wrapper.appendChild(digitalWrapper);
wrapper.appendChild(analogWrapper);
}
};
if (placement === "left" || placement === "right") {
digitalWrapper.style.display = "inline-block";
digitalWrapper.style.verticalAlign = "top";
analogWrapper.style.display = "inline-block";
appendClocks("left", 1, 3);
} else {
digitalWrapper.style.textAlign = "center";
appendClocks("top", 2, 0);
} }
} else if (this.config.displayType === "both") {
wrapper.classList.add("clockGrid--" + this.config.analogPlacement);
} }
wrapper.appendChild(analogWrapper);
wrapper.appendChild(digitalWrapper);
// Return the wrapper to the dom. // Return the wrapper to the dom.
return wrapper; return wrapper;
} }

View File

@@ -1,5 +1,26 @@
.clockGrid {
display: inline-flex;
gap: 15px;
}
.clockGrid--left {
flex-direction: row;
}
.clockGrid--right {
flex-direction: row-reverse;
}
.clockGrid--top {
flex-direction: column;
}
.clockGrid--bottom {
flex-direction: column-reverse;
}
.clockCircle { .clockCircle {
margin: 0 auto; place-self: center;
position: relative; position: relative;
border-radius: 50%; border-radius: 50%;
background-size: 100%; background-size: 100%;

View File

@@ -1,3 +1,5 @@
/* eslint-disable */
/* Magic Mirror /* Magic Mirror
* Module: CurrentWeather * Module: CurrentWeather
* *

View File

@@ -0,0 +1,177 @@
const util = require("util");
const exec = util.promisify(require("child_process").exec);
const fs = require("fs");
const path = require("path");
const Log = require("logger");
class gitHelper {
constructor() {
this.gitRepos = [];
this.baseDir = path.normalize(__dirname + "/../../../");
}
getRefRegex(branch) {
return new RegExp("s*([a-z,0-9]+[.][.][a-z,0-9]+) " + branch, "g");
}
async execShell(command) {
let res = { stdout: "", stderr: "" };
const { stdout, stderr } = await exec(command);
res.stdout = stdout;
res.stderr = stderr;
return res;
}
async isGitRepo(moduleFolder) {
let res = await this.execShell("cd " + moduleFolder + " && git remote -v");
if (res.stderr) {
Log.error("Failed to fetch git data for " + moduleFolder + ": " + res.stderr);
return false;
} else {
return true;
}
}
add(moduleName) {
let moduleFolder = this.baseDir;
if (moduleName !== "default") {
moduleFolder = moduleFolder + "modules/" + moduleName;
}
try {
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)) {
// Folder has .git and has at least one git remote, watch this folder
this.gitRepos.unshift({ module: moduleName, folder: moduleFolder });
}
} catch (err) {
// Error when directory .git doesn't exist or doesn't have any remotes
// This module is not managed with git, skip
}
}
async getStatusInfo(repo) {
let gitInfo = {
module: repo.module,
// commits behind:
behind: 0,
// branch name:
current: "",
// current hash:
hash: "",
// remote branch:
tracking: "",
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");
if (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");
}
if (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:
// ## develop...origin/develop
// ## master...origin/master [behind 8]
status = status.match(/(?![.#])([^.]*)/g);
// examples for status:
// [ ' develop', 'origin/develop', '' ]
// [ ' master', 'origin/master [behind 8]', '' ]
gitInfo.current = status[0].trim();
status = status[1].split(" ");
// examples for status:
// [ '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");
}
// 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]);
gitInfo.behind = parseInt(res.stdout);
return gitInfo;
} catch (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);
}
}
return gitResultList;
}
async getRepos() {
const gitResultList = [];
for (let repo of this.gitRepos) {
const gitInfo = await this.getRepoInfo(repo);
if (gitInfo) {
gitResultList.unshift(gitInfo);
}
}
return gitResultList;
}
}
module.exports.gitHelper = gitHelper;

View File

@@ -1,9 +1,5 @@
const SimpleGit = require("simple-git"); const GitHelper = require(__dirname + "/git_helper.js");
const simpleGits = [];
const fs = require("fs");
const path = require("path");
const defaultModules = require(__dirname + "/../defaultmodules.js"); const defaultModules = require(__dirname + "/../defaultmodules.js");
const Log = require("logger");
const NodeHelper = require("node_helper"); const NodeHelper = require("node_helper");
module.exports = NodeHelper.create({ module.exports = NodeHelper.create({
@@ -12,32 +8,19 @@ module.exports = NodeHelper.create({
updateTimer: null, updateTimer: null,
updateProcessStarted: false, updateProcessStarted: false,
gitHelper: new GitHelper.gitHelper(),
start: function () {}, start: function () {},
configureModules: async function (modules) { configureModules: async function (modules) {
// Push MagicMirror itself , biggest chance it'll show up last in UI and isn't overwritten // Push MagicMirror itself , biggest chance it'll show up last in UI and isn't overwritten
// others will be added in front // others will be added in front
// this method returns promises so we can't wait for every one to resolve before continuing // this method returns promises so we can't wait for every one to resolve before continuing
simpleGits.push({ module: "default", git: this.createGit(path.normalize(__dirname + "/../../../")) }); this.gitHelper.add("default");
for (let moduleName in modules) { for (let moduleName in modules) {
if (!this.ignoreUpdateChecking(moduleName)) { if (!this.ignoreUpdateChecking(moduleName)) {
// Default modules are included in the main MagicMirror repo this.gitHelper.add(moduleName);
let moduleFolder = path.normalize(__dirname + "/../../" + moduleName);
try {
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
let git = await this.resolveRemote(moduleFolder);
// Folder has .git and has at least one git remote, watch this folder
simpleGits.unshift({ module: moduleName, git: git });
} catch (err) {
// Error when directory .git doesn't exist or doesn't have any remotes
// This module is not managed with git, skip
continue;
}
} }
} }
}, },
@@ -54,35 +37,9 @@ module.exports = NodeHelper.create({
} }
}, },
resolveRemote: async function (moduleFolder) {
let git = this.createGit(moduleFolder);
let remotes = await git.getRemotes(true);
if (remotes.length < 1 || remotes[0].name.length < 1) {
throw new Error("No valid remote for folder " + moduleFolder);
}
return git;
},
performFetch: async function () { performFetch: async function () {
for (let sg of simpleGits) { for (let gitInfo of await this.gitHelper.getRepos()) {
try { this.sendSocketNotification("STATUS", gitInfo);
let fetchData = await sg.git.fetch(["--dry-run"]).status();
let logData = await sg.git.log({ "-1": null });
if (logData.latest && "hash" in logData.latest) {
this.sendSocketNotification("STATUS", {
module: sg.module,
behind: fetchData.behind,
current: fetchData.current,
hash: logData.latest.hash,
tracking: fetchData.tracking
});
}
} catch (err) {
Log.error("Failed to fetch git data for " + sg.module + ": " + err);
}
} }
this.scheduleNextFetch(this.config.updateInterval); this.scheduleNextFetch(this.config.updateInterval);
@@ -100,10 +57,6 @@ module.exports = NodeHelper.create({
}, delay); }, delay);
}, },
createGit: function (folder) {
return SimpleGit({ baseDir: folder, timeout: { block: this.config.timeout } });
},
ignoreUpdateChecking: function (moduleName) { ignoreUpdateChecking: function (moduleName) {
// Should not check for updates for default modules // Should not check for updates for default modules
if (defaultModules.indexOf(moduleName) >= 0) { if (defaultModules.indexOf(moduleName) >= 0) {

View File

@@ -2,6 +2,9 @@
{% set numSteps = forecast | calcNumSteps %} {% set numSteps = forecast | calcNumSteps %}
{% set currentStep = 0 %} {% set currentStep = 0 %}
<table class="{{ config.tableClass }}"> <table class="{{ config.tableClass }}">
{% if config.ignoreToday %}
{% set forecast = forecast.splice(1) %}
{% endif %}
{% set forecast = forecast.slice(0, numSteps) %} {% set forecast = forecast.slice(0, numSteps) %}
{% for f in forecast %} {% for f in forecast %}
<tr {% if config.colored %}class="colored"{% endif %} {% if config.fade %}style="opacity: {{ currentStep | opacity(numSteps) }};"{% endif %}> <tr {% if config.colored %}class="colored"{% endif %} {% if config.fade %}style="opacity: {{ currentStep | opacity(numSteps) }};"{% endif %}>

View File

@@ -1,148 +1,3 @@
# MagicMirror² Weather Module Weather Provider Development Documentation # Weather Module Weather Provider Development Documentation
This document describes the way to develop your own MagicMirror² weather module weather provider. For how to develop your own weather provider, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/development/weather-provider.html).
Table of Contents:
- The weather provider file: yourprovider.js
- [Weather provider methods to implement](#weather-provider-methods-to-implement)
- [Weather Provider instance methods](#weather-provider-instance-methods)
- [WeatherObject](#weatherobject)
---
## The weather provider file: yourprovider.js
This is the script in which the weather provider will be defined. In its most simple form, the weather provider must implement the following:
```javascript
WeatherProvider.register("yourprovider", {
providerName: "YourProvider",
fetchCurrentWeather() {},
fetchWeatherForecast() {}
});
```
### Weather provider methods to implement
#### `fetchCurrentWeather()`
This method is called when the weather module tries to fetch the current weather of your provider. The implementation of this method is required for current weather support.
The implementation can make use of the already implemented function `this.fetchData(url, method, data);`, which is returning a promise.
After the response is processed, the current weather information (as a [WeatherObject](#weatherobject)) needs to be set with `this.setCurrentWeather(currentWeather);`.
It will then automatically refresh the module DOM with the new data.
#### `fetchWeatherForecast()`
This method is called when the weather module tries to fetch the weather of your provider. The implementation of this method is required for forecast support.
The implementation can make use of the already implemented function `this.fetchData(url, method, data);`, which is returning a promise.
After the response is processed, the weather forecast information (as an array of [WeatherObject](#weatherobject)s) needs to be set with `this.setWeatherForecast(forecast);`.
It will then automatically refresh the module DOM with the new data.
#### `fetchWeatherHourly()`
This method is called when the weather module tries to fetch the weather of your provider. The implementation of this method is required for hourly support.
The implementation can make use of the already implemented function `this.fetchData(url, method, data);`, which is returning a promise.
After the response is processed, the hourly weather forecast information (as an array of [WeatherObject](#weatherobject)s) needs to be set with `this.setWeatherHourly(forecast);`.
It will then automatically refresh the module DOM with the new data.
### Weather Provider instance methods
#### `init()`
Called when a weather provider is initialized.
#### `setConfig(config)`
Called to set the config, this config is the same as the weather module's config.
#### `start()`
Called when the weather provider is about to start.
#### `currentWeather()`
This returns a WeatherDay object for the current weather.
#### `weatherForecast()`
This returns an array of WeatherDay objects for the weather forecast.
#### `weatherHourly()`
This returns an array of WeatherDay objects for the hourly weather forecast.
#### `fetchedLocation()`
This returns the name of the fetched location or an empty string.
#### `setCurrentWeather(currentWeatherObject)`
Set the currentWeather and notify the delegate that new information is available.
#### `setWeatherForecast(weatherForecastArray)`
Set the weatherForecastArray and notify the delegate that new information is available.
#### `setWeatherHourly(weatherHourlyArray)`
Set the weatherHourlyArray and notify the delegate that new information is available.
#### `setFetchedLocation(name)`
Set the fetched location name.
#### `updateAvailable()`
Notify the delegate that new weather is available.
#### `fetchData(url, method, data)`
A convenience function to make requests. It returns a promise.
### WeatherObject
| Property | Type | Value/Unit |
| -------------- | -------- | --------------------------------------------------------------------------------------------------------------- |
| units | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| tempUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| windUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| date | `object` | [Moment.js](https://momentjs.com/) object of the time/date. |
| windSpeed | `number` | Metric: `meter/second` <br> Imperial: `miles/hour` |
| windDirection | `number` | Direction of the wind in degrees. |
| sunrise | `object` | [Moment.js](https://momentjs.com/) object of sunrise. |
| sunset | `object` | [Moment.js](https://momentjs.com/) object of sunset. |
| temperature | `number` | Current temperature |
| minTemperature | `number` | Lowest temperature of the day. |
| maxTemperature | `number` | Highest temperature of the day. |
| weatherType | `string` | Icon name of the weather type. <br> Possible values: [WeatherIcons](https://www.npmjs.com/package/weathericons) |
| humidity | `number` | Percentage of humidity |
| rain | `number` | Metric: `millimeters` <br> Imperial: `inches` |
| snow | `number` | Metric: `millimeters` <br> Imperial: `inches` |
| precipitation | `number` | Metric: `millimeters` <br> Imperial: `inches` <br> UK Met Office provider: `percent` |
#### Current weather
For the current weather object the following properties are required:
- humidity
- sunrise
- sunset
- temperature
- units
- weatherType
- windDirection
- windSpeed
#### Weather forecast
For the forecast weather object the following properties are required:
- date
- maxTemperature
- minTemperature
- rain
- units
- weatherType

View File

@@ -8,7 +8,8 @@
* MIT Licensed * MIT Licensed
* *
* This class is a provider for Dark Sky. * This class is a provider for Dark Sky.
* Note that the Dark Sky API does not provide rainfall. Instead it provides snowfall and precipitation probability * Note that the Dark Sky API does not provide rainfall. Instead it provides
* snowfall and precipitation probability
*/ */
WeatherProvider.register("darksky", { WeatherProvider.register("darksky", {
// Set the name of the provider. // Set the name of the provider.
@@ -98,7 +99,8 @@ WeatherProvider.register("darksky", {
weather.snow = 0; weather.snow = 0;
// The API will return centimeters if units is 'si' and will return inches for 'us' // The API will return centimeters if units is 'si' and will return inches for 'us'
// Note that the Dark Sky API does not provide rainfall. Instead it provides snowfall and precipitation probability // Note that the Dark Sky API does not provide rainfall.
// Instead it provides snowfall and precipitation probability
if (forecast.hasOwnProperty("precipAccumulation")) { if (forecast.hasOwnProperty("precipAccumulation")) {
if (this.config.units === "imperial" && !isNaN(forecast.precipAccumulation)) { if (this.config.units === "imperial" && !isNaN(forecast.precipAccumulation)) {
weather.snow = forecast.precipAccumulation; weather.snow = forecast.precipAccumulation;

View File

@@ -134,7 +134,7 @@ WeatherProvider.register("envcanada", {
// //
fetchData: function (url, method = "GET", data = null) { fetchData: function (url, method = "GET", data = null) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
var request = new XMLHttpRequest(); const request = new XMLHttpRequest();
request.open(method, url, true); request.open(method, url, true);
request.onreadystatechange = function () { request.onreadystatechange = function () {
if (this.readyState === 4) { if (this.readyState === 4) {
@@ -164,9 +164,7 @@ WeatherProvider.register("envcanada", {
// CORS errors when accessing EC // CORS errors when accessing EC
// //
getUrl() { getUrl() {
var path = "https://thingproxy.freeboard.io/fetch/https://dd.weather.gc.ca/citypage_weather/xml/" + this.config.provCode + "/" + this.config.siteCode + "_e.xml"; return "https://thingproxy.freeboard.io/fetch/https://dd.weather.gc.ca/citypage_weather/xml/" + this.config.provCode + "/" + this.config.siteCode + "_e.xml";
return path;
}, },
// //
@@ -232,7 +230,7 @@ WeatherProvider.register("envcanada", {
// Capture the sunrise and sunset values from EC data // Capture the sunrise and sunset values from EC data
// //
var sunList = ECdoc.querySelectorAll("siteData riseSet dateTime"); const sunList = ECdoc.querySelectorAll("siteData riseSet dateTime");
currentWeather.sunrise = moment(sunList[1].querySelector("timeStamp").textContent, "YYYYMMDDhhmmss"); currentWeather.sunrise = moment(sunList[1].querySelector("timeStamp").textContent, "YYYYMMDDhhmmss");
currentWeather.sunset = moment(sunList[3].querySelector("timeStamp").textContent, "YYYYMMDDhhmmss"); currentWeather.sunset = moment(sunList[3].querySelector("timeStamp").textContent, "YYYYMMDDhhmmss");
@@ -249,14 +247,14 @@ WeatherProvider.register("envcanada", {
const days = []; const days = [];
var weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
var foreBaseDates = ECdoc.querySelectorAll("siteData forecastGroup dateTime"); const foreBaseDates = ECdoc.querySelectorAll("siteData forecastGroup dateTime");
var baseDate = foreBaseDates[1].querySelector("timeStamp").textContent; const baseDate = foreBaseDates[1].querySelector("timeStamp").textContent;
weather.date = moment(baseDate, "YYYYMMDDhhmmss"); weather.date = moment(baseDate, "YYYYMMDDhhmmss");
var foreGroup = ECdoc.querySelectorAll("siteData forecastGroup forecast"); const foreGroup = ECdoc.querySelectorAll("siteData forecastGroup forecast");
// For simplicity, we will only accumulate precipitation and will not try to break out // For simplicity, we will only accumulate precipitation and will not try to break out
// rain vs snow accumulations // rain vs snow accumulations
@@ -288,9 +286,9 @@ WeatherProvider.register("envcanada", {
// where the next day's (aka Tomorrow's) forecast is located in the forecast array. // where the next day's (aka Tomorrow's) forecast is located in the forecast array.
// //
var nextDay = 0; let nextDay = 0;
var lastDay = 0; let lastDay = 0;
var currentTemp = ECdoc.querySelector("siteData currentConditions temperature").textContent; const currentTemp = ECdoc.querySelector("siteData currentConditions temperature").textContent;
// //
// If the first Element is Current Today, look at Current Today and Current Tonight for the current day. // If the first Element is Current Today, look at Current Today and Current Tonight for the current day.
@@ -356,10 +354,10 @@ WeatherProvider.register("envcanada", {
// iteration looking at the current Element and the next Element. // iteration looking at the current Element and the next Element.
// //
var lastDate = moment(baseDate, "YYYYMMDDhhmmss"); let lastDate = moment(baseDate, "YYYYMMDDhhmmss");
for (var stepDay = nextDay; stepDay < lastDay; stepDay += 2) { for (let stepDay = nextDay; stepDay < lastDay; stepDay += 2) {
var weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
// Add 1 to the date to reflect the current forecast day we are building // Add 1 to the date to reflect the current forecast day we are building
@@ -402,23 +400,23 @@ WeatherProvider.register("envcanada", {
// Get local timezone UTC offset so that each hourly time can be calculated properly // Get local timezone UTC offset so that each hourly time can be calculated properly
var baseHours = ECdoc.querySelectorAll("siteData hourlyForecastGroup dateTime"); const baseHours = ECdoc.querySelectorAll("siteData hourlyForecastGroup dateTime");
var hourOffset = baseHours[1].getAttribute("UTCOffset"); const hourOffset = baseHours[1].getAttribute("UTCOffset");
// //
// The EC hourly forecast is held in a 24-element array - Elements 0 to 23 - with Element 0 holding // The EC hourly forecast is held in a 24-element array - Elements 0 to 23 - with Element 0 holding
// the forecast for the next 'on the hour' timeslot. This means the array is a rolling 24 hours. // the forecast for the next 'on the hour' timeslot. This means the array is a rolling 24 hours.
// //
var hourGroup = ECdoc.querySelectorAll("siteData hourlyForecastGroup hourlyForecast"); const hourGroup = ECdoc.querySelectorAll("siteData hourlyForecastGroup hourlyForecast");
for (var stepHour = 0; stepHour < 24; stepHour += 1) { for (let stepHour = 0; stepHour < 24; stepHour += 1) {
var weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
// Determine local time by applying UTC offset to the forecast timestamp // Determine local time by applying UTC offset to the forecast timestamp
var foreTime = moment(hourGroup[stepHour].getAttribute("dateTimeUTC"), "YYYYMMDDhhmmss"); const foreTime = moment(hourGroup[stepHour].getAttribute("dateTimeUTC"), "YYYYMMDDhhmmss");
var currTime = foreTime.add(hourOffset, "hours"); const currTime = foreTime.add(hourOffset, "hours");
weather.date = moment(currTime, "X"); weather.date = moment(currTime, "X");
// Capture the temperature // Capture the temperature
@@ -427,7 +425,7 @@ WeatherProvider.register("envcanada", {
// Capture Likelihood of Precipitation (LOP) and unit-of-measure values // Capture Likelihood of Precipitation (LOP) and unit-of-measure values
var precipLOP = hourGroup[stepHour].querySelector("lop").textContent * 1.0; const precipLOP = hourGroup[stepHour].querySelector("lop").textContent * 1.0;
if (precipLOP > 0) { if (precipLOP > 0) {
weather.precipitation = precipLOP; weather.precipitation = precipLOP;
@@ -453,9 +451,9 @@ WeatherProvider.register("envcanada", {
// //
setMinMaxTemps(weather, foreGroup, today, fullDay, currentTemp) { setMinMaxTemps(weather, foreGroup, today, fullDay, currentTemp) {
var todayTemp = foreGroup[today].querySelector("temperatures temperature").textContent; const todayTemp = foreGroup[today].querySelector("temperatures temperature").textContent;
var todayClass = foreGroup[today].querySelector("temperatures temperature").getAttribute("class"); const todayClass = foreGroup[today].querySelector("temperatures temperature").getAttribute("class");
// //
// The following logic is largely aimed at accommodating the Current day's forecast whereby we // The following logic is largely aimed at accommodating the Current day's forecast whereby we
@@ -500,9 +498,9 @@ WeatherProvider.register("envcanada", {
} }
} }
var nextTemp = foreGroup[today + 1].querySelector("temperatures temperature").textContent; const nextTemp = foreGroup[today + 1].querySelector("temperatures temperature").textContent;
var nextClass = foreGroup[today + 1].querySelector("temperatures temperature").getAttribute("class"); const nextClass = foreGroup[today + 1].querySelector("temperatures temperature").getAttribute("class");
if (fullDay === true) { if (fullDay === true) {
if (nextClass === "low") { if (nextClass === "low") {
@@ -513,8 +511,6 @@ WeatherProvider.register("envcanada", {
weather.maxTemperature = this.convertTemp(nextTemp); weather.maxTemperature = this.convertTemp(nextTemp);
} }
} }
return;
}, },
// //
@@ -560,8 +556,6 @@ WeatherProvider.register("envcanada", {
weather.precipitation = foreGroup[today].querySelector("abbreviatedForecast pop").textContent; weather.precipitation = foreGroup[today].querySelector("abbreviatedForecast pop").textContent;
weather.precipitationUnits = foreGroup[today].querySelector("abbreviatedForecast pop").getAttribute("units"); weather.precipitationUnits = foreGroup[today].querySelector("abbreviatedForecast pop").getAttribute("units");
} }
return;
}, },
// //
@@ -577,6 +571,7 @@ WeatherProvider.register("envcanada", {
return temp; return temp;
} }
}, },
// //
// Convert km/h to mph // Convert km/h to mph
// //

View File

@@ -18,10 +18,10 @@ WeatherProvider.register("openweathermap", {
defaults: { defaults: {
apiVersion: "2.5", apiVersion: "2.5",
apiBase: "https://api.openweathermap.org/data/", apiBase: "https://api.openweathermap.org/data/",
weatherEndpoint: "", weatherEndpoint: "", // can be "onecall", "forecast" or "weather" (for current)
locationID: false, locationID: false,
location: false, location: false,
lat: 0, lat: 0, // the onecall endpoint needs lat / lon values, it doesn'T support the locationId
lon: 0, lon: 0,
apiKey: "" apiKey: ""
}, },
@@ -89,7 +89,7 @@ WeatherProvider.register("openweathermap", {
/** /**
* Overrides method for setting config to check if endpoint is correct for hourly * Overrides method for setting config to check if endpoint is correct for hourly
* *
* @param config * @param {object} config The configuration object
*/ */
setConfig(config) { setConfig(config) {
this.config = config; this.config = config;

View File

@@ -1,4 +1,4 @@
/* global WeatherProvider, WeatherObject, SunCalc */ /* global WeatherProvider, WeatherObject */
/* Magic Mirror /* Magic Mirror
* Module: Weather * Module: Weather
@@ -7,9 +7,8 @@
* By BuXXi https://github.com/buxxi * By BuXXi https://github.com/buxxi
* MIT Licensed * MIT Licensed
* *
* This class is a provider for SMHI (Sweden only). * This class is a provider for SMHI (Sweden only). Metric system is the only
* Note that SMHI doesn't provide sunrise and sundown, use SunCalc to calculate it. * supported unit.
* Metric system is the only supported unit.
*/ */
WeatherProvider.register("smhi", { WeatherProvider.register("smhi", {
providerName: "SMHI", providerName: "SMHI",
@@ -56,11 +55,11 @@ WeatherProvider.register("smhi", {
/** /**
* Overrides method for setting config with checks for the precipitationValue being unset or invalid * Overrides method for setting config with checks for the precipitationValue being unset or invalid
* *
* @param config * @param {object} config The configuration object
*/ */
setConfig(config) { setConfig(config) {
this.config = config; this.config = config;
if (!config.precipitationValue || ["pmin", "pmean", "pmedian", "pmax"].indexOf(config.precipitationValue) == -1) { if (!config.precipitationValue || ["pmin", "pmean", "pmedian", "pmax"].indexOf(config.precipitationValue) === -1) {
console.log("invalid or not set: " + config.precipitationValue); console.log("invalid or not set: " + config.precipitationValue);
config.precipitationValue = this.defaults.precipitationValue; config.precipitationValue = this.defaults.precipitationValue;
} }
@@ -69,7 +68,8 @@ WeatherProvider.register("smhi", {
/** /**
* Of all the times returned find out which one is closest to the current time, should be the first if the data isn't old. * Of all the times returned find out which one is closest to the current time, should be the first if the data isn't old.
* *
* @param times * @param {object[]} times Array of time objects
* @returns {object} The weatherdata closest to the current time
*/ */
getClosestToCurrentTime(times) { getClosestToCurrentTime(times) {
let now = moment(); let now = moment();
@@ -85,6 +85,8 @@ WeatherProvider.register("smhi", {
/** /**
* Get the forecast url for the configured coordinates * Get the forecast url for the configured coordinates
*
* @returns {string} the url for the specified coordinates
*/ */
getURL() { getURL() {
let lon = this.config.lon; let lon = this.config.lon;
@@ -97,25 +99,25 @@ WeatherProvider.register("smhi", {
* The returned units is always in metric system. * The returned units is always in metric system.
* Requires coordinates to determine if its daytime or nighttime to know which icon to use and also to set sunrise and sunset. * Requires coordinates to determine if its daytime or nighttime to know which icon to use and also to set sunrise and sunset.
* *
* @param weatherData * @param {object} weatherData Weatherdata to convert
* @param coordinates * @param {object} coordinates Coordinates of the locations of the weather
* @param weatherData * @returns {WeatherObject} The converted weatherdata at the specified location
* @param coordinates
*/ */
convertWeatherDataToObject(weatherData, coordinates) { convertWeatherDataToObject(weatherData, coordinates) {
let currentWeather = new WeatherObject("metric", "metric", "metric"); //Weather data is only for Sweden and nobody in Sweden would use imperial // Weather data is only for Sweden and nobody in Sweden would use imperial
let currentWeather = new WeatherObject("metric", "metric", "metric");
currentWeather.date = moment(weatherData.validTime); currentWeather.date = moment(weatherData.validTime);
let times = SunCalc.getTimes(currentWeather.date.toDate(), coordinates.lat, coordinates.lon); currentWeather.updateSunTime(coordinates.lat, coordinates.lon);
currentWeather.sunrise = moment(times.sunrise, "X");
currentWeather.sunset = moment(times.sunset, "X");
currentWeather.humidity = this.paramValue(weatherData, "r"); currentWeather.humidity = this.paramValue(weatherData, "r");
currentWeather.temperature = this.paramValue(weatherData, "t"); currentWeather.temperature = this.paramValue(weatherData, "t");
currentWeather.windSpeed = this.paramValue(weatherData, "ws"); currentWeather.windSpeed = this.paramValue(weatherData, "ws");
currentWeather.windDirection = this.paramValue(weatherData, "wd"); currentWeather.windDirection = this.paramValue(weatherData, "wd");
currentWeather.weatherType = this.convertWeatherType(this.paramValue(weatherData, "Wsymb2"), this.isDayTime(currentWeather)); currentWeather.weatherType = this.convertWeatherType(this.paramValue(weatherData, "Wsymb2"), currentWeather.isDayTime());
//Determine the precipitation amount and category and update the weatherObject with it, the valuetype to use can be configured or uses median as default. // Determine the precipitation amount and category and update the
// weatherObject with it, the valuetype to use can be configured or uses
// median as default.
let precipitationValue = this.paramValue(weatherData, this.config.precipitationValue); let precipitationValue = this.paramValue(weatherData, this.config.precipitationValue);
switch (this.paramValue(weatherData, "pcat")) { switch (this.paramValue(weatherData, "pcat")) {
// 0 = No precipitation // 0 = No precipitation
@@ -143,10 +145,9 @@ WeatherProvider.register("smhi", {
/** /**
* Takes all of the data points and converts it to one WeatherObject per day. * Takes all of the data points and converts it to one WeatherObject per day.
* *
* @param allWeatherData * @param {object[]} allWeatherData Array of weatherdata
* @param coordinates * @param {object} coordinates Coordinates of the locations of the weather
* @param allWeatherData * @returns {WeatherObject[]} Array of weatherobjects
* @param coordinates
*/ */
convertWeatherDataGroupedByDay(allWeatherData, coordinates) { convertWeatherDataGroupedByDay(allWeatherData, coordinates) {
let currentWeather; let currentWeather;
@@ -170,7 +171,7 @@ WeatherProvider.register("smhi", {
} }
//Keep track of what icons has been used for each hour of daytime and use the middle one for the forecast //Keep track of what icons has been used for each hour of daytime and use the middle one for the forecast
if (this.isDayTime(weatherObject)) { if (weatherObject.isDayTime()) {
dayWeatherTypes.push(weatherObject.weatherType); dayWeatherTypes.push(weatherObject.weatherType);
} }
if (dayWeatherTypes.length > 0) { if (dayWeatherTypes.length > 0) {
@@ -191,37 +192,31 @@ WeatherProvider.register("smhi", {
}, },
/** /**
* Resolve coordinates from the response data (probably preferably to use this if it's not matching the config values exactly) * Resolve coordinates from the response data (probably preferably to use
* this if it's not matching the config values exactly)
* *
* @param data * @param {object} data Response data from the weather service
* @returns {{lon, lat}} the lat/long coordinates of the data
*/ */
resolveCoordinates(data) { resolveCoordinates(data) {
return { lat: data.geometry.coordinates[0][1], lon: data.geometry.coordinates[0][0] }; return { lat: data.geometry.coordinates[0][1], lon: data.geometry.coordinates[0][0] };
}, },
/**
* Checks if the weatherObject is at dayTime.
*
* @param weatherObject
*/
isDayTime(weatherObject) {
return weatherObject.date.isBetween(weatherObject.sunrise, weatherObject.sunset, undefined, "[]");
},
/** /**
* The distance between the data points is increasing in the data the more distant the prediction is. * The distance between the data points is increasing in the data the more distant the prediction is.
* Find these gaps and fill them with the previous hours data to make the data returned a complete set. * Find these gaps and fill them with the previous hours data to make the data returned a complete set.
* *
* @param data * @param {object[]} data Response data from the weather service
* @returns {object[]} Given data with filled gaps
*/ */
fillInGaps(data) { fillInGaps(data) {
let result = []; let result = [];
for (const i = 1; i < data.length; i++) { for (let i = 1; i < data.length; i++) {
let to = moment(data[i].validTime); let to = moment(data[i].validTime);
let from = moment(data[i - 1].validTime); let from = moment(data[i - 1].validTime);
let hours = moment.duration(to.diff(from)).asHours(); let hours = moment.duration(to.diff(from)).asHours();
// For each hour add a datapoint but change the validTime // For each hour add a datapoint but change the validTime
for (const j = 0; j < hours; j++) { for (let j = 0; j < hours; j++) {
let current = Object.assign({}, data[i]); let current = Object.assign({}, data[i]);
current.validTime = from.clone().add(j, "hours").toISOString(); current.validTime = from.clone().add(j, "hours").toISOString();
result.push(current); result.push(current);
@@ -231,84 +226,81 @@ WeatherProvider.register("smhi", {
}, },
/** /**
* Helper method to fetch a property from the returned data set. * Helper method to get a property from the returned data set.
* The returned values is an array with always one value in it.
* *
* @param currentWeatherData * @param {object} currentWeatherData Weatherdata to get from
* @param name * @param {string} name The name of the property
* @param currentWeatherData * @returns {*} The value of the property in the weatherdata
* @param name
*/ */
paramValue(currentWeatherData, name) { paramValue(currentWeatherData, name) {
return currentWeatherData.parameters.filter((p) => p.name == name).flatMap((p) => p.values)[0]; return currentWeatherData.parameters.filter((p) => p.name === name).flatMap((p) => p.values)[0];
}, },
/** /**
* Map the icon value from SHMI to an icon that MagicMirror understands. * Map the icon value from SMHI to an icon that MagicMirror understands.
* Uses different icons depending if its daytime or nighttime. * Uses different icons depending if its daytime or nighttime.
* SHMI's description of what the numeric value means is the comment after the case. * SMHI's description of what the numeric value means is the comment after the case.
* *
* @param input * @param {number} input The SMHI icon value
* @param isDayTime * @param {boolean} isDayTime True if the icon should be for daytime, false for nighttime
* @param input * @returns {string} The icon name for the MagicMirror
* @param isDayTime
*/ */
convertWeatherType(input, isDayTime) { convertWeatherType(input, isDayTime) {
switch (input) { switch (input) {
case 1: case 1:
return isDayTime ? "day-sunny" : "night-clear"; // Clear sky return isDayTime ? "day-sunny" : "night-clear"; // Clear sky
case 2: case 2:
return isDayTime ? "day-sunny-overcast" : "night-partly-cloudy"; //Nearly clear sky return isDayTime ? "day-sunny-overcast" : "night-partly-cloudy"; // Nearly clear sky
case 3: case 3:
return isDayTime ? "day-cloudy" : "night-cloudy"; //Variable cloudiness return isDayTime ? "day-cloudy" : "night-cloudy"; // Variable cloudiness
case 4: case 4:
return isDayTime ? "day-cloudy" : "night-cloudy"; //Halfclear sky return isDayTime ? "day-cloudy" : "night-cloudy"; // Halfclear sky
case 5: case 5:
return "cloudy"; //Cloudy sky return "cloudy"; // Cloudy sky
case 6: case 6:
return "cloudy"; //Overcast return "cloudy"; // Overcast
case 7: case 7:
return "fog"; //Fog return "fog"; // Fog
case 8: case 8:
return "showers"; //Light rain showers return "showers"; // Light rain showers
case 9: case 9:
return "showers"; //Moderate rain showers return "showers"; // Moderate rain showers
case 10: case 10:
return "showers"; //Heavy rain showers return "showers"; // Heavy rain showers
case 11: case 11:
return "thunderstorm"; //Thunderstorm return "thunderstorm"; // Thunderstorm
case 12: case 12:
return "sleet"; //Light sleet showers return "sleet"; // Light sleet showers
case 13: case 13:
return "sleet"; //Moderate sleet showers return "sleet"; // Moderate sleet showers
case 14: case 14:
return "sleet"; //Heavy sleet showers return "sleet"; // Heavy sleet showers
case 15: case 15:
return "snow"; //Light snow showers return "snow"; // Light snow showers
case 16: case 16:
return "snow"; //Moderate snow showers return "snow"; // Moderate snow showers
case 17: case 17:
return "snow"; //Heavy snow showers return "snow"; // Heavy snow showers
case 18: case 18:
return "rain"; //Light rain return "rain"; // Light rain
case 19: case 19:
return "rain"; //Moderate rain return "rain"; // Moderate rain
case 20: case 20:
return "rain"; //Heavy rain return "rain"; // Heavy rain
case 21: case 21:
return "thunderstorm"; //Thunder return "thunderstorm"; // Thunder
case 22: case 22:
return "sleet"; // Light sleet return "sleet"; // Light sleet
case 23: case 23:
return "sleet"; //Moderate sleet return "sleet"; // Moderate sleet
case 24: case 24:
return "sleet"; // Heavy sleet return "sleet"; // Heavy sleet
case 25: case 25:
return "snow"; // Light snowfall return "snow"; // Light snowfall
case 26: case 26:
return "snow"; //Moderate snowfall return "snow"; // Moderate snowfall
case 27: case 27:
return "snow"; //Heavy snowfall return "snow"; // Heavy snowfall
default: default:
return ""; return "";
} }

View File

@@ -1,4 +1,4 @@
/* global WeatherProvider, WeatherObject, SunCalc */ /* global WeatherProvider, WeatherObject */
/* Magic Mirror /* Magic Mirror
* Module: Weather * Module: Weather
@@ -116,9 +116,7 @@ WeatherProvider.register("ukmetoffice", {
} }
// determine the sunrise/sunset times - not supplied in UK Met Office data // determine the sunrise/sunset times - not supplied in UK Met Office data
let times = this.calcAstroData(location); currentWeather.updateSunTime(location.lat, location.lon);
currentWeather.sunrise = times[0];
currentWeather.sunset = times[1];
return currentWeather; return currentWeather;
}, },
@@ -154,20 +152,6 @@ WeatherProvider.register("ukmetoffice", {
return days; return days;
}, },
/*
* calculate the astronomical data
*/
calcAstroData(location) {
const sunTimes = [];
// determine the sunrise/sunset times
let times = SunCalc.getTimes(new Date(), location.lat, location.lon);
sunTimes.push(moment(times.sunrise, "X"));
sunTimes.push(moment(times.sunset, "X"));
return sunTimes;
},
/* /*
* Convert the Met Office icons to a more usable name. * Convert the Met Office icons to a more usable name.
*/ */
@@ -248,16 +232,16 @@ WeatherProvider.register("ukmetoffice", {
return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null; return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null;
}, },
/* /**
* Generates an url with api parameters based on the config. * Generates an url with api parameters based on the config.
* *
* return String - URL params. * @param {string} forecastType daily or 3hourly forecast
* @returns {string} url
*/ */
getParams(forecastType) { getParams(forecastType) {
let params = "?"; let params = "?";
params += "res=" + forecastType; params += "res=" + forecastType;
params += "&key=" + this.config.apiKey; params += "&key=" + this.config.apiKey;
return params; return params;
} }
}); });

View File

@@ -1,3 +1,5 @@
/* global WeatherProvider, WeatherObject */
/* Magic Mirror /* Magic Mirror
* Module: Weather * Module: Weather
* *
@@ -11,9 +13,8 @@
* Hourly data for next 2 days ("hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-hourly.pdf * Hourly data for next 2 days ("hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-hourly.pdf
* 3-hourly data for the next 7 days ("3hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-3-hourly.pdf * 3-hourly data for the next 7 days ("3hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-3-hourly.pdf
* Daily data for the next 7 days ("daily") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-daily.pdf * Daily data for the next 7 days ("daily") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-daily.pdf
*/ *
* NOTES
/* NOTES
* This provider requires longitude/latitude coordinates, rather than a location ID (as with the previous Met Office provider) * This provider requires longitude/latitude coordinates, rather than a location ID (as with the previous Met Office provider)
* Provide the following in your config.js file: * Provide the following in your config.js file:
* weatherProvider: "ukmetofficedatahub", * weatherProvider: "ukmetofficedatahub",
@@ -69,13 +70,11 @@ WeatherProvider.register("ukmetofficedatahub", {
// For DataHub requests, the API key/secret are sent in the headers rather than as query strings. // For DataHub requests, the API key/secret are sent in the headers rather than as query strings.
// Headers defined according to Data Hub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api) // Headers defined according to Data Hub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api)
getHeaders() { getHeaders() {
let headers = { return {
accept: "application/json", accept: "application/json",
"x-ibm-client-id": this.config.apiKey, "x-ibm-client-id": this.config.apiKey,
"x-ibm-client-secret": this.config.apiSecret "x-ibm-client-secret": this.config.apiSecret
}; };
return headers;
}, },
// Fetch data using supplied URL and request headers // Fetch data using supplied URL and request headers
@@ -91,7 +90,7 @@ WeatherProvider.register("ukmetofficedatahub", {
this.fetchWeather(this.getUrl("hourly"), this.getHeaders()) this.fetchWeather(this.getUrl("hourly"), this.getHeaders())
.then((data) => { .then((data) => {
// Check data is useable // Check data is useable
if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length == 0) { if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length === 0) {
// Did not receive usable new data. // Did not receive usable new data.
// Maybe this needs a better check? // Maybe this needs a better check?
Log.error("Possibly bad current/hourly data?"); Log.error("Possibly bad current/hourly data?");
@@ -125,7 +124,7 @@ WeatherProvider.register("ukmetofficedatahub", {
let nowUtc = moment.utc(); let nowUtc = moment.utc();
// Find hour that contains the current time // Find hour that contains the current time
for (hour in forecastDataHours) { for (let hour in forecastDataHours) {
let forecastTime = moment.utc(forecastDataHours[hour].time); let forecastTime = moment.utc(forecastDataHours[hour].time);
if (nowUtc.isSameOrAfter(forecastTime) && nowUtc.isBefore(moment(forecastTime.add(1, "h")))) { if (nowUtc.isSameOrAfter(forecastTime) && nowUtc.isBefore(moment(forecastTime.add(1, "h")))) {
currentWeather.date = forecastTime; currentWeather.date = forecastTime;
@@ -148,11 +147,9 @@ WeatherProvider.register("ukmetofficedatahub", {
} }
// Determine the sunrise/sunset times - (still) not supplied in UK Met Office data // Determine the sunrise/sunset times - (still) not supplied in UK Met Office data
// Passes {longitude, latitude, height} to calcAstroData // Passes {longitude, latitude} to SunCalc, could pass height to, but
// Could just pass lat/long from this.config, but returned data from MO also contains elevation // SunCalc.getTimes doesnt take that into account
let times = this.calcAstroData(currentWeatherData.features[0].geometry.coordinates); currentWeather.updateSunTime(this.config.lat, this.config.lon);
currentWeather.sunrise = times[0];
currentWeather.sunset = times[1];
return currentWeather; return currentWeather;
}, },
@@ -162,7 +159,7 @@ WeatherProvider.register("ukmetofficedatahub", {
this.fetchWeather(this.getUrl("daily"), this.getHeaders()) this.fetchWeather(this.getUrl("daily"), this.getHeaders())
.then((data) => { .then((data) => {
// Check data is useable // Check data is useable
if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length == 0) { if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length === 0) {
// Did not receive usable new data. // Did not receive usable new data.
// Maybe this needs a better check? // Maybe this needs a better check?
Log.error("Possibly bad forecast data?"); Log.error("Possibly bad forecast data?");
@@ -196,7 +193,7 @@ WeatherProvider.register("ukmetofficedatahub", {
let today = moment.utc().startOf("date"); let today = moment.utc().startOf("date");
// Go through each day in the forecasts // Go through each day in the forecasts
for (day in forecastDataDays) { for (let day in forecastDataDays) {
const forecastWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh); const forecastWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
// Get date of forecast // Get date of forecast
@@ -221,7 +218,6 @@ WeatherProvider.register("ukmetofficedatahub", {
// Pass on full details so they can be used in custom templates // Pass on full details so they can be used in custom templates
// Note the units of the supplied data when using this (see top of file) // Note the units of the supplied data when using this (see top of file)
forecastWeather.rawData = forecastDataDays[day]; forecastWeather.rawData = forecastDataDays[day];
dailyForecasts.push(forecastWeather); dailyForecasts.push(forecastWeather);
@@ -236,18 +232,6 @@ WeatherProvider.register("ukmetofficedatahub", {
this.fetchedLocationName = name; this.fetchedLocationName = name;
}, },
// Calculate sunrise/sunset times
calcAstroData(location) {
const sunTimes = [];
// Careful to pass values to SunCalc in correct order (latitude, longitude, elevation)
let times = SunCalc.getTimes(new Date(), location[1], location[0], location[2]);
sunTimes.push(moment(times.sunrise, "X"));
sunTimes.push(moment(times.sunset, "X"));
return sunTimes;
},
// Convert temperatures to Fahrenheit (from degrees C), if required // Convert temperatures to Fahrenheit (from degrees C), if required
convertTemp(tempInC) { convertTemp(tempInC) {
return this.config.tempUnits === "imperial" ? (tempInC * 9) / 5 + 32 : tempInC; return this.config.tempUnits === "imperial" ? (tempInC * 9) / 5 + 32 : tempInC;
@@ -258,11 +242,11 @@ WeatherProvider.register("ukmetofficedatahub", {
// To use kilometres per hour, use "kph" // To use kilometres per hour, use "kph"
// Else assumed imperial and the value is returned in miles per hour (a Met Office user is likely to be UK-based) // Else assumed imperial and the value is returned in miles per hour (a Met Office user is likely to be UK-based)
convertWindSpeed(windInMpS) { convertWindSpeed(windInMpS) {
if (this.config.windUnits == "mps") { if (this.config.windUnits === "mps") {
return windInMpS; return windInMpS;
} }
if (this.config.windUnits == "kph" || this.config.windUnits == "metric" || this.config.useKmh) { if (this.config.windUnits === "kph" || this.config.windUnits === "metric" || this.config.useKmh) {
return windInMpS * 3.6; return windInMpS * 3.6;
} }

View File

@@ -7,7 +7,8 @@
* By Andrew Pometti * By Andrew Pometti
* MIT Licensed * MIT Licensed
* *
* This class is a provider for Weatherbit, based on Nicholas Hubbard's class for Dark Sky & Vince Peri's class for Weather.gov. * This class is a provider for Weatherbit, based on Nicholas Hubbard's class
* for Dark Sky & Vince Peri's class for Weather.gov.
*/ */
WeatherProvider.register("weatherbit", { WeatherProvider.register("weatherbit", {
// Set the name of the provider. // Set the name of the provider.
@@ -89,7 +90,6 @@ WeatherProvider.register("weatherbit", {
currentWeather.windSpeed = parseFloat(currentWeatherData.data[0].wind_spd); currentWeather.windSpeed = parseFloat(currentWeatherData.data[0].wind_spd);
currentWeather.windDirection = currentWeatherData.data[0].wind_dir; currentWeather.windDirection = currentWeatherData.data[0].wind_dir;
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.data[0].weather.icon); currentWeather.weatherType = this.convertWeatherType(currentWeatherData.data[0].weather.icon);
Log.log("Wx Icon: " + currentWeatherData.data[0].weather.icon);
currentWeather.sunrise = moment(currentWeatherData.data[0].sunrise, "HH:mm").add(tzOffset, "m"); currentWeather.sunrise = moment(currentWeatherData.data[0].sunrise, "HH:mm").add(tzOffset, "m");
currentWeather.sunset = moment(currentWeatherData.data[0].sunset, "HH:mm").add(tzOffset, "m"); currentWeather.sunset = moment(currentWeatherData.data[0].sunset, "HH:mm").add(tzOffset, "m");

View File

@@ -1,4 +1,4 @@
/* global WeatherProvider, WeatherObject, SunCalc */ /* global WeatherProvider, WeatherObject */
/* Magic Mirror /* Magic Mirror
* Module: Weather * Module: Weather
@@ -21,7 +21,7 @@ WeatherProvider.register("weathergov", {
// Set the default config properties that is specific to this provider // Set the default config properties that is specific to this provider
defaults: { defaults: {
apiBase: "https://api.weatherbit.io/v2.0", apiBase: "https://api.weather.gov/points/",
weatherEndpoint: "/forecast", weatherEndpoint: "/forecast",
lat: 0, lat: 0,
lon: 0 lon: 0
@@ -129,7 +129,12 @@ WeatherProvider.register("weathergov", {
.finally(() => { .finally(() => {
// excellent, let's fetch some actual wx data // excellent, let's fetch some actual wx data
this.configURLs = true; this.configURLs = true;
this.fetchCurrentWeather(); // handle 'forecast' config, fall back to 'current'
if (config.type === "forecast") {
this.fetchWeatherForecast();
} else {
this.fetchCurrentWeather();
}
}); });
}, },
@@ -153,18 +158,11 @@ WeatherProvider.register("weathergov", {
currentWeather.precipitation = this.convertLength(currentWeatherData.precipitationLastHour.value); currentWeather.precipitation = this.convertLength(currentWeatherData.precipitationLastHour.value);
currentWeather.feelsLikeTemp = this.convertTemp(currentWeatherData.heatIndex.value); currentWeather.feelsLikeTemp = this.convertTemp(currentWeatherData.heatIndex.value);
let isDaytime = true;
if (currentWeatherData.icon.includes("day")) {
isDaytime = true;
} else {
isDaytime = false;
}
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.textDescription, isDaytime);
// determine the sunrise/sunset times - not supplied in weather.gov data // determine the sunrise/sunset times - not supplied in weather.gov data
let times = this.calcAstroData(this.config.lat, this.config.lon); currentWeather.updateSunTime(this.config.lat, this.config.lon);
currentWeather.sunrise = times[0];
currentWeather.sunset = times[1]; // update weatherType
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.textDescription, currentWeather.isDayTime());
return currentWeather; return currentWeather;
}, },
@@ -267,20 +265,6 @@ WeatherProvider.register("weathergov", {
} }
}, },
/*
* Calculate the astronomical data
*/
calcAstroData(lat, lon) {
const sunTimes = [];
// determine the sunrise/sunset times
let times = SunCalc.getTimes(new Date(), lat, lon);
sunTimes.push(moment(times.sunrise, "X"));
sunTimes.push(moment(times.sunset, "X"));
return sunTimes;
},
/* /*
* Convert the icons to a more usable name. * Convert the icons to a more usable name.
*/ */

View File

@@ -132,10 +132,6 @@ Module.register("weather", {
getTemplateData: function () { getTemplateData: function () {
const forecast = this.weatherProvider.weatherForecast(); const forecast = this.weatherProvider.weatherForecast();
if (this.config.ignoreToday) {
forecast.splice(0, 1);
}
return { return {
config: this.config, config: this.config,
current: this.weatherProvider.currentWeather(), current: this.weatherProvider.currentWeather(),

View File

@@ -1,3 +1,5 @@
/* global SunCalc */
/* Magic Mirror /* Magic Mirror
* Module: Weather * Module: Weather
* *
@@ -10,6 +12,14 @@
* As soon as we start implementing the forecast, mode properties will be added. * As soon as we start implementing the forecast, mode properties will be added.
*/ */
class WeatherObject { class WeatherObject {
/**
* Constructor for a WeatherObject
*
* @param {string} units what units to use, "imperial" or "metric"
* @param {string} tempUnits what tempunits to use
* @param {string} windUnits what windunits to use
* @param {boolean} useKmh use kmh if true, mps if false
*/
constructor(units, tempUnits, windUnits, useKmh) { constructor(units, tempUnits, windUnits, useKmh) {
this.units = units; this.units = units;
this.tempUnits = tempUnits; this.tempUnits = tempUnits;
@@ -80,8 +90,7 @@ class WeatherObject {
} }
kmhWindSpeed() { kmhWindSpeed() {
const windInKmh = this.windUnits === "imperial" ? this.windSpeed * 1.609344 : (this.windSpeed * 60 * 60) / 1000; return this.windUnits === "imperial" ? this.windSpeed * 1.609344 : (this.windSpeed * 60 * 60) / 1000;
return windInKmh;
} }
nextSunAction() { nextSunAction() {
@@ -113,4 +122,33 @@ class WeatherObject {
return this.tempUnits === "imperial" ? feelsLike : ((feelsLike - 32) * 5) / 9; return this.tempUnits === "imperial" ? feelsLike : ((feelsLike - 32) * 5) / 9;
} }
/**
* Checks if the weatherObject is at dayTime.
*
* @returns {boolean} true if it is at dayTime
*/
isDayTime() {
return this.date.isBetween(this.sunrise, this.sunset, undefined, "[]");
}
/**
* Update the sunrise / sunset time depending on the location. This can be
* used if your provider doesnt provide that data by itself. Then SunCalc
* is used here to calculate them according to the location.
*
* @param {number} lat latitude
* @param {number} lon longitude
*/
updateSunTime(lat, lon) {
let now = !this.date ? new Date() : this.date.toDate();
let times = SunCalc.getTimes(now, lat, lon);
this.sunrise = moment(times.sunrise, "X");
this.sunset = moment(times.sunset, "X");
}
}
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = WeatherObject;
} }

View File

@@ -1,3 +1,5 @@
/* eslint-disable */
/* Magic Mirror /* Magic Mirror
* Module: WeatherForecast * Module: WeatherForecast
* *

4555
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "magicmirror", "name": "magicmirror",
"version": "2.16.0", "version": "2.17.0",
"description": "The open source modular smart mirror platform.", "description": "The open source modular smart mirror platform.",
"main": "js/electron.js", "main": "js/electron.js",
"scripts": { "scripts": {
@@ -12,16 +12,17 @@
"postinstall": "npm run install-fonts && echo \"MagicMirror installation finished successfully! \n\"", "postinstall": "npm run install-fonts && echo \"MagicMirror installation finished successfully! \n\"",
"test": "NODE_ENV=test jest -i --forceExit", "test": "NODE_ENV=test jest -i --forceExit",
"test:coverage": "NODE_ENV=test nyc --reporter=lcov --reporter=text jest -i --forceExit", "test:coverage": "NODE_ENV=test nyc --reporter=lcov --reporter=text jest -i --forceExit",
"test:electron": "NODE_ENV=test jest --selectProjects electron -i --forceExit",
"test:e2e": "NODE_ENV=test jest --selectProjects e2e -i --forceExit", "test:e2e": "NODE_ENV=test jest --selectProjects e2e -i --forceExit",
"test:unit": "NODE_ENV=test jest --selectProjects unit -i --forceExit", "test:unit": "NODE_ENV=test jest --selectProjects unit -i --forceExit",
"test:prettier": "prettier . --check", "test:prettier": "prettier . --check",
"test:js": "eslint js/**/*.js modules/default/**/*.js clientonly/*.js serveronly/*.js translations/*.js vendor/*.js tests/**/*.js config/* --config .eslintrc.json --quiet", "test:js": "eslint 'js/**/*.js' 'modules/default/**/*.js' 'clientonly/*.js' 'serveronly/*.js' 'translations/*.js' 'vendor/*.js' 'tests/**/*.js' 'config/*' --config .eslintrc.json",
"test:css": "stylelint css/main.css modules/default/**/*.css --config .stylelintrc.json", "test:css": "stylelint 'css/main.css' 'fonts/*.css' 'modules/default/**/*.css' 'vendor/*.css' --config .stylelintrc.json",
"test:calendar": "node ./modules/default/calendar/debug.js", "test:calendar": "node ./modules/default/calendar/debug.js",
"config:check": "node js/check_config.js", "config:check": "node js/check_config.js",
"lint:prettier": "prettier . --write", "lint:prettier": "prettier . --write",
"lint:js": "eslint js/**/*.js modules/default/**/*.js clientonly/*.js serveronly/*.js translations/*.js vendor/*.js tests/**/*.js config/* --config .eslintrc.json --fix", "lint:js": "eslint 'js/**/*.js' 'modules/default/**/*.js' 'clientonly/*.js' 'serveronly/*.js' 'translations/*.js' 'vendor/*.js' 'tests/**/*.js' 'config/*' --config .eslintrc.json --fix",
"lint:css": "stylelint css/main.css modules/default/**/*.css --config .stylelintrc.json --fix", "lint:css": "stylelint 'css/main.css' 'fonts/*.css' 'modules/default/**/*.css' 'vendor/*.css' --config .stylelintrc.json --fix",
"lint:staged": "pretty-quick --staged", "lint:staged": "pretty-quick --staged",
"prepare": "[ -f node_modules/.bin/husky ] && husky install || echo no husky installed." "prepare": "[ -f node_modules/.bin/husky ] && husky install || echo no husky installed."
}, },
@@ -46,32 +47,33 @@
"homepage": "https://magicmirror.builders", "homepage": "https://magicmirror.builders",
"devDependencies": { "devDependencies": {
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-jsdoc": "^35.4.0", "eslint-plugin-jest": "^24.4.2",
"eslint-plugin-prettier": "^3.4.0", "eslint-plugin-jsdoc": "^36.1.0",
"eslint-plugin-jest": "^24.3.6", "eslint-plugin-prettier": "^4.0.0",
"express-basic-auth": "^1.2.0", "express-basic-auth": "^1.2.0",
"husky": "^6.0.0", "husky": "^7.0.2",
"jest": "27.0.5", "jest": "^27.2.2",
"jsdom": "^16.6.0", "jsdom": "^17.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"prettier": "^2.3.1", "prettier": "^2.4.1",
"pretty-quick": "^3.1.1", "pretty-quick": "^3.1.1",
"sinon": "^11.1.1", "sinon": "^11.1.2",
"spectron": "^13.0.0", "spectron": "^15.0.0",
"stylelint": "^13.13.1", "stylelint": "^13.13.1",
"stylelint-config-prettier": "^8.0.2", "stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^22.0.0", "stylelint-config-standard": "^22.0.0",
"stylelint-prettier": "^1.2.0" "stylelint-prettier": "^1.2.0",
"suncalc": "^1.8.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"electron": "^11.4.9" "electron": "^13.5.0"
}, },
"dependencies": { "dependencies": {
"colors": "^1.4.0", "colors": "^1.4.0",
"console-stamp": "^3.0.2", "console-stamp": "^3.0.3",
"digest-fetch": "^1.2.0", "digest-fetch": "^1.2.1",
"eslint": "^7.29.0", "eslint": "^7.32.0",
"express": "^4.17.1", "express": "^4.17.1",
"express-ipfilter": "^1.2.0", "express-ipfilter": "^1.2.0",
"feedme": "^2.0.2", "feedme": "^2.0.2",
@@ -79,10 +81,9 @@
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"module-alias": "^2.2.2", "module-alias": "^2.2.2",
"moment": "^2.29.1", "moment": "^2.29.1",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.5",
"node-ical": "^0.13.0", "node-ical": "^0.13.0",
"simple-git": "^2.40.0", "socket.io": "^4.2.0"
"socket.io": "^4.1.2"
}, },
"_moduleAliases": { "_moduleAliases": {
"node_helper": "js/node_helper.js", "node_helper": "js/node_helper.js",
@@ -96,6 +97,9 @@
"projects": [ "projects": [
{ {
"displayName": "unit", "displayName": "unit",
"moduleNameMapper": {
"logger": "<rootDir>/js/logger.js"
},
"testMatch": [ "testMatch": [
"**/tests/unit/**/*.[jt]s?(x)" "**/tests/unit/**/*.[jt]s?(x)"
], ],
@@ -103,14 +107,31 @@
"<rootDir>/tests/unit/mocks" "<rootDir>/tests/unit/mocks"
] ]
}, },
{
"displayName": "electron",
"testMatch": [
"**/tests/electron/**/*.[jt]s?(x)"
],
"testPathIgnorePatterns": [
"<rootDir>/tests/electron/modules/mocks",
"<rootDir>/tests/electron/global-setup.js",
"<rootDir>/tests/electron/modules/basic-auth.js"
]
},
{ {
"displayName": "e2e", "displayName": "e2e",
"setupFilesAfterEnv": [
"<rootDir>/tests/e2e/mock-console.js"
],
"testMatch": [ "testMatch": [
"**/tests/e2e/**/*.[jt]s?(x)" "**/tests/e2e/**/*.[jt]s?(x)"
], ],
"modulePaths": [
"<rootDir>/js/"
],
"testPathIgnorePatterns": [ "testPathIgnorePatterns": [
"<rootDir>/tests/e2e/modules/mocks", "<rootDir>/tests/e2e/global-setup.js",
"<rootDir>/tests/e2e/global-setup.js" "<rootDir>/tests/e2e/mock-console.js"
] ]
} }
] ]

21
tests/configs/default.js Normal file
View File

@@ -0,0 +1,21 @@
/* Magic Mirror Test default config for modules
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
exports.configFactory = function (options) {
return Object.assign(
{
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false
}
},
modules: []
},
options
);
};

View File

@@ -3,22 +3,9 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080, ipWhitelist: []
ipWhitelist: [], });
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: []
};
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -3,22 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory();
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: []
};
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -4,19 +4,6 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
module: "alert", module: "alert",

View File

@@ -3,19 +3,8 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
@@ -35,7 +24,7 @@ let config = {
} }
} }
] ]
}; });
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -3,19 +3,8 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
@@ -36,7 +25,7 @@ let config = {
} }
} }
] ]
}; });
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -3,19 +3,8 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
@@ -35,7 +24,7 @@ let config = {
} }
} }
] ]
}; });
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -3,19 +3,8 @@
* By Rejas * By Rejas
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
@@ -35,7 +24,7 @@ let config = {
} }
} }
] ]
}; });
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -3,19 +3,8 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
@@ -31,7 +20,7 @@ let config = {
} }
} }
] ]
}; });
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -5,19 +5,8 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
@@ -38,7 +27,7 @@ let config = {
} }
} }
] ]
}; });
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -3,19 +3,8 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
@@ -33,7 +22,7 @@ let config = {
} }
} }
] ]
}; });
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -3,19 +3,8 @@
* By Rejas * By Rejas
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
@@ -32,7 +21,7 @@ let config = {
} }
} }
] ]
}; });
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -4,18 +4,7 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {

View File

@@ -4,19 +4,6 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
module: "clock", module: "clock",

View File

@@ -3,19 +3,6 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
module: "clock", module: "clock",

View File

@@ -4,18 +4,7 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {

View File

@@ -4,18 +4,7 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {

View File

@@ -0,0 +1,23 @@
/* Magic Mirror Test config for default clock module
*
* By Johan Hammar
* MIT Licensed.
*/
let config = {
timeFormat: 12,
modules: [
{
module: "clock",
position: "middle_center",
config: {
showTime: false
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}

View File

@@ -4,18 +4,7 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {

View File

@@ -4,18 +4,8 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "es", language: "es",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {

View File

@@ -4,18 +4,7 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "es", language: "es",
timeFormat: 24,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {

View File

@@ -4,18 +4,8 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "es", language: "es",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {

View File

@@ -5,18 +5,8 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "es", language: "es",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {

View File

@@ -4,18 +4,7 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {

View File

@@ -4,18 +4,7 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {

View File

@@ -4,18 +4,7 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {

View File

@@ -4,18 +4,7 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {

View File

@@ -4,22 +4,6 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
fullscreen: false,
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
module: "helloworld", module: "helloworld",

View File

@@ -4,19 +4,6 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
module: "helloworld", module: "helloworld",

View File

@@ -4,19 +4,6 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
module: "helloworld", module: "helloworld",

View File

@@ -4,18 +4,7 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {

View File

@@ -3,18 +3,7 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {

View File

@@ -3,18 +3,7 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {

View File

@@ -4,19 +4,6 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: modules:
// Using exotic content. This is why don't accept go to JSON configuration file // Using exotic content. This is why don't accept go to JSON configuration file
(function () { (function () {

View File

@@ -3,21 +3,7 @@
* By rejas https://github.com/rejas * By rejas https://github.com/rejas
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
fullscreen: false,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
module: "compliments", module: "compliments",
@@ -39,7 +25,7 @@ let config = {
} }
} }
] ]
}; });
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -3,19 +3,8 @@
* By fewieden https://github.com/fewieden * By fewieden https://github.com/fewieden
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
@@ -28,7 +17,7 @@ let config = {
} }
} }
] ]
}; });
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -3,20 +3,7 @@
* By fewieden https://github.com/fewieden * By fewieden https://github.com/fewieden
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
module: "weather", module: "weather",
@@ -34,7 +21,7 @@ let config = {
} }
} }
] ]
}; });
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -3,19 +3,8 @@
* By fewieden https://github.com/fewieden * By fewieden https://github.com/fewieden
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "imperial", units: "imperial",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
@@ -30,7 +19,7 @@ let config = {
} }
} }
] ]
}; });
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -3,19 +3,8 @@
* By fewieden https://github.com/fewieden * By fewieden https://github.com/fewieden
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
@@ -30,7 +19,7 @@ let config = {
} }
} }
] ]
}; });
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -3,19 +3,8 @@
* By fewieden https://github.com/fewieden * By fewieden https://github.com/fewieden
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12, timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
@@ -33,7 +22,7 @@ let config = {
} }
} }
] ]
}; });
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -3,19 +3,8 @@
* By rejas * By rejas
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "imperial", units: "imperial",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [ modules: [
{ {
@@ -31,7 +20,7 @@ let config = {
} }
} }
] ]
}; });
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -3,22 +3,9 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8080, ipWhitelist: ["x.x.x.x"]
ipWhitelist: ["x.x.x.x"], });
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: []
};
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -3,22 +3,9 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = require(process.cwd() + "/tests/configs/default.js").configFactory({
port: 8090, port: 8090
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], });
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: []
};
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@@ -4,18 +4,7 @@
* MIT Licensed. * MIT Licensed.
*/ */
let config = { let config = {
port: 8080, ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.10.1"]
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.10.1"],
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
}
}; };
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/

View File

@@ -1,42 +1,13 @@
const helpers = require("./global-setup");
const fetch = require("node-fetch"); const fetch = require("node-fetch");
const helpers = require("./global-setup");
describe("Electron app environment", function () { describe("Electron app environment", function () {
helpers.setupTimeout(this); beforeAll(function (done) {
helpers.startApplication("tests/configs/default.js");
let app = null; helpers.getDocument(done);
beforeAll(function () {
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/env.js";
}); });
afterAll(function () {
beforeEach(function () { helpers.stopApplication();
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
});
afterEach(function () {
return helpers.stopApplication(app);
});
it("should open a browserwindow", async function () {
await app.client.waitUntilWindowLoaded();
app.browserWindow.focus();
expect(await app.client.getWindowCount()).toBe(1);
expect(await app.browserWindow.isMinimized()).toBe(false);
expect(await app.browserWindow.isDevToolsOpened()).toBe(false);
expect(await app.browserWindow.isVisible()).toBe(true);
expect(await app.browserWindow.isFocused()).toBe(true);
const bounds = await app.browserWindow.getBounds();
expect(bounds.width).toBeGreaterThan(0);
expect(bounds.height).toBeGreaterThan(0);
expect(await app.browserWindow.getTitle()).toBe("MagicMirror²");
}); });
it("get request from http://localhost:8080 should return 200", function (done) { it("get request from http://localhost:8080 should return 200", function (done) {
@@ -52,4 +23,10 @@ describe("Electron app environment", function () {
done(); done();
}); });
}); });
it("should show the title MagicMirror²", function () {
const elem = document.querySelector("title");
expect(elem).not.toBe(null);
expect(elem.textContent).toBe("MagicMirror²");
});
}); });

View File

@@ -1,10 +1,7 @@
const helpers = require("./global-setup");
const fetch = require("node-fetch"); const fetch = require("node-fetch");
const helpers = require("./global-setup");
describe("All font files from roboto.css should be downloadable", function () { describe("All font files from roboto.css should be downloadable", function () {
helpers.setupTimeout(this);
let app;
const fontFiles = []; const fontFiles = [];
// Statements below filters out all 'url' lines in the CSS file // Statements below filters out all 'url' lines in the CSS file
const fileContent = require("fs").readFileSync(__dirname + "/../../fonts/roboto.css", "utf8"); const fileContent = require("fs").readFileSync(__dirname + "/../../fonts/roboto.css", "utf8");
@@ -18,20 +15,10 @@ describe("All font files from roboto.css should be downloadable", function () {
} }
beforeAll(function () { beforeAll(function () {
// Set config sample for use in test helpers.startApplication("tests/configs/without_modules.js");
process.env.MM_CONFIG_FILE = "tests/configs/without_modules.js";
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
}); });
afterAll(function () { afterAll(function () {
return helpers.stopApplication(app); helpers.stopApplication();
}); });
test.each(fontFiles)("should return 200 HTTP code for file '%s'", (fontFile, done) => { test.each(fontFiles)("should return 200 HTTP code for file '%s'", (fontFile, done) => {

View File

@@ -1,53 +1,32 @@
/* const jsdom = require("jsdom");
* Magic Mirror Global Setup Test Suite
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
const Application = require("spectron").Application;
const assert = require("assert");
const path = require("path");
const EventEmitter = require("events");
exports.getElectronPath = function () { exports.startApplication = function (configFilename, exec) {
let electronPath = path.join(__dirname, "..", "..", "node_modules", ".bin", "electron"); jest.resetModules();
if (process.platform === "win32") { if (global.app) {
electronPath += ".cmd"; global.app.stop();
} }
return electronPath; // Set config sample for use in test
process.env.MM_CONFIG_FILE = configFilename;
if (exec) exec;
global.app = require("app.js");
global.app.start();
}; };
// Set timeout - if this is run as CI Job, increase timeout exports.stopApplication = function () {
exports.setupTimeout = function (test) { if (global.app) {
if (process.env.CI) { global.app.stop();
jest.setTimeout(30000);
} else {
jest.setTimeout(10000);
} }
}; };
exports.startApplication = function (options) { exports.getDocument = function (callback, ms) {
const emitter = new EventEmitter(); const url = "http://" + (config.address || "localhost") + ":" + (config.port || "8080");
emitter.setMaxListeners(100); jsdom.JSDOM.fromURL(url, { resources: "usable", runScripts: "dangerously" }).then((dom) => {
dom.window.name = "jsdom";
options.path = exports.getElectronPath(); dom.window.onload = function () {
if (process.env.CI) { global.document = dom.window.document;
options.startTimeout = 30000; setTimeout(() => {
} callback();
}, ms);
const app = new Application(options); };
return app.start().then(function () {
assert.strictEqual(app.isRunning(), true);
return app;
});
};
exports.stopApplication = function (app) {
if (!app || !app.isRunning()) {
return;
}
return app.stop().then(function () {
assert.strictEqual(app.isRunning(), false);
}); });
}; };

View File

@@ -1,29 +1,13 @@
const helpers = require("./global-setup");
const fetch = require("node-fetch"); const fetch = require("node-fetch");
const helpers = require("./global-setup");
describe("ipWhitelist directive configuration", function () { describe("ipWhitelist directive configuration", function () {
helpers.setupTimeout(this);
let app = null;
beforeEach(function () {
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
});
afterEach(function () {
return helpers.stopApplication(app);
});
describe("Set ipWhitelist without access", function () { describe("Set ipWhitelist without access", function () {
beforeAll(function () { beforeAll(function () {
// Set config sample for use in test helpers.startApplication("tests/configs/noIpWhiteList.js");
process.env.MM_CONFIG_FILE = "tests/configs/noIpWhiteList.js"; });
afterAll(function () {
helpers.stopApplication();
}); });
it("should return 403", function (done) { it("should return 403", function (done) {
@@ -36,8 +20,10 @@ describe("ipWhitelist directive configuration", function () {
describe("Set ipWhitelist []", function () { describe("Set ipWhitelist []", function () {
beforeAll(function () { beforeAll(function () {
// Set config sample for use in test helpers.startApplication("tests/configs/empty_ipWhiteList.js");
process.env.MM_CONFIG_FILE = "tests/configs/empty_ipWhiteList.js"; });
afterAll(function () {
helpers.stopApplication();
}); });
it("should return 200", function (done) { it("should return 200", function (done) {

21
tests/e2e/mock-console.js Normal file
View File

@@ -0,0 +1,21 @@
/**
* Suppresses errors concerning web server already shut down.
*
* @param {string} err The error message.
*/
function mockError(err) {
if (err.includes("ECONNREFUSED") || err.includes("ECONNRESET") || err.includes("socket hang up") || err.includes("exports is not defined")) {
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

@@ -1,32 +1,17 @@
const helpers = require("../global-setup"); const helpers = require("../global-setup");
describe("Alert module", function () { describe("Alert module", function () {
helpers.setupTimeout(this); beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/alert/default.js");
let app = null; helpers.getDocument(done, 1000);
});
beforeEach(function () { afterAll(function () {
return helpers helpers.stopApplication();
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
}); });
afterEach(function () { it("should show the welcome message", function () {
return helpers.stopApplication(app); const elem = document.querySelector(".ns-box .ns-box-inner .light.bright.small");
}); expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Welcome, start was successful!");
describe("Default configuration", function () {
beforeAll(function () {
// Set config sample for use in test
process.env.MM_CONFIG_FILE = "tests/configs/modules/alert/default.js";
});
it("should show the welcome message", function () {
return app.client.waitUntilTextExists(".ns-box .ns-box-inner .light.bright.small", "Welcome, start was successful!", 10000);
});
}); });
}); });

View File

@@ -1,86 +1,71 @@
const helpers = require("../global-setup"); const helpers = require("../global-setup");
describe("Clock set to spanish language module", function () { describe("Clock set to spanish language module", function () {
helpers.setupTimeout(this); afterAll(function () {
helpers.stopApplication();
});
let app = null; const testMatch = function (element, regex) {
const elem = document.querySelector(element);
testMatch = async function (element, regex) { expect(elem).not.toBe(null);
await app.client.waitUntilWindowLoaded(); expect(elem.textContent).toMatch(regex);
const elem = await app.client.$(element);
const txt = await elem.getText(element);
return expect(txt).toMatch(regex);
}; };
beforeEach(function () {
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
});
afterEach(function () {
return helpers.stopApplication(app);
});
describe("with default 24hr clock config", function () { describe("with default 24hr clock config", function () {
beforeAll(function () { beforeAll(function (done) {
// Set config sample for use in test helpers.startApplication("tests/configs/modules/clock/es/clock_24hr.js");
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/es/clock_24hr.js"; helpers.getDocument(done, 1000);
}); });
it("shows date with correct format", async function () { it("shows date with correct format", function () {
const dateRegex = /^(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo), \d{1,2} de (?:enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre) de \d{4}$/; const dateRegex = /^(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo), \d{1,2} de (?:enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre) de \d{4}$/;
return testMatch(".clock .date", dateRegex); testMatch(".clock .date", dateRegex);
}); });
it("shows time in 24hr format", async function () { it("shows time in 24hr format", function () {
const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/; const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/;
return testMatch(".clock .time", timeRegex); testMatch(".clock .time", timeRegex);
}); });
}); });
describe("with default 12hr clock config", function () { describe("with default 12hr clock config", function () {
beforeAll(function () { beforeAll(function (done) {
// Set config sample for use in test helpers.startApplication("tests/configs/modules/clock/es/clock_12hr.js");
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/es/clock_12hr.js"; helpers.getDocument(done, 1000);
}); });
it("shows date with correct format", async function () { it("shows date with correct format", function () {
const dateRegex = /^(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo), \d{1,2} de (?:enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre) de \d{4}$/; const dateRegex = /^(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo), \d{1,2} de (?:enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre) de \d{4}$/;
return testMatch(".clock .date", dateRegex); testMatch(".clock .date", dateRegex);
}); });
it("shows time in 12hr format", async function () { it("shows time in 12hr format", function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/; const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/;
return testMatch(".clock .time", timeRegex); testMatch(".clock .time", timeRegex);
}); });
}); });
describe("with showPeriodUpper config enabled", function () { describe("with showPeriodUpper config enabled", function () {
beforeAll(function () { beforeAll(function (done) {
// Set config sample for use in test helpers.startApplication("tests/configs/modules/clock/es/clock_showPeriodUpper.js");
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/es/clock_showPeriodUpper.js"; helpers.getDocument(done, 1000);
}); });
it("shows 12hr time with upper case AM/PM", async function () { it("shows 12hr time with upper case AM/PM", function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/; const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/;
return testMatch(".clock .time", timeRegex); testMatch(".clock .time", timeRegex);
}); });
}); });
describe("with showWeek config enabled", function () { describe("with showWeek config enabled", function () {
beforeAll(function () { beforeAll(function (done) {
// Set config sample for use in test helpers.startApplication("tests/configs/modules/clock/es/clock_showWeek.js");
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/es/clock_showWeek.js"; helpers.getDocument(done, 1000);
}); });
it("shows week with correct format", async function () { it("shows week with correct format", function () {
const weekRegex = /^Semana [0-9]{1,2}$/; const weekRegex = /^Semana [0-9]{1,2}$/;
return testMatch(".clock .week", weekRegex); testMatch(".clock .week", weekRegex);
}); });
}); });
}); });

View File

@@ -2,120 +2,115 @@ const helpers = require("../global-setup");
const moment = require("moment"); const moment = require("moment");
describe("Clock module", function () { describe("Clock module", function () {
helpers.setupTimeout(this); afterAll(function () {
helpers.stopApplication();
});
let app = null; const testMatch = function (element, regex) {
const elem = document.querySelector(element);
testMatch = async function (element, regex) { expect(elem).not.toBe(null);
await app.client.waitUntilWindowLoaded(); expect(elem.textContent).toMatch(regex);
const elem = await app.client.$(element);
const txt = await elem.getText(element);
return expect(txt).toMatch(regex);
}; };
beforeEach(function () {
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
});
afterEach(function () {
return helpers.stopApplication(app);
});
describe("with default 24hr clock config", function () { describe("with default 24hr clock config", function () {
beforeAll(function () { beforeAll(function (done) {
// Set config sample for use in test helpers.startApplication("tests/configs/modules/clock/clock_24hr.js");
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_24hr.js"; helpers.getDocument(done, 1000);
}); });
it("should show the date in the correct format", async function () { it("should show the date in the correct format", function () {
const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/; const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/;
return testMatch(".clock .date", dateRegex); testMatch(".clock .date", dateRegex);
}); });
it("should show the time in 24hr format", async function () { it("should show the time in 24hr format", function () {
const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/; const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/;
return testMatch(".clock .time", timeRegex); testMatch(".clock .time", timeRegex);
}); });
}); });
describe("with default 12hr clock config", function () { describe("with default 12hr clock config", function () {
beforeAll(function () { beforeAll(function (done) {
// Set config sample for use in test helpers.startApplication("tests/configs/modules/clock/clock_12hr.js");
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_12hr.js"; helpers.getDocument(done, 1000);
}); });
it("should show the date in the correct format", async function () { it("should show the date in the correct format", function () {
const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/; const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/;
return testMatch(".clock .date", dateRegex); testMatch(".clock .date", dateRegex);
}); });
it("should show the time in 12hr format", async function () { it("should show the time in 12hr format", function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/; const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/;
return testMatch(".clock .time", timeRegex); testMatch(".clock .time", timeRegex);
}); });
}); });
describe("with showPeriodUpper config enabled", function () { describe("with showPeriodUpper config enabled", function () {
beforeAll(function () { beforeAll(function (done) {
// Set config sample for use in test helpers.startApplication("tests/configs/modules/clock/clock_showPeriodUpper.js");
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_showPeriodUpper.js"; helpers.getDocument(done, 1000);
}); });
it("should show 12hr time with upper case AM/PM", async function () { it("should show 12hr time with upper case AM/PM", function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/; const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/;
return testMatch(".clock .time", timeRegex); testMatch(".clock .time", timeRegex);
}); });
}); });
describe("with displaySeconds config disabled", function () { describe("with displaySeconds config disabled", function () {
beforeAll(function () { beforeAll(function (done) {
// Set config sample for use in test helpers.startApplication("tests/configs/modules/clock/clock_displaySeconds_false.js");
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_displaySeconds_false.js"; helpers.getDocument(done, 1000);
}); });
it("should show 12hr time without seconds am/pm", async function () { it("should show 12hr time without seconds am/pm", function () {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[ap]m$/; const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[ap]m$/;
return testMatch(".clock .time", timeRegex); testMatch(".clock .time", timeRegex);
});
});
describe("with showTime config disabled", function () {
beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/clock/clock_showTime.js");
helpers.getDocument(done, 1000);
});
it("should show not show the time when digital clock is shown", function () {
const elem = document.querySelector(".clock .digital .time");
expect(elem).toBe(null);
}); });
}); });
describe("with showWeek config enabled", function () { describe("with showWeek config enabled", function () {
beforeAll(function () { beforeAll(function (done) {
// Set config sample for use in test helpers.startApplication("tests/configs/modules/clock/clock_showWeek.js");
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_showWeek.js"; helpers.getDocument(done, 1000);
}); });
it("should show the week in the correct format", async function () { it("should show the week in the correct format", function () {
const weekRegex = /^Week [0-9]{1,2}$/; const weekRegex = /^Week [0-9]{1,2}$/;
return testMatch(".clock .week", weekRegex); testMatch(".clock .week", weekRegex);
}); });
it("should show the week with the correct number of week of year", async function () { it("should show the week with the correct number of week of year", function () {
const currentWeekNumber = moment().week(); const currentWeekNumber = moment().week();
const weekToShow = "Week " + currentWeekNumber; const weekToShow = "Week " + currentWeekNumber;
await app.client.waitUntilWindowLoaded(); const elem = document.querySelector(".clock .week");
const elem = await app.client.$(".clock .week"); expect(elem).not.toBe(null);
const txt = await elem.getText(".clock .week"); expect(elem.textContent).toBe(weekToShow);
return expect(txt).toBe(weekToShow);
}); });
}); });
describe("with analog clock face enabled", function () { describe("with analog clock face enabled", function () {
beforeAll(function () { beforeAll(function (done) {
// Set config sample for use in test helpers.startApplication("tests/configs/modules/clock/clock_analog.js");
process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_analog.js"; helpers.getDocument(done, 1000);
}); });
it("should show the analog clock face", async () => { it("should show the analog clock face", () => {
await app.client.waitUntilWindowLoaded(); const elem = document.querySelector(".clockCircle");
const clock = await app.client.$$(".clockCircle"); expect(elem).not.toBe(null);
return expect(clock.length).toBe(1);
}); });
}); });
}); });

View File

@@ -1,106 +1,87 @@
const helpers = require("../global-setup"); const helpers = require("../global-setup");
/**
* move similar tests in function doTest
*
* @param {Array} complimentsArray The array of compliments.
*/
function doTest(complimentsArray) {
let elem = document.querySelector(".compliments");
expect(elem).not.toBe(null);
elem = document.querySelector(".module-content");
expect(elem).not.toBe(null);
expect(complimentsArray).toContain(elem.textContent);
}
describe("Compliments module", function () { describe("Compliments module", function () {
helpers.setupTimeout(this); afterAll(function () {
helpers.stopApplication();
let app = null;
beforeEach(function () {
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
});
afterEach(function () {
return helpers.stopApplication(app);
}); });
describe("parts of days", function () { describe("parts of days", function () {
beforeAll(function () { beforeAll(function (done) {
// Set config sample for use in test helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js");
process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_parts_day.js"; helpers.getDocument(done, 1000);
}); });
it("if Morning compliments for that part of day", async function () { it("if Morning compliments for that part of day", function () {
const hour = new Date().getHours(); const hour = new Date().getHours();
if (hour >= 3 && hour < 12) { if (hour >= 3 && hour < 12) {
// if morning check // if morning check
const elem = await app.client.$(".compliments"); doTest(["Hi", "Good Morning", "Morning test"]);
return elem.getText(".compliments").then(function (text) {
expect(["Hi", "Good Morning", "Morning test"]).toContain(text);
});
} }
}); });
it("if Afternoon show Compliments for that part of day", async function () { it("if Afternoon show Compliments for that part of day", function () {
const hour = new Date().getHours(); const hour = new Date().getHours();
if (hour >= 12 && hour < 17) { if (hour >= 12 && hour < 17) {
// if afternoon check // if afternoon check
const elem = await app.client.$(".compliments"); doTest(["Hello", "Good Afternoon", "Afternoon test"]);
return elem.getText(".compliments").then(function (text) {
expect(["Hello", "Good Afternoon", "Afternoon test"]).toContain(text);
});
} }
}); });
it("if Evening show Compliments for that part of day", async function () { it("if Evening show Compliments for that part of day", function () {
const hour = new Date().getHours(); const hour = new Date().getHours();
if (!(hour >= 3 && hour < 12) && !(hour >= 12 && hour < 17)) { if (!(hour >= 3 && hour < 12) && !(hour >= 12 && hour < 17)) {
// if evening check // if evening check
const elem = await app.client.$(".compliments"); doTest(["Hello There", "Good Evening", "Evening test"]);
return elem.getText(".compliments").then(function (text) {
expect(["Hello There", "Good Evening", "Evening test"]).toContain(text);
});
} }
}); });
}); });
describe("Feature anytime in compliments module", function () { describe("Feature anytime in compliments module", function () {
describe("Set anytime and empty compliments for morning, evening and afternoon ", function () { describe("Set anytime and empty compliments for morning, evening and afternoon ", function () {
beforeAll(function () { beforeAll(function (done) {
// Set config sample for use in test helpers.startApplication("tests/configs/modules/compliments/compliments_anytime.js");
process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_anytime.js"; helpers.getDocument(done, 1000);
}); });
it("Show anytime because if configure empty parts of day compliments and set anytime compliments", async function () { it("Show anytime because if configure empty parts of day compliments and set anytime compliments", function () {
const elem = await app.client.$(".compliments"); doTest(["Anytime here"]);
return elem.getText(".compliments").then(function (text) {
expect(["Anytime here"]).toContain(text);
});
}); });
}); });
describe("Only anytime present in configuration compliments", function () { describe("Only anytime present in configuration compliments", function () {
beforeAll(function () { beforeAll(function (done) {
// Set config sample for use in test helpers.startApplication("tests/configs/modules/compliments/compliments_only_anytime.js");
process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_only_anytime.js"; helpers.getDocument(done, 1000);
}); });
it("Show anytime compliments", async function () { it("Show anytime compliments", function () {
const elem = await app.client.$(".compliments"); doTest(["Anytime here"]);
return elem.getText(".compliments").then(function (text) {
expect(["Anytime here"]).toContain(text);
});
}); });
}); });
}); });
describe("Feature date in compliments module", function () { describe("Feature date in compliments module", function () {
describe("Set date and empty compliments for anytime, morning, evening and afternoon", function () { describe("Set date and empty compliments for anytime, morning, evening and afternoon", function () {
beforeAll(function () { beforeAll(function (done) {
// Set config sample for use in test helpers.startApplication("tests/configs/modules/compliments/compliments_date.js");
process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_date.js"; helpers.getDocument(done, 1000);
}); });
it("Show happy new year compliment on new years day", async function () { it("Show happy new year compliment on new years day", function () {
const elem = await app.client.$(".compliments"); doTest(["Happy new year!"]);
return elem.getText(".compliments").then(function (text) {
expect(["Happy new year!"]).toContain(text);
});
}); });
}); });
}); });

View File

@@ -1,45 +1,33 @@
const helpers = require("../global-setup"); const helpers = require("../global-setup");
describe("Test helloworld module", function () { describe("Test helloworld module", function () {
helpers.setupTimeout(this); afterAll(function () {
helpers.stopApplication();
let app = null;
beforeEach(function () {
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
});
afterEach(function () {
return helpers.stopApplication(app);
}); });
describe("helloworld set config text", function () { describe("helloworld set config text", function () {
beforeAll(function () { beforeAll(function (done) {
// Set config sample for use in test helpers.startApplication("tests/configs/modules/helloworld/helloworld.js");
process.env.MM_CONFIG_FILE = "tests/configs/modules/helloworld/helloworld.js"; helpers.getDocument(done, 1000);
}); });
it("Test message helloworld module", async function () { it("Test message helloworld module", function () {
const elem = await app.client.$(".helloworld"); const elem = document.querySelector(".helloworld");
return expect(await elem.getText(".helloworld")).toBe("Test HelloWorld Module"); expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Test HelloWorld Module");
}); });
}); });
describe("helloworld default config text", function () { describe("helloworld default config text", function () {
beforeAll(function () { beforeAll(function (done) {
// Set config sample for use in test helpers.startApplication("tests/configs/modules/helloworld/helloworld_default.js");
process.env.MM_CONFIG_FILE = "tests/configs/modules/helloworld/helloworld_default.js"; helpers.getDocument(done, 1000);
}); });
it("Test message helloworld module", async function () { it("Test message helloworld module", function () {
const elem = await app.client.$(".helloworld"); const elem = document.querySelector(".helloworld");
return expect(await elem.getText(".helloworld")).toBe("Hello World!"); expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Hello World!");
}); });
}); });
}); });

View File

@@ -1,67 +1,63 @@
const helpers = require("../global-setup"); const helpers = require("../global-setup");
describe("Newsfeed module", function () { describe("Newsfeed module", function () {
helpers.setupTimeout(this); afterAll(function () {
helpers.stopApplication();
let app = null;
beforeEach(function () {
return helpers
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
});
afterEach(function () {
return helpers.stopApplication(app);
}); });
describe("Default configuration", function () { describe("Default configuration", function () {
beforeAll(function () { beforeAll(function (done) {
process.env.MM_CONFIG_FILE = "tests/configs/modules/newsfeed/default.js"; helpers.startApplication("tests/configs/modules/newsfeed/default.js");
helpers.getDocument(done, 3000);
}); });
it("should show the newsfeed title", function () { it("should show the newsfeed title", function () {
return app.client.waitUntilTextExists(".newsfeed .newsfeed-source", "Rodrigo Ramirez Blog", 10000); const elem = document.querySelector(".newsfeed .newsfeed-source");
expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Rodrigo Ramirez Blog");
}); });
it("should show the newsfeed article", function () { it("should show the newsfeed article", function () {
return app.client.waitUntilTextExists(".newsfeed .newsfeed-title", "QPanel", 10000); const elem = document.querySelector(".newsfeed .newsfeed-title");
expect(elem).not.toBe(null);
expect(elem.textContent).toContain("QPanel");
}); });
it("should NOT show the newsfeed description", async () => { it("should NOT show the newsfeed description", () => {
await app.client.waitUntilTextExists(".newsfeed .newsfeed-title", "QPanel", 10000); const elem = document.querySelector(".newsfeed .newsfeed-desc");
const events = await app.client.$$(".newsfeed .newsfeed-desc"); expect(elem).toBe(null);
return expect(events.length).toBe(0);
}); });
}); });
describe("Custom configuration", function () { describe("Custom configuration", function () {
beforeAll(function () { beforeAll(function (done) {
process.env.MM_CONFIG_FILE = "tests/configs/modules/newsfeed/prohibited_words.js"; helpers.startApplication("tests/configs/modules/newsfeed/prohibited_words.js");
helpers.getDocument(done, 3000);
}); });
it("should not show articles with prohibited words", function () { it("should not show articles with prohibited words", function () {
return app.client.waitUntilTextExists(".newsfeed .newsfeed-title", "Problema VirtualBox", 10000); const elem = document.querySelector(".newsfeed .newsfeed-title");
expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Problema VirtualBox");
}); });
it("should show the newsfeed description", async () => { it("should show the newsfeed description", () => {
await app.client.waitUntilTextExists(".newsfeed .newsfeed-title", "Problema VirtualBox", 10000); const elem = document.querySelector(".newsfeed .newsfeed-desc");
const events = await app.client.$$(".newsfeed .newsfeed-desc"); expect(elem).not.toBe(null);
return expect(events.length).toBe(1); expect(elem.textContent.length).not.toBe(0);
}); });
}); });
describe("Invalid configuration", function () { describe("Invalid configuration", function () {
beforeAll(function () { beforeAll(function (done) {
process.env.MM_CONFIG_FILE = "tests/configs/modules/newsfeed/incorrect_url.js"; helpers.startApplication("tests/configs/modules/newsfeed/incorrect_url.js");
helpers.getDocument(done, 3000);
}); });
it("should show malformed url warning", function () { it("should show malformed url warning", function () {
return app.client.waitUntilTextExists(".newsfeed .small", "Error in the Newsfeed module. Malformed url.", 10000); const elem = document.querySelector(".newsfeed .small");
expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Error in the Newsfeed module. Malformed url.");
}); });
}); });
}); });

View File

@@ -1,38 +1,24 @@
const helpers = require("./global-setup"); const helpers = require("./global-setup");
describe("Display of modules", function () { describe("Display of modules", function () {
helpers.setupTimeout(this); beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/display.js");
let app = null; helpers.getDocument(done);
});
beforeEach(function () { afterAll(function () {
return helpers helpers.stopApplication();
.startApplication({
args: ["js/electron.js"]
})
.then(function (startedApp) {
app = startedApp;
});
}); });
afterEach(function () { it("should show the test header", function () {
return helpers.stopApplication(app); const elem = document.querySelector("#module_0_helloworld .module-header");
expect(elem).not.toBe(null);
// textContent gibt hier lowercase zurück, das uppercase wird durch css realisiert, was daher nicht in textContent landet
expect(elem.textContent).toBe("test_header");
}); });
describe("Using helloworld", function () { it("should show no header if no header text is specified", function () {
beforeAll(function () { const elem = document.querySelector("#module_1_helloworld .module-header");
// Set config sample for use in test expect(elem).not.toBe(null);
process.env.MM_CONFIG_FILE = "tests/configs/modules/display.js"; expect(elem.textContent).toBe("undefined");
});
it("should show the test header", async () => {
const elem = await app.client.$("#module_0_helloworld .module-header", 10000);
return expect(await elem.getText("#module_0_helloworld .module-header")).toBe("TEST_HEADER");
});
it("should show no header if no header text is specified", async () => {
const elem = await app.client.$("#module_1_helloworld .module-header", 10000);
return expect(await elem.getText("#module_1_helloworld .module-header")).toBe("");
});
}); });
}); });

Some files were not shown because too many files have changed in this diff Show More