Merge pull request #2921 from MichMich/develop

Release 2.21.0
This commit is contained in:
Michael Teeuw 2022-10-01 20:06:53 +02:00 committed by GitHub
commit 9e0293047f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 2827 additions and 2212 deletions

View File

@ -20,9 +20,7 @@ If you are facing an issue or found a bug while trying to install MagicMirror²
## 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 corresponding repository: 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://gitlab.com/khassel/magicmirror](https://gitlab.com/khassel/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)
--- ---

View File

@ -23,7 +23,7 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: "npm" cache: "npm"
@ -31,7 +31,7 @@ jobs:
run: | run: |
Xvfb :99 -screen 0 1024x768x16 & Xvfb :99 -screen 0 1024x768x16 &
export DISPLAY=:99 export DISPLAY=:99
npm install npm run install-mm:dev
touch css/custom.css touch css/custom.css
npm run test:prettier npm run test:prettier
npm run test:js npm run test:js

View File

@ -27,7 +27,7 @@ jobs:
touch css/custom.css touch css/custom.css
npm run test:coverage npm run test:coverage
- name: Upload coverage results to codecov - name: Upload coverage results to codecov
uses: codecov/codecov-action@v2 uses: codecov/codecov-action@v3
with: with:
files: ./coverage/lcov.info files: ./coverage/lcov.info
fail_ci_if_error: true fail_ci_if_error: true

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.21.0] - 2022-10-01
Special thanks to: @BKeyport, @buxxi, @davide125, @khassel, @kolbyjack, @krukle, @MikeBishop, @rejas, @sdetweil, @SkySails and @veeck
## Added
- Possibility to fetch calendars through socket notifications.
- New scripts `install-mm` (and `install-mm:dev`) for simplifying mm installation (now: `npm run install-mm`) and adding params `--no-audit --no-fund --no-update-notifier` for less noise.
- New `showTimeToday` option in calendar module shows time for current-day events even if `timeFormat` is `"relative"`.
- Add hourly forecasts, apparent temperature & custom location name to SMHI weather provider.
## Removed
- Old weather deprecated modules `currentweather` and `weatherforecast`.
## Updated
- Removed `DAYAFTERTOMORROW` from English.
- Update dependencies.
- Updated jsdoc.
- Updated font tree to use variables consistantly.
- Removed deprecated Docker Repository from issue template.
## Fixed
- Broadcast all calendar events while still honoring global and per-calendar maximumEntries.
- Respect rss ttl provided by newsfeed (#2883).
- Fix multi day calendar events always presented as "(1/X)" instead of the amount of days the event has progressed.
- Fix weatherbit provider to use type config value instead of endpoint.
- Fix calendar events which DO NOT specify rrule byday adjusted incorrectly (#2885).
- Fix e2e tests not failing on errors (#2911).
## [2.20.0] - 2022-07-02 ## [2.20.0] - 2022-07-02
Special thanks to the following contributors: @eouia, @khassel, @kolbyjack, @KristjanESPERANTO, @nathannaveen, @naveensrinivasan, @rejas, @rohitdharavath and @sdetweil. Special thanks to the following contributors: @eouia, @khassel, @kolbyjack, @KristjanESPERANTO, @nathannaveen, @naveensrinivasan, @rejas, @rohitdharavath and @sdetweil.
@ -13,13 +45,13 @@ Special thanks to the following contributors: @eouia, @khassel, @kolbyjack, @Kri
- Added a new config option `httpHeaders` used by helmet (see https://helmetjs.github.io/). You can now set own httpHeaders which will override the defaults in `js/defauls.js` which is useful e.g. if you want to embed MagicMirror into annother website (solves #2847). - Added a new config option `httpHeaders` used by helmet (see https://helmetjs.github.io/). You can now set own httpHeaders which will override the defaults in `js/defauls.js` which is useful e.g. if you want to embed MagicMirror into annother website (solves #2847).
- Show endDate for calendar events when dateHeader is enabled and showEnd is set to true (#2192). - Show endDate for calendar events when dateHeader is enabled and showEnd is set to true (#2192).
- Added the notification emitting from the weather module on infromation updated. - Added the notification emitting from the weather module on information updated.
- Use recommended file extention for YAML files (#2864). - Use recommended file extension for YAML files (#2864).
### Updated ### Updated
- Use latest node 18 when running tests on github actions. - Use latest node 18 when running tests on github actions.
- Update `electron` to v19 and other dependencies. - Updated `electron` to v19 and other dependencies.
- Use internal fetch function of node instead external `node-fetch` library if used node version >= `v18`. - Use internal fetch function of node instead external `node-fetch` library if used node version >= `v18`.
- Include duplicate events in broadcasts. - Include duplicate events in broadcasts.
@ -45,12 +77,12 @@ Special thanks to the following contributors: @10bias, @CFenner, @JHWelch, @k1rd
### Updated ### Updated
- Deprecated roboto fonts package `roboto-fontface-bower` replaced with `fontsource`. - Deprecated roboto fonts package `roboto-fontface-bower` replaced with `fontsource`.
- Update `electron` to v17, `helmet` to v5 (use defaults of v4) and other dependencies - Updated `electron` to v17, `helmet` to v5 (use defaults of v4) and other dependencies
- Updates Font Awesome css class to new default style (fixes #2768) - Updated Font Awesome css class to new default style (fixes #2768)
- Replaced deprecated modules `currentweather` and `weatherforecast` with dummy modules only displaying that they have to be replaced. - Replaced deprecated modules `currentweather` and `weatherforecast` with dummy modules only displaying that they have to be replaced.
- Include all calendar events from the configured date range when broadcasting. - Include all calendar events from the configured date range when broadcasting.
- Update Danish and German translation. - Updated Danish and German translation.
- Update `node-ical` to v0.15 and added `luxon` as dependency for not breaking the "no-optional" install (see #2718 and #2824). - Updated `node-ical` to v0.15 and added `luxon` as dependency for not breaking the "no-optional" install (see #2718 and #2824).
### Fixed ### Fixed
@ -76,8 +108,8 @@ Special thanks to the following contributors: @AmpioRosso, @eouia, @fewieden, @j
- ESLint version supports now ECMAScript 2018. - ESLint version supports now ECMAScript 2018.
- Cleaned up `updatenotification` module and switched to nunjuck template. - Cleaned up `updatenotification` module and switched to nunjuck template.
- Moved calendar tests from category `electron` to `e2e`. - Moved calendar tests from category `electron` to `e2e`.
- Update missed translations for Korean language (ko.json). - Updated missed translations for Korean language (ko.json).
- Update missed translations for Dutch language (nl.json). - Updated missed translations for Dutch language (nl.json).
- Cleaned up `alert` module and switched to nunjuck template. - Cleaned up `alert` module and switched to nunjuck template.
- Moved weather tests from category `electron` to `e2e`. - Moved weather tests from category `electron` to `e2e`.
- Updated github actions. - Updated github actions.
@ -128,14 +160,14 @@ Special thanks to the following contributors: @apiontek, @eouia, @jupadin, @khas
- Refactor test configs, use default test config for all tests. - Refactor test configs, use default test config for all tests.
- Updated github templates. - Updated github templates.
- Actually test all js and css files when lint script is run. - Actually test all js and css files when lint script is run.
- Update jsdocs and print warnings during testing too. - Updated jsdocs and print warnings during testing too.
- Update weathergov provider to try fetching not just current, but also foreacst, when API URLs available. - Updated weathergov provider to try fetching not just current, but also foreacst, when API URLs available.
- Refactored clock layout. - Refactored clock layout.
- Refactored methods from weatherproviders into weatherobject (isDaytime, updateSunTime). - Refactored methods from weatherproviders into weatherobject (isDaytime, updateSunTime).
- Use of `logger.js` in jest tests. - Use of `logger.js` in jest tests.
- Run prettier over all relevant files. - Run prettier over all relevant files.
- Move tests needing electron in new category `electron`, use `server only` mode in `e2e` tests. - Move tests needing electron in new category `electron`, use `server only` mode in `e2e` tests.
- Update dependencies in package.json. - Updated dependencies in package.json.
### Fixed ### Fixed
@ -169,13 +201,13 @@ Special thanks to the following contributors: @210954, @B1gG, @codac, @Crazylegs
- Refactor code into es6 where possible (e.g. var -> let/const). - Refactor code into es6 where possible (e.g. var -> let/const).
- Use node v16 in github workflow (replacing node v10). - Use node v16 in github workflow (replacing node v10).
- Moved some files into better suited directories. - Moved some files into better suited directories.
- Update dependencies in package.json, require node >= v12, remove `rrule-alt` and `rrule`. - Updated dependencies in package.json, require node >= v12, remove `rrule-alt` and `rrule`.
- Update dependencies in package.json and migrate husky to v6, fix husky setup in prod environment. - Updated dependencies in package.json and migrate husky to v6, fix husky setup in prod environment.
- Cleaned up error handling in newsfeed and calendar modules for real. - Cleaned up error handling in newsfeed and calendar modules for real.
- Updated default WEATHER module such that a provider can optionally set a custom unit-of-measure for precipitation (`weatherObject.precipitationUnits`). - Updated default WEATHER module such that a provider can optionally set a custom unit-of-measure for precipitation (`weatherObject.precipitationUnits`).
- Update documentation. - Updated documentation.
- Update jest tests: Reset changes on js/logger.js, mock logger.js in global_vars tests. - Updated jest tests: Reset changes on js/logger.js, mock logger.js in global_vars tests.
- Update dependencies in package.json. - Updated dependencies in package.json.
### Removed ### Removed
@ -287,10 +319,10 @@ Special thanks to the following contributors: @Alvinger, @AndyPoms, @ashishtank,
- Merging .gitignore in the config-folder with the .gitignore in the root-folder. - Merging .gitignore in the config-folder with the .gitignore in the root-folder.
- Weather module - forecast now show TODAY and TOMORROW instead of weekday, to make it easier to understand. - Weather module - forecast now show TODAY and TOMORROW instead of weekday, to make it easier to understand.
- Update dependencies to latest versions. - Updated dependencies to latest versions.
- Update dependencies eslint, feedme, simple-git and socket.io to latest versions. - Updated dependencies eslint, feedme, simple-git and socket.io to latest versions.
- Update lithuanian translation. - Updated lithuanian translation.
- Update config sample. - Updated config sample.
- Highlight required version mismatch. - Highlight required version mismatch.
- No select Text for TouchScreen use. - No select Text for TouchScreen use.
- Corrected logic for timeFormat "relative" and "absolute". - Corrected logic for timeFormat "relative" and "absolute".
@ -318,12 +350,12 @@ Special thanks to the following contributors: @Alvinger, @AndyPoms, @ashishtank,
- Catch errors when parsing calendar data with ical. (#2022) - Catch errors when parsing calendar data with ical. (#2022)
- Fix Default Alert Module does not hide black overlay when alert is dismissed manually. (#2228) - Fix Default Alert Module does not hide black overlay when alert is dismissed manually. (#2228)
- Weather module - Always displays night icons when local is other than English. (#2221) - Weather module - Always displays night icons when local is other than English. (#2221)
- Update node-ical 0.12.4, fix invalid RRULE format in cal entries - Updated node-ical 0.12.4, fix invalid RRULE format in cal entries
- Fix package.json for optional electron dependency (2378) - Fix package.json for optional electron dependency (2378)
- Update node-ical version again, 0.12.5, change RRULE fix (#2371, #2379) - Updated node-ical version again, 0.12.5, change RRULE fix (#2371, #2379)
- Remove undefined objects from modules array (#2382) - Remove undefined objects from modules array (#2382)
- Update node-ical version again, 0.12.7, change RRULE fix (#2371, #2379), node-ical now throws error (which we catch) - Updated node-ical version again, 0.12.7, change RRULE fix (#2371, #2379), node-ical now throws error (which we catch)
- Update simple-git version to 2.31 unhandled promise rejection (#2383) - Updated simple-git version to 2.31 unhandled promise rejection (#2383)
## [2.13.0] - 2020-10-01 ## [2.13.0] - 2020-10-01
@ -548,10 +580,10 @@ Special thanks to @sdetweil for all his great contributions!
- English translation for "Feels" to "Feels like" - English translation for "Feels" to "Feels like"
- Fixed the example calendar url in `config.js.sample` - Fixed the example calendar url in `config.js.sample`
- Update `ical.js` to solve various calendar issues. - Updated `ical.js` to solve various calendar issues.
- Update weather city list url [#1676](https://github.com/MichMich/MagicMirror/issues/1676) - Updated 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. - Updated weatherprovider documentation.
### Fixed ### Fixed
@ -571,7 +603,7 @@ Special thanks to @sdetweil for all his great contributions!
- use current username vs hardcoded 'pi' to support non-pi install - use current username vs hardcoded 'pi' to support non-pi install
- check for npm installed. node install doesn't do npm anymore - check for npm installed. node install doesn't do npm anymore
- check for mac as part of PM2 install, add install option string - check for mac as part of PM2 install, add install option string
- update pm2 config with current username instead of hard coded 'pi' - Updated pm2 config with current username instead of hard coded 'pi'
- check for screen saver config, "/etc/xdg/lxsession", bypass if not setup - check for screen saver config, "/etc/xdg/lxsession", bypass if not setup
## [2.7.1] - 2019-04-02 ## [2.7.1] - 2019-04-02
@ -779,7 +811,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Add types for module. - Add types for module.
- Implement Danger.js to notify contributors when CHANGELOG.md is missing in PR. - Implement Danger.js to notify contributors when CHANGELOG.md is missing in PR.
- Allow scrolling in full page article view of default newsfeed module with gesture events from [MMM-Gestures](https://github.com/thobach/MMM-Gestures) - Allow scrolling in full page article view of default newsfeed module with gesture events from [MMM-Gestures](https://github.com/thobach/MMM-Gestures)
- Changed 'compliments.js' - update DOM if remote compliments are loaded instead of waiting one updateInterval to show custom compliments - Changed 'compliments.js' - Updated DOM if remote compliments are loaded instead of waiting one updateInterval to show custom compliments
- Automated unit tests utils, deprecated, translator, cloneObject(lockstrings) - Automated unit tests utils, deprecated, translator, cloneObject(lockstrings)
- Automated integration tests translations - Automated integration tests translations
- Add advanced filtering to the excludedEvents configuration of the default calendar module - Add advanced filtering to the excludedEvents configuration of the default calendar module
@ -791,7 +823,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Add link to GitHub repository which contains the respective Dockerfile. - Add link to GitHub repository which contains the respective Dockerfile.
- Optimized automated unit tests cloneObject, cmpVersions - Optimized automated unit tests cloneObject, cmpVersions
- Update notifications use now translation templates instead of normal strings. - Updated notifications use now translation templates instead of normal strings.
- Yarn can be used now as an installation tool - Yarn can be used now as an installation tool
- Changed Electron dependency to v1.7.13. - Changed Electron dependency to v1.7.13.
@ -998,7 +1030,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
### Fixed ### Fixed
- Update .gitignore to not ignore default modules folder. - Updated .gitignore to not ignore default modules folder.
- Remove white flash on boot up. - Remove white flash on boot up.
- Added `update` in Raspberry Pi installation script. - Added `update` in Raspberry Pi installation script.
- Fix an issue where the analog clock looked scrambled. ([#611](https://github.com/MichMich/MagicMirror/issues/611)) - Fix an issue where the analog clock looked scrambled. ([#611](https://github.com/MichMich/MagicMirror/issues/611))
@ -1083,8 +1115,8 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
### Updated ### Updated
- Force fullscreen when kioskmode is active. - Force fullscreen when kioskmode is active.
- Update the .github templates and information with more modern information. - Updated the .github templates and information with more modern information.
- Update the Gruntfile with a more functional StyleLint implementation. - Updated the Gruntfile with a more functional StyleLint implementation.
## [2.0.4] - 2016-08-07 ## [2.0.4] - 2016-08-07

View File

@ -1,6 +1,6 @@
# The MIT License (MIT) # The MIT License (MIT)
Copyright © 2016-2021 Michael Teeuw Copyright © 2016-2022 Michael Teeuw
Permission is hereby granted, free of charge, to any person Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation obtaining a copy of this software and associated documentation

View File

@ -8,7 +8,11 @@
--font-secondary: "Roboto"; --font-secondary: "Roboto";
--font-size: 20px; --font-size: 20px;
--font-size-small: 0.75rem; --font-size-xsmall: 0.75rem;
--font-size-small: 1rem;
--font-size-medium: 1.5rem;
--font-size-large: 3.25rem;
--font-size-xlarge: 3.75rem;
--gap-body-top: 60px; --gap-body-top: 60px;
--gap-body-right: 60px; --gap-body-right: 60px;
@ -60,27 +64,27 @@ body {
} }
.xsmall { .xsmall {
font-size: var(--font-size-small); font-size: var(--font-size-xsmall);
line-height: 1.275; line-height: 1.275;
} }
.small { .small {
font-size: 1rem; font-size: var(--font-size-small);
line-height: 1.25; line-height: 1.25;
} }
.medium { .medium {
font-size: 1.5rem; font-size: var(--font-size-medium);
line-height: 1.225; line-height: 1.225;
} }
.large { .large {
font-size: 3.25rem; font-size: var(--font-size-large);
line-height: 1; line-height: 1;
} }
.xlarge { .xlarge {
font-size: 3.75rem; font-size: var(--font-size-xlarge);
line-height: 1; line-height: 1;
letter-spacing: -3px; letter-spacing: -3px;
} }
@ -115,7 +119,7 @@ body {
header { header {
text-transform: uppercase; text-transform: uppercase;
font-size: var(--font-size-small); font-size: var(--font-size-xsmall);
font-family: var(--font-primary), Arial, Helvetica, sans-serif; font-family: var(--font-primary), Arial, Helvetica, sans-serif;
font-weight: 400; font-weight: 400;
border-bottom: 1px solid var(--color-text-dimmed); border-bottom: 1px solid var(--color-text-dimmed);

View File

@ -7,31 +7,31 @@
"name": "magicmirror-fonts", "name": "magicmirror-fonts",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fontsource/roboto": "^4.5.7", "@fontsource/roboto": "^4.5.8",
"@fontsource/roboto-condensed": "^4.5.8" "@fontsource/roboto-condensed": "^4.5.9"
} }
}, },
"node_modules/@fontsource/roboto": { "node_modules/@fontsource/roboto": {
"version": "4.5.7", "version": "4.5.8",
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.7.tgz", "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz",
"integrity": "sha512-m57UMER23Mk6Drg9OjtHW1Y+0KPGyZfE5XJoPTOsLARLar6013kJj4X2HICt+iFLJqIgTahA/QAvSn9lwF1EEw==" "integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA=="
}, },
"node_modules/@fontsource/roboto-condensed": { "node_modules/@fontsource/roboto-condensed": {
"version": "4.5.8", "version": "4.5.9",
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-4.5.8.tgz", "resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-4.5.9.tgz",
"integrity": "sha512-HCuf1rVSOsXnl/KgHNRLCr8XS/Dunzn10BjhliJiEZ5qPynXCWH4RRBFupIODHamhj2Uyp/iZkSQp574luKp6A==" "integrity": "sha512-ql4sQq+h8puBVildZ5ssjYf8DWDONYDe3PD3Bu/p1ZW9GnRETRNPPcCTs/q62HIl3QimwwkiKWynn6wZhQaetg=="
} }
}, },
"dependencies": { "dependencies": {
"@fontsource/roboto": { "@fontsource/roboto": {
"version": "4.5.7", "version": "4.5.8",
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.7.tgz", "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz",
"integrity": "sha512-m57UMER23Mk6Drg9OjtHW1Y+0KPGyZfE5XJoPTOsLARLar6013kJj4X2HICt+iFLJqIgTahA/QAvSn9lwF1EEw==" "integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA=="
}, },
"@fontsource/roboto-condensed": { "@fontsource/roboto-condensed": {
"version": "4.5.8", "version": "4.5.9",
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-4.5.8.tgz", "resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-4.5.9.tgz",
"integrity": "sha512-HCuf1rVSOsXnl/KgHNRLCr8XS/Dunzn10BjhliJiEZ5qPynXCWH4RRBFupIODHamhj2Uyp/iZkSQp574luKp6A==" "integrity": "sha512-ql4sQq+h8puBVildZ5ssjYf8DWDONYDe3PD3Bu/p1ZW9GnRETRNPPcCTs/q62HIl3QimwwkiKWynn6wZhQaetg=="
} }
} }
} }

View File

@ -10,7 +10,7 @@
"url": "https://github.com/MichMich/MagicMirror/issues" "url": "https://github.com/MichMich/MagicMirror/issues"
}, },
"dependencies": { "dependencies": {
"@fontsource/roboto": "^4.5.7", "@fontsource/roboto": "^4.5.8",
"@fontsource/roboto-condensed": "^4.5.8" "@fontsource/roboto-condensed": "^4.5.9"
} }
} }

View File

@ -22,39 +22,38 @@ const NodeHelper = Class.extend({
Log.log(`Starting module helper: ${this.name}`); Log.log(`Starting module helper: ${this.name}`);
}, },
/* stop() /**
* Called when the MagicMirror² server receives a `SIGINT` * Called when the MagicMirror² server receives a `SIGINT`
* Close any open connections, stop any sub-processes and * Close any open connections, stop any sub-processes and
* gracefully exit the module. * gracefully exit the module.
*
*/ */
stop() { stop() {
Log.log(`Stopping module helper: ${this.name}`); Log.log(`Stopping module helper: ${this.name}`);
}, },
/* socketNotificationReceived(notification, payload) /**
* This method is called when a socket notification arrives. * This method is called when a socket notification arrives.
* *
* argument notification string - The identifier of the notification. * @param {string} notification The identifier of the notification.
* argument payload mixed - The payload of the notification. * @param {*} payload The payload of the notification.
*/ */
socketNotificationReceived(notification, payload) { socketNotificationReceived(notification, payload) {
Log.log(`${this.name} received a socket notification: ${notification} - Payload: ${payload}`); Log.log(`${this.name} received a socket notification: ${notification} - Payload: ${payload}`);
}, },
/* setName(name) /**
* Set the module name. * Set the module name.
* *
* argument name string - Module name. * @param {string} name Module name.
*/ */
setName(name) { setName(name) {
this.name = name; this.name = name;
}, },
/* setPath(path) /**
* Set the module path. * Set the module path.
* *
* argument path string - Module path. * @param {string} path Module path.
*/ */
setPath(path) { setPath(path) {
this.path = path; this.path = path;

0
modules/default/calendar/README.md Executable file → Normal file
View File

21
modules/default/calendar/calendar.js Executable file → Normal file
View File

@ -37,6 +37,7 @@ Module.register("calendar", {
hidePrivate: false, hidePrivate: false,
hideOngoing: false, hideOngoing: false,
hideTime: false, hideTime: false,
showTimeToday: false,
colored: false, colored: false,
coloredSymbolOnly: false, coloredSymbolOnly: false,
customEvents: [], // Array of {keyword: "", symbol: "", color: ""} where Keyword is a regexp and symbol/color are to be applied for matched customEvents: [], // Array of {keyword: "", symbol: "", color: ""} where Keyword is a regexp and symbol/color are to be applied for matched
@ -133,6 +134,10 @@ Module.register("calendar", {
// Override socket notification handler. // Override socket notification handler.
socketNotificationReceived: function (notification, payload) { socketNotificationReceived: function (notification, payload) {
if (notification === "FETCH_CALENDAR") {
this.sendSocketNotification(notification, { url: payload.url, id: this.identifier });
}
if (this.identifier !== payload.id) { if (this.identifier !== payload.id) {
return; return;
} }
@ -368,7 +373,7 @@ Module.register("calendar", {
} else { } else {
timeWrapper.innerHTML = this.capFirst( timeWrapper.innerHTML = this.capFirst(
moment(event.startDate, "x").calendar(null, { moment(event.startDate, "x").calendar(null, {
sameDay: "[" + this.translate("TODAY") + "]", sameDay: this.config.showTimeToday ? "LT" : "[" + this.translate("TODAY") + "]",
nextDay: "[" + this.translate("TOMORROW") + "]", nextDay: "[" + this.translate("TOMORROW") + "]",
nextWeek: "dddd", nextWeek: "dddd",
sameElse: event.fullDayEvent ? this.config.fullDayEventDateFormat : this.config.dateFormat sameElse: event.fullDayEvent ? this.config.fullDayEventDateFormat : this.config.dateFormat
@ -493,6 +498,7 @@ Module.register("calendar", {
for (const calendarUrl in this.calendarData) { for (const calendarUrl in this.calendarData) {
const calendar = this.calendarData[calendarUrl]; const calendar = this.calendarData[calendarUrl];
let remainingEntries = this.maximumEntriesForUrl(calendarUrl);
for (const e in calendar) { for (const e in calendar) {
const event = JSON.parse(JSON.stringify(calendar[e])); // clone object const event = JSON.parse(JSON.stringify(calendar[e])); // clone object
@ -510,6 +516,9 @@ Module.register("calendar", {
if (this.listContainsEvent(events, event)) { if (this.listContainsEvent(events, event)) {
continue; continue;
} }
if (--remainingEntries < 0) {
break;
}
} }
event.url = calendarUrl; event.url = calendarUrl;
event.today = event.startDate >= today && event.startDate < today + 24 * 60 * 60 * 1000; event.today = event.startDate >= today && event.startDate < today + 24 * 60 * 60 * 1000;
@ -718,6 +727,16 @@ Module.register("calendar", {
return this.getCalendarProperty(url, "repeatingCountTitle", this.config.defaultRepeatingCountTitle); return this.getCalendarProperty(url, "repeatingCountTitle", this.config.defaultRepeatingCountTitle);
}, },
/**
* Retrieves the maximum entry count for a specific calendar url.
*
* @param {string} url The calendar url
* @returns {number} The maximum entry count
*/
maximumEntriesForUrl: function (url) {
return this.getCalendarProperty(url, "maximumEntries", this.config.maximumEntries);
},
/** /**
* Helper method to retrieve the property for a specific calendar url. * Helper method to retrieve the property for a specific calendar url.
* *

View File

@ -333,9 +333,12 @@ const CalendarUtils = {
// 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 (dh < Math.abs(dateoffset / 60)) { if (dh < Math.abs(dateoffset / 60)) {
// if the rrule byweekday WAS explicitly set , correct it
// reduce the time by the offset // reduce the time by the offset
if (curEvent.rrule.origOptions.byweekday !== undefined) {
// 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(24 * 60) * 60000); date = new Date(date.getTime() - Math.abs(24 * 60) * 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
//duration = 24 * 60 * 60 * 1000; //duration = 24 * 60 * 60 * 1000;
@ -346,8 +349,11 @@ const CalendarUtils = {
//if (event.start.tz === moment.tz.guess()) { //if (event.start.tz === moment.tz.guess()) {
// if the date hour is less than the offset // if the date hour is less than the offset
if (24 - dh <= Math.abs(dateoffset / 60)) { if (24 - dh <= Math.abs(dateoffset / 60)) {
// if the rrule byweekday WAS explicitly set , correct it
if (curEvent.rrule.origOptions.byweekday !== undefined) {
// apply the correction to the date/time back to right day // apply the correction to the date/time back to right day
date = new Date(date.getTime() + Math.abs(24 * 60) * 60000); date = new Date(date.getTime() + Math.abs(24 * 60) * 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
//duration = 24 * 60 * 60 * 1000; //duration = 24 * 60 * 60 * 1000;
@ -361,9 +367,12 @@ 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: // if the rrule byweekday WAS explicitly set , correct it
if (curEvent.rrule.origOptions.byweekday !== undefined) {
// Reduce the time by t:
// 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(24 * 60) * 60000); date = new Date(date.getTime() - Math.abs(24 * 60) * 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
//duration = 24 * 60 * 60 * 1000; //duration = 24 * 60 * 60 * 1000;
@ -374,8 +383,11 @@ const CalendarUtils = {
//if (event.start.tz === moment.tz.guess()) { //if (event.start.tz === moment.tz.guess()) {
// if the date hour is less than the offset // if the date hour is less than the offset
if (24 - dh <= Math.abs(dateoffset / 60)) { if (24 - dh <= Math.abs(dateoffset / 60)) {
// if the rrule byweekday WAS explicitly set , correct it
if (curEvent.rrule.origOptions.byweekday !== undefined) {
// apply the correction to the date/time back to right day // apply the correction to the date/time back to right day
date = new Date(date.getTime() + Math.abs(24 * 60) * 60000); date = new Date(date.getTime() + Math.abs(24 * 60) * 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
//duration = 24 * 60 * 60 * 1000; //duration = 24 * 60 * 60 * 1000;
@ -469,10 +481,6 @@ const CalendarUtils = {
return; return;
} }
// Adjust start date so multiple day events will be displayed as happening today even though they started some days ago already
if (fullDayEvent && startDate <= today && endDate > today) {
startDate = moment(today);
}
// if the start and end are the same, then make end the 'end of day' value (start is at 00:00:00) // if the start and end are the same, then make end the 'end of day' value (start is at 00:00:00)
if (fullDayEvent && startDate.format("x") === endDate.format("x")) { if (fullDayEvent && startDate.format("x") === endDate.format("x")) {
endDate = endDate.endOf("day"); endDate = endDate.endOf("day");
@ -498,8 +506,7 @@ const CalendarUtils = {
return a.startDate - b.startDate; return a.startDate - b.startDate;
}); });
let maxEvents = newEvents.slice(0, config.maximumEntries); return newEvents;
return maxEvents;
}, },
/** /**

View File

@ -19,6 +19,14 @@ module.exports = NodeHelper.create({
socketNotificationReceived: function (notification, payload) { socketNotificationReceived: function (notification, payload) {
if (notification === "ADD_CALENDAR") { if (notification === "ADD_CALENDAR") {
this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents, payload.selfSignedCert, payload.id); this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents, payload.selfSignedCert, payload.id);
} else if (notification === "FETCH_CALENDAR") {
const key = payload.id + payload.url;
if (typeof this.fetchers[key] === "undefined") {
Log.error("Calendar Error. No fetcher exists with key: ", key);
this.sendSocketNotification("CALENDAR_ERROR", { error_type: "MODULE_ERROR_UNSPECIFIED" });
return;
}
this.fetchers[key].startFetch();
} }
}, },

View File

@ -1,33 +0,0 @@
/* eslint-disable */
/* MagicMirror²
* Module: CurrentWeather
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*
* This module is deprecated. Any additional feature will no longer be merged.
*/
Module.register("currentweather", {
// Define start sequence.
start: function () {
Log.info("Starting module: " + this.name);
},
// Override dom generator.
getDom: function () {
var wrapper = document.createElement("div");
wrapper.className = this.config.tableClass;
wrapper.innerHTML =
"<style>text-decoration: none</style>" +
"This module is deprecated since release v2.15 and removed with v2.19." +
'<br>Please use the `weather` module as replacement, more info in the <a href="https://docs.magicmirror.builders/modules/weather.html" style="color: #ffffff">documentation</a>.';
wrapper.className = "dimmed light small";
return wrapper;
},
// Override getHeader method.
getHeader: function () {
return "deprecated currentweather";
}
});

View File

@ -4,7 +4,7 @@
* By Michael Teeuw https://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
const defaultModules = ["alert", "calendar", "clock", "compliments", "currentweather", "helloworld", "newsfeed", "weatherforecast", "updatenotification", "weather"]; const defaultModules = ["alert", "calendar", "clock", "compliments", "helloworld", "newsfeed", "updatenotification", "weather"];
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") { if (typeof module !== "undefined") {

View File

@ -78,6 +78,19 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
scheduleTimer(); scheduleTimer();
}); });
parser.on("ttl", (minutes) => {
try {
// 86400000 = 24 hours is mentioned in the docs as maximum value:
const ttlms = Math.min(minutes * 60 * 1000, 86400000);
if (ttlms > reloadInterval) {
reloadInterval = ttlms;
Log.info("Newsfeed-Fetcher: reloadInterval set to ttl=" + reloadInterval + " for url " + url);
}
} catch (error) {
Log.warn("Newsfeed-Fetcher: feed ttl is no valid integer=" + minutes + " for url " + url);
}
});
const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); const nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
const headers = { const headers = {
"User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version, "User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version,

2
modules/default/weather/README.md Executable file → Normal file
View File

@ -1,5 +1,5 @@
# Weather Module # Weather Module
This module aims to be the replacement for the current `currentweather` and `weatherforcast` modules. The module will be configurable to be used as a current weather view, or to show the forecast. This way the module can be used twice to fulfill both purposes. This module will be configurable to be used as a current weather view, or to show the forecast. This way the module can be used twice to fulfill both purposes.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/weather.html). For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/weather.html).

0
modules/default/weather/current.njk Executable file → Normal file
View File

0
modules/default/weather/providers/README.md Executable file → Normal file
View File

0
modules/default/weather/providers/darksky.js Executable file → Normal file
View File

0
modules/default/weather/providers/openweathermap.js Executable file → Normal file
View File

View File

@ -17,19 +17,20 @@ WeatherProvider.register("smhi", {
defaults: { defaults: {
lat: 0, lat: 0,
lon: 0, lon: 0,
precipitationValue: "pmedian" precipitationValue: "pmedian",
location: false
}, },
/** /**
* Implements method in interface for fetching current weather * Implements method in interface for fetching current weather.
*/ */
fetchCurrentWeather() { fetchCurrentWeather() {
this.fetchData(this.getURL()) this.fetchData(this.getURL())
.then((data) => { .then((data) => {
let closest = this.getClosestToCurrentTime(data.timeSeries); const closest = this.getClosestToCurrentTime(data.timeSeries);
let coordinates = this.resolveCoordinates(data); const coordinates = this.resolveCoordinates(data);
let weatherObject = this.convertWeatherDataToObject(closest, coordinates); const weatherObject = this.convertWeatherDataToObject(closest, coordinates);
this.setFetchedLocation(`(${coordinates.lat},${coordinates.lon})`); this.setFetchedLocation(this.config.location || `(${coordinates.lat},${coordinates.lon})`);
this.setCurrentWeather(weatherObject); this.setCurrentWeather(weatherObject);
}) })
.catch((error) => Log.error("Could not load data: " + error.message)) .catch((error) => Log.error("Could not load data: " + error.message))
@ -37,21 +38,35 @@ WeatherProvider.register("smhi", {
}, },
/** /**
* Implements method in interface for fetching a forecast. * Implements method in interface for fetching a multi-day forecast.
* Handling hourly forecast would be easy as not grouping by day but it seems really specific for one weather provider for now.
*/ */
fetchWeatherForecast() { fetchWeatherForecast() {
this.fetchData(this.getURL()) this.fetchData(this.getURL())
.then((data) => { .then((data) => {
let coordinates = this.resolveCoordinates(data); const coordinates = this.resolveCoordinates(data);
let weatherObjects = this.convertWeatherDataGroupedByDay(data.timeSeries, coordinates); const weatherObjects = this.convertWeatherDataGroupedBy(data.timeSeries, coordinates);
this.setFetchedLocation(`(${coordinates.lat},${coordinates.lon})`); this.setFetchedLocation(this.config.location || `(${coordinates.lat},${coordinates.lon})`);
this.setWeatherForecast(weatherObjects); this.setWeatherForecast(weatherObjects);
}) })
.catch((error) => Log.error("Could not load data: " + error.message)) .catch((error) => Log.error("Could not load data: " + error.message))
.finally(() => this.updateAvailable()); .finally(() => this.updateAvailable());
}, },
/**
* Implements method in interface for fetching hourly forecasts.
*/
fetchWeatherHourly() {
this.fetchData(this.getURL())
.then((data) => {
const coordinates = this.resolveCoordinates(data);
const weatherObjects = this.convertWeatherDataGroupedBy(data.timeSeries, coordinates, "hour");
this.setFetchedLocation(this.config.location || `(${coordinates.lat},${coordinates.lon})`);
this.setWeatherHourly(weatherObjects);
})
.catch((error) => Log.error("Could not load data: " + error.message))
.finally(() => this.updateAvailable());
},
/** /**
* 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
* *
@ -94,6 +109,21 @@ WeatherProvider.register("smhi", {
return `https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/geotype/point/lon/${lon}/lat/${lat}/data.json`; return `https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/geotype/point/lon/${lon}/lat/${lat}/data.json`;
}, },
/**
* Calculates the apparent temperature based on known atmospheric data.
*
* @param {object} weatherData Weatherdata to use for the calculation
* @returns {number} The apparent temperature
*/
calculateApparentTemperature(weatherData) {
const Ta = this.paramValue(weatherData, "t");
const rh = this.paramValue(weatherData, "r");
const ws = this.paramValue(weatherData, "ws");
const p = (rh / 100) * 6.105 * Math.E * ((17.27 * Ta) / (237.7 + Ta));
return Ta + 0.33 * p - 0.7 * ws - 4;
},
/** /**
* Converts the returned data into a WeatherObject with required properties set for both current weather and forecast. * Converts the returned data into a WeatherObject with required properties set for both current weather and forecast.
* The returned units is always in metric system. * The returned units is always in metric system.
@ -114,6 +144,7 @@ WeatherProvider.register("smhi", {
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"), currentWeather.isDayTime()); currentWeather.weatherType = this.convertWeatherType(this.paramValue(weatherData, "Wsymb2"), currentWeather.isDayTime());
currentWeather.feelsLikeTemp = this.calculateAT(weatherData);
// Determine the precipitation amount and category and update the // Determine the precipitation amount and category and update the
// weatherObject with it, the valuetype to use can be configured or uses // weatherObject with it, the valuetype to use can be configured or uses
@ -147,9 +178,10 @@ WeatherProvider.register("smhi", {
* *
* @param {object[]} allWeatherData Array of weatherdata * @param {object[]} allWeatherData Array of weatherdata
* @param {object} coordinates Coordinates of the locations of the weather * @param {object} coordinates Coordinates of the locations of the weather
* @param {string} groupBy The interval to use for grouping the data (day, hour)
* @returns {WeatherObject[]} Array of weatherobjects * @returns {WeatherObject[]} Array of weatherobjects
*/ */
convertWeatherDataGroupedByDay(allWeatherData, coordinates) { convertWeatherDataGroupedBy(allWeatherData, coordinates, groupBy = "day") {
let currentWeather; let currentWeather;
let result = []; let result = [];
@ -157,10 +189,11 @@ WeatherProvider.register("smhi", {
let dayWeatherTypes = []; let dayWeatherTypes = [];
for (const weatherObject of allWeatherObjects) { for (const weatherObject of allWeatherObjects) {
//If its the first object or if a day change we need to reset the summary object //If its the first object or if a day/hour change we need to reset the summary object
if (!currentWeather || !currentWeather.date.isSame(weatherObject.date, "day")) { if (!currentWeather || !currentWeather.date.isSame(weatherObject.date, groupBy)) {
currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
dayWeatherTypes = []; dayWeatherTypes = [];
currentWeather.temperature = weatherObject.temperature;
currentWeather.date = weatherObject.date; currentWeather.date = weatherObject.date;
currentWeather.minTemperature = Infinity; currentWeather.minTemperature = Infinity;
currentWeather.maxTemperature = -Infinity; currentWeather.maxTemperature = -Infinity;

0
modules/default/weather/providers/ukmetoffice.js Executable file → Normal file
View File

View File

@ -18,7 +18,6 @@ WeatherProvider.register("weatherbit", {
// 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.weatherbit.io/v2.0",
weatherEndpoint: "/current",
apiKey: "", apiKey: "",
lat: 0, lat: 0,
lon: 0 lon: 0
@ -69,6 +68,31 @@ WeatherProvider.register("weatherbit", {
.finally(() => this.updateAvailable()); .finally(() => this.updateAvailable());
}, },
/**
* Overrides method for setting config to check if endpoint is correct for hourly
*
* @param {object} config The configuration object
*/
setConfig(config) {
this.config = config;
if (!this.config.weatherEndpoint) {
switch (this.config.type) {
case "hourly":
this.config.weatherEndpoint = "/forecast/hourly";
break;
case "daily":
case "forecast":
this.config.weatherEndpoint = "/forecast/daily";
break;
case "current":
this.config.weatherEndpoint = "/current";
break;
default:
Log.error("weatherEndpoint not configured and could not resolve it based on type");
}
}
},
// Create a URL from the config and base URL. // Create a URL from the config and base URL.
getUrl() { getUrl() {
const units = this.units[this.config.units] || "auto"; const units = this.units[this.config.units] || "auto";

0
modules/default/weather/providers/weathergov.js Executable file → Normal file
View File

0
modules/default/weather/weatherobject.js Executable file → Normal file
View File

View File

@ -1,33 +0,0 @@
/* eslint-disable */
/* MagicMirror²
* Module: CurrentWeather
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*
* This module is deprecated. Any additional feature will no longer be merged.
*/
Module.register("weatherforecast", {
// Define start sequence.
start: function () {
Log.info("Starting module: " + this.name);
},
// Override dom generator.
getDom: function () {
var wrapper = document.createElement("div");
wrapper.className = this.config.tableClass;
wrapper.innerHTML =
"<style>text-decoration: none</style>" +
"This module is deprecated since release v2.15 and removed with v2.19." +
'<br>Please use the `weather` module as replacement, more info in the <a href="https://docs.magicmirror.builders/modules/weather.html" style="color: #ffffff">documentation</a>.';
wrapper.className = "dimmed light small";
return wrapper;
},
// Override getHeader method.
getHeader: function () {
return "deprecated weatherforecast";
}
});

3819
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,17 @@
{ {
"name": "magicmirror", "name": "magicmirror",
"version": "2.20.0", "version": "2.21.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": {
"start": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js", "start": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js",
"start:dev": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js dev", "start:dev": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js dev",
"server": "node ./serveronly", "server": "node ./serveronly",
"install": "echo \"Installing vendor files ...\n\" && cd vendor && npm install --loglevel=error", "install-mm": "npm install --no-audit --no-fund --no-update-notifier --only=prod --omit=dev",
"install-fonts": "echo \"Installing fonts ...\n\" && cd fonts && npm install --loglevel=error", "install-mm:dev": "npm install --no-audit --no-fund --no-update-notifier",
"postinstall": "npm run install-fonts && echo \"MagicMirror² installation finished successfully! \n\"", "install-vendor": "echo \"Installing vendor files ...\n\" && cd vendor && npm install --loglevel=error --no-audit --no-fund --no-update-notifier",
"install-fonts": "echo \"Installing fonts ...\n\" && cd fonts && npm install --loglevel=error --no-audit --no-fund --no-update-notifier",
"postinstall": "npm run install-vendor && 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:electron": "NODE_ENV=test jest --selectProjects electron -i --forceExit",
@ -48,44 +50,44 @@
"homepage": "https://magicmirror.builders", "homepage": "https://magicmirror.builders",
"devDependencies": { "devDependencies": {
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest": "^26.5.3", "eslint-plugin-jest": "^27.0.4",
"eslint-plugin-jsdoc": "^39.3.3", "eslint-plugin-jsdoc": "^39.3.6",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.2.1",
"express-basic-auth": "^1.2.1", "express-basic-auth": "^1.2.1",
"husky": "^8.0.1", "husky": "^8.0.1",
"jest": "^28.1.1", "jest": "^29.0.3",
"jsdom": "^20.0.0", "jsdom": "^20.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"playwright": "^1.22.2", "playwright": "^1.26.1",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"pretty-quick": "^3.1.3", "pretty-quick": "^3.1.3",
"sinon": "^14.0.0", "sinon": "^14.0.0",
"stylelint": "^14.9.1", "stylelint": "^14.12.1",
"stylelint-config-prettier": "^9.0.3", "stylelint-config-prettier": "^9.0.3",
"stylelint-config-standard": "^26.0.0", "stylelint-config-standard": "^28.0.0",
"stylelint-prettier": "^2.0.0", "stylelint-prettier": "^2.0.0",
"suncalc": "^1.9.0" "suncalc": "^1.9.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"electron": "^19.0.6" "electron": "^19.1.0"
}, },
"dependencies": { "dependencies": {
"colors": "^1.4.0", "colors": "^1.4.0",
"console-stamp": "^3.0.6", "console-stamp": "^3.0.6",
"digest-fetch": "^1.2.1", "digest-fetch": "^1.3.0",
"eslint": "^8.18.0", "eslint": "^8.24.0",
"express": "^4.18.1", "express": "^4.18.1",
"express-ipfilter": "^1.2.0", "express-ipfilter": "^1.3.1",
"feedme": "^2.0.2", "feedme": "^2.0.2",
"helmet": "^5.1.0", "helmet": "^6.0.0",
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"luxon": "^1.28.0", "luxon": "^1.28.0",
"module-alias": "^2.2.2", "module-alias": "^2.2.2",
"moment": "^2.29.3", "moment": "^2.29.4",
"node-fetch": "^2.6.7", "node-fetch": "^2.6.7",
"node-ical": "^0.15.1", "node-ical": "^0.15.1",
"socket.io": "^4.5.1" "socket.io": "^4.5.2"
}, },
"_moduleAliases": { "_moduleAliases": {
"node_helper": "js/node_helper.js", "node_helper": "js/node_helper.js",

View File

@ -12,7 +12,7 @@ BEGIN:STANDARD
TZOFFSETFROM:+0000 TZOFFSETFROM:+0000
TZOFFSETTO:+0000 TZOFFSETTO:+0000
TZNAME:GMT TZNAME:GMT
DTSTART:19700101T00000--äüüßßß-0 DTSTART:19700101T000000
END:STANDARD END:STANDARD
END:VTIMEZONE END:VTIMEZONE
BEGIN:VEVENT BEGIN:VEVENT

View File

@ -1,31 +1,32 @@
const fetch = require("fetch"); const fetch = require("fetch");
const helpers = require("./global-setup"); const helpers = require("./global-setup");
describe("App environment", function () { describe("App environment", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/default.js"); helpers.startApplication("tests/configs/default.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
afterAll(async function () { afterAll(async () => {
await helpers.stopApplication(); await helpers.stopApplication();
}); });
it("get request from http://localhost:8080 should return 200", function (done) { it("get request from http://localhost:8080 should return 200", (done) => {
fetch("http://localhost:8080").then((res) => { fetch("http://localhost:8080").then((res) => {
done();
expect(res.status).toBe(200); expect(res.status).toBe(200);
done();
}); });
}); });
it("get request from http://localhost:8080/nothing should return 404", function (done) { it("get request from http://localhost:8080/nothing should return 404", (done) => {
fetch("http://localhost:8080/nothing").then((res) => { fetch("http://localhost:8080/nothing").then((res) => {
expect(res.status).toBe(404);
done(); done();
expect(res.status).toBe(404);
}); });
}); });
it("should show the title MagicMirror²", function () { it("should show the title MagicMirror²", (done) => {
helpers.waitForElement("title").then((elem) => { helpers.waitForElement("title").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toBe("MagicMirror²"); expect(elem.textContent).toBe("MagicMirror²");
}); });

View File

@ -1,6 +1,6 @@
const jsdom = require("jsdom"); const jsdom = require("jsdom");
exports.startApplication = function (configFilename, exec) { exports.startApplication = (configFilename, exec) => {
jest.resetModules(); jest.resetModules();
this.stopApplication(); this.stopApplication();
// Set config sample for use in test // Set config sample for use in test
@ -14,41 +14,60 @@ exports.startApplication = function (configFilename, exec) {
global.app.start(); global.app.start();
}; };
exports.stopApplication = async function () { exports.stopApplication = async () => {
if (global.app) { if (global.app) {
global.app.stop(); global.app.stop();
} }
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
}; };
exports.getDocument = function (callback) { exports.getDocument = (callback) => {
const url = "http://" + (config.address || "localhost") + ":" + (config.port || "8080"); const url = "http://" + (config.address || "localhost") + ":" + (config.port || "8080");
jsdom.JSDOM.fromURL(url, { resources: "usable", runScripts: "dangerously" }).then((dom) => { jsdom.JSDOM.fromURL(url, { resources: "usable", runScripts: "dangerously" }).then((dom) => {
dom.window.name = "jsdom"; dom.window.name = "jsdom";
dom.window.onload = function () { dom.window.onload = () => {
global.MutationObserver = dom.window.MutationObserver;
global.document = dom.window.document; global.document = dom.window.document;
callback(); callback();
}; };
}); });
}; };
exports.waitForElement = function (selector) { exports.waitForElement = (selector, ignoreValue = "") => {
return new Promise((resolve) => { return new Promise((resolve) => {
if (document.querySelector(selector) && document.querySelector(selector).value !== undefined) { let oldVal = "dummy12345";
return resolve(document.querySelector(selector)); const interval = setInterval(() => {
const element = document.querySelector(selector);
if (element) {
let newVal = element.textContent;
if (newVal === oldVal) {
clearInterval(interval);
resolve(element);
} else {
if (ignoreValue === "") {
oldVal = newVal;
} else {
if (!newVal.includes(ignoreValue)) oldVal = newVal;
} }
const observer = new MutationObserver(() => {
if (document.querySelector(selector) && document.querySelector(selector).value !== undefined) {
resolve(document.querySelector(selector));
observer.disconnect();
} }
}); }
}, 100);
observer.observe(document.body, { });
childList: true, };
subtree: true
}); exports.waitForAllElements = (selector) => {
return new Promise((resolve) => {
let oldVal = 999999;
const interval = setInterval(() => {
const element = document.querySelectorAll(selector);
if (element) {
let newVal = element.length;
if (newVal === oldVal) {
clearInterval(interval);
resolve(element);
} else {
if (newVal !== 0) oldVal = newVal;
}
}
}, 100);
}); });
}; };

View File

@ -1,16 +1,17 @@
const helpers = require("../global-setup"); const helpers = require("../global-setup");
describe("Alert module", function () { describe("Alert module", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/alert/default.js"); helpers.startApplication("tests/configs/modules/alert/default.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
afterAll(async function () { afterAll(async () => {
await helpers.stopApplication(); await helpers.stopApplication();
}); });
it("should show the welcome message", function () { it("should show the welcome message", (done) => {
helpers.waitForElement(".ns-box .ns-box-inner .light.bright.small").then((elem) => { helpers.waitForElement(".ns-box .ns-box-inner .light.bright.small").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Welcome, start was successful!"); expect(elem.textContent).toContain("Welcome, start was successful!");
}); });

View File

@ -1,14 +1,16 @@
const helpers = require("../global-setup"); const helpers = require("../global-setup");
const serverBasicAuth = require("./basic-auth.js"); const serverBasicAuth = require("./basic-auth.js");
describe("Calendar module", function () { describe("Calendar module", () => {
/** /**
* @param {string} done test done
* @param {string} element css selector * @param {string} element css selector
* @param {string} result expected number * @param {string} result expected number
* @param {string} not reverse result * @param {string} not reverse result
*/ */
function testElementLength(element, result, not) { const testElementLength = (done, element, result, not) => {
helpers.waitForElement(element).then((elem) => { helpers.waitForAllElements(element).then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
if (not === "not") { if (not === "not") {
expect(elem.length).not.toBe(result); expect(elem.length).not.toBe(result);
@ -16,147 +18,148 @@ describe("Calendar module", function () {
expect(elem.length).toBe(result); expect(elem.length).toBe(result);
} }
}); });
} };
const testTextContain = function (element, text) { const testTextContain = (done, element, text) => {
helpers.waitForElement(element).then((elem) => { helpers.waitForElement(element, "undefinedLoading").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toContain(text); expect(elem.textContent).toContain(text);
}); });
}; };
afterAll(async function () { afterAll(async () => {
await helpers.stopApplication(); await helpers.stopApplication();
}); });
describe("Default configuration", function () { describe("Default configuration", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/calendar/default.js"); helpers.startApplication("tests/configs/modules/calendar/default.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should show the default maximumEntries of 10", () => { it("should show the default maximumEntries of 10", (done) => {
testElementLength(".calendar .event", 10); testElementLength(done, ".calendar .event", 10);
}); });
it("should show the default calendar symbol in each event", () => { it("should show the default calendar symbol in each event", (done) => {
testElementLength(".calendar .event .fa-calendar-alt", 0, "not"); testElementLength(done, ".calendar .event .fa-calendar-alt", 0, "not");
}); });
}); });
describe("Custom configuration", function () { describe("Custom configuration", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/calendar/custom.js"); helpers.startApplication("tests/configs/modules/calendar/custom.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should show the custom maximumEntries of 4", () => { it("should show the custom maximumEntries of 4", (done) => {
testElementLength(".calendar .event", 4); testElementLength(done, ".calendar .event", 4);
}); });
it("should show the custom calendar symbol in each event", () => { it("should show the custom calendar symbol in each event", (done) => {
testElementLength(".calendar .event .fa-birthday-cake", 4); testElementLength(done, ".calendar .event .fa-birthday-cake", 4);
}); });
it("should show two custom icons for repeating events", () => { it("should show two custom icons for repeating events", (done) => {
testElementLength(".calendar .event .fa-undo", 2); testElementLength(done, ".calendar .event .fa-undo", 2);
}); });
it("should show two custom icons for day events", () => { it("should show two custom icons for day events", (done) => {
testElementLength(".calendar .event .fa-calendar-day", 2); testElementLength(done, ".calendar .event .fa-calendar-day", 2);
}); });
}); });
describe("Recurring event", function () { describe("Recurring event", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/calendar/recurring.js"); helpers.startApplication("tests/configs/modules/calendar/recurring.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should show the recurring birthday event 6 times", () => { it("should show the recurring birthday event 6 times", (done) => {
testElementLength(".calendar .event", 6); testElementLength(done, ".calendar .event", 6);
}); });
}); });
process.setMaxListeners(0); process.setMaxListeners(0);
for (let i = -12; i < 12; i++) { for (let i = -12; i < 12; i++) {
describe("Recurring event per timezone", function () { describe("Recurring event per timezone", () => {
beforeAll(function (done) { beforeAll((done) => {
Date.prototype.getTimezoneOffset = function () { Date.prototype.getTimezoneOffset = () => {
return i * 60; return i * 60;
}; };
helpers.startApplication("tests/configs/modules/calendar/recurring.js"); helpers.startApplication("tests/configs/modules/calendar/recurring.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it('should contain text "Mar 25th" in timezone UTC ' + -i, () => { it('should contain text "Mar 25th" in timezone UTC ' + -i, (done) => {
testTextContain(".calendar", "Mar 25th"); testTextContain(done, ".calendar", "Mar 25th");
}); });
}); });
} }
describe("Changed port", function () { describe("Changed port", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/calendar/changed-port.js"); helpers.startApplication("tests/configs/modules/calendar/changed-port.js");
serverBasicAuth.listen(8010); serverBasicAuth.listen(8010);
helpers.getDocument(done); helpers.getDocument(done);
}); });
afterAll(function (done) { afterAll((done) => {
serverBasicAuth.close(done()); serverBasicAuth.close(done());
}); });
it("should return TestEvents", function () { it("should return TestEvents", (done) => {
testElementLength(".calendar .event", 0, "not"); testElementLength(done, ".calendar .event", 0, "not");
}); });
}); });
describe("Basic auth", function () { describe("Basic auth", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/calendar/basic-auth.js"); helpers.startApplication("tests/configs/modules/calendar/basic-auth.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should return TestEvents", function () { it("should return TestEvents", (done) => {
testElementLength(".calendar .event", 0, "not"); testElementLength(done, ".calendar .event", 0, "not");
}); });
}); });
describe("Basic auth by default", function () { describe("Basic auth by default", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/calendar/auth-default.js"); helpers.startApplication("tests/configs/modules/calendar/auth-default.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should return TestEvents", function () { it("should return TestEvents", (done) => {
testElementLength(".calendar .event", 0, "not"); testElementLength(done, ".calendar .event", 0, "not");
}); });
}); });
describe("Basic auth backward compatibility configuration: DEPRECATED", function () { describe("Basic auth backward compatibility configuration: DEPRECATED", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/calendar/old-basic-auth.js"); helpers.startApplication("tests/configs/modules/calendar/old-basic-auth.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should return TestEvents", function () { it("should return TestEvents", (done) => {
testElementLength(".calendar .event", 0, "not"); testElementLength(done, ".calendar .event", 0, "not");
}); });
}); });
describe("Fail Basic auth", function () { describe("Fail Basic auth", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/calendar/fail-basic-auth.js"); helpers.startApplication("tests/configs/modules/calendar/fail-basic-auth.js");
serverBasicAuth.listen(8020); serverBasicAuth.listen(8020);
helpers.getDocument(done); helpers.getDocument(done);
}); });
afterAll(function (done) { afterAll((done) => {
serverBasicAuth.close(done()); serverBasicAuth.close(done());
}); });
it("should show Unauthorized error", function () { it("should show Unauthorized error", (done) => {
testTextContain(".calendar", "Error in the calendar module. Authorization failed"); testTextContain(done, ".calendar", "Error in the calendar module. Authorization failed");
}); });
}); });
}); });

View File

@ -1,72 +1,73 @@
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", () => {
afterAll(async function () { afterAll(async () => {
await helpers.stopApplication(); await helpers.stopApplication();
}); });
const testMatch = function (element, regex) { const testMatch = (done, element, regex) => {
helpers.waitForElement(element).then((elem) => { helpers.waitForElement(element).then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toMatch(regex); expect(elem.textContent).toMatch(regex);
}); });
}; };
describe("with default 24hr clock config", function () { describe("with default 24hr clock config", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/clock/es/clock_24hr.js"); helpers.startApplication("tests/configs/modules/clock/es/clock_24hr.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("shows date with correct format", function () { it("shows date with correct format", (done) => {
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}$/;
testMatch(".clock .date", dateRegex); testMatch(done, ".clock .date", dateRegex);
}); });
it("shows time in 24hr format", function () { it("shows time in 24hr format", (done) => {
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$/;
testMatch(".clock .time", timeRegex); testMatch(done, ".clock .time", timeRegex);
}); });
}); });
describe("with default 12hr clock config", function () { describe("with default 12hr clock config", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/clock/es/clock_12hr.js"); helpers.startApplication("tests/configs/modules/clock/es/clock_12hr.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("shows date with correct format", function () { it("shows date with correct format", (done) => {
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}$/;
testMatch(".clock .date", dateRegex); testMatch(done, ".clock .date", dateRegex);
}); });
it("shows time in 12hr format", function () { it("shows time in 12hr format", (done) => {
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$/;
testMatch(".clock .time", timeRegex); testMatch(done, ".clock .time", timeRegex);
}); });
}); });
describe("with showPeriodUpper config enabled", function () { describe("with showPeriodUpper config enabled", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/clock/es/clock_showPeriodUpper.js"); helpers.startApplication("tests/configs/modules/clock/es/clock_showPeriodUpper.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("shows 12hr time with upper case AM/PM", function () { it("shows 12hr time with upper case AM/PM", (done) => {
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$/;
testMatch(".clock .time", timeRegex); testMatch(done, ".clock .time", timeRegex);
}); });
}); });
describe("with showWeek config enabled", function () { describe("with showWeek config enabled", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/clock/es/clock_showWeek.js"); helpers.startApplication("tests/configs/modules/clock/es/clock_showWeek.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("shows week with correct format", function () { it("shows week with correct format", (done) => {
const weekRegex = /^Semana [0-9]{1,2}$/; const weekRegex = /^Semana [0-9]{1,2}$/;
testMatch(".clock .week", weekRegex); testMatch(done, ".clock .week", weekRegex);
}); });
}); });
}); });

View File

@ -1,118 +1,121 @@
const helpers = require("../global-setup"); const helpers = require("../global-setup");
const moment = require("moment"); const moment = require("moment");
describe("Clock module", function () { describe("Clock module", () => {
afterAll(async function () { afterAll(async () => {
await helpers.stopApplication(); await helpers.stopApplication();
}); });
const testMatch = function (element, regex) { const testMatch = (done, element, regex) => {
helpers.waitForElement(element).then((elem) => { helpers.waitForElement(element).then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toMatch(regex); expect(elem.textContent).toMatch(regex);
}); });
}; };
describe("with default 24hr clock config", function () { describe("with default 24hr clock config", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/clock/clock_24hr.js"); helpers.startApplication("tests/configs/modules/clock/clock_24hr.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should show the date in the correct format", function () { it("should show the date in the correct format", (done) => {
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}$/;
testMatch(".clock .date", dateRegex); testMatch(done, ".clock .date", dateRegex);
}); });
it("should show the time in 24hr format", function () { it("should show the time in 24hr format", (done) => {
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$/;
testMatch(".clock .time", timeRegex); testMatch(done, ".clock .time", timeRegex);
}); });
}); });
describe("with default 12hr clock config", function () { describe("with default 12hr clock config", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/clock/clock_12hr.js"); helpers.startApplication("tests/configs/modules/clock/clock_12hr.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should show the date in the correct format", function () { it("should show the date in the correct format", (done) => {
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}$/;
testMatch(".clock .date", dateRegex); testMatch(done, ".clock .date", dateRegex);
}); });
it("should show the time in 12hr format", function () { it("should show the time in 12hr format", (done) => {
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$/;
testMatch(".clock .time", timeRegex); testMatch(done, ".clock .time", timeRegex);
}); });
}); });
describe("with showPeriodUpper config enabled", function () { describe("with showPeriodUpper config enabled", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/clock/clock_showPeriodUpper.js"); helpers.startApplication("tests/configs/modules/clock/clock_showPeriodUpper.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should show 12hr time with upper case AM/PM", function () { it("should show 12hr time with upper case AM/PM", (done) => {
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$/;
testMatch(".clock .time", timeRegex); testMatch(done, ".clock .time", timeRegex);
}); });
}); });
describe("with displaySeconds config disabled", function () { describe("with displaySeconds config disabled", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/clock/clock_displaySeconds_false.js"); helpers.startApplication("tests/configs/modules/clock/clock_displaySeconds_false.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should show 12hr time without seconds am/pm", function () { it("should show 12hr time without seconds am/pm", (done) => {
const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[ap]m$/; const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[ap]m$/;
testMatch(".clock .time", timeRegex); testMatch(done, ".clock .time", timeRegex);
}); });
}); });
describe("with showTime config disabled", function () { describe("with showTime config disabled", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/clock/clock_showTime.js"); helpers.startApplication("tests/configs/modules/clock/clock_showTime.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should show not show the time when digital clock is shown", function () { it("should not show the time when digital clock is shown", (done) => {
helpers.waitForElement(".clock .digital .time").then((elem) => { const elem = document.querySelector(".clock .digital .time");
done();
expect(elem).toBe(null); expect(elem).toBe(null);
}); });
}); });
});
describe("with showWeek config enabled", function () { describe("with showWeek config enabled", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/clock/clock_showWeek.js"); helpers.startApplication("tests/configs/modules/clock/clock_showWeek.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should show the week in the correct format", function () { it("should show the week in the correct format", (done) => {
const weekRegex = /^Week [0-9]{1,2}$/; const weekRegex = /^Week [0-9]{1,2}$/;
testMatch(".clock .week", weekRegex); testMatch(done, ".clock .week", weekRegex);
}); });
it("should show the week with the correct number of week of year", function () { it("should show the week with the correct number of week of year", (done) => {
const currentWeekNumber = moment().week(); const currentWeekNumber = moment().week();
const weekToShow = "Week " + currentWeekNumber; const weekToShow = "Week " + currentWeekNumber;
helpers.waitForElement(".clock .week").then((elem) => { helpers.waitForElement(".clock .week").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toBe(weekToShow); expect(elem.textContent).toBe(weekToShow);
}); });
}); });
}); });
describe("with analog clock face enabled", function () { describe("with analog clock face enabled", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/clock/clock_analog.js"); helpers.startApplication("tests/configs/modules/clock/clock_analog.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should show the analog clock face", () => { it("should show the analog clock face", (done) => {
helpers.waitForElement(".clockCircle").then((elem) => { helpers.waitForElement(".clockCircle").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
}); });
}); });

View File

@ -3,87 +3,95 @@ const helpers = require("../global-setup");
/** /**
* move similar tests in function doTest * move similar tests in function doTest
* *
* @param {string} done test done
* @param {Array} complimentsArray The array of compliments. * @param {Array} complimentsArray The array of compliments.
*/ */
function doTest(complimentsArray) { const doTest = (done, complimentsArray) => {
helpers.waitForElement(".compliments").then((elem) => { helpers.waitForElement(".compliments").then((elem) => {
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
helpers.waitForElement(".module-content").then((elem) => { helpers.waitForElement(".module-content").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(complimentsArray).toContain(elem.textContent); expect(complimentsArray).toContain(elem.textContent);
}); });
}); });
} };
describe("Compliments module", function () { describe("Compliments module", () => {
afterAll(async function () { afterAll(async () => {
await helpers.stopApplication(); await helpers.stopApplication();
}); });
describe("parts of days", function () { describe("parts of days", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js"); helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("if Morning compliments for that part of day", function () { it("if Morning compliments for that part of day", (done) => {
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
doTest(["Hi", "Good Morning", "Morning test"]); doTest(done, ["Hi", "Good Morning", "Morning test"]);
} else {
done();
} }
}); });
it("if Afternoon show Compliments for that part of day", function () { it("if Afternoon show Compliments for that part of day", (done) => {
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
doTest(["Hello", "Good Afternoon", "Afternoon test"]); doTest(done, ["Hello", "Good Afternoon", "Afternoon test"]);
} else {
done();
} }
}); });
it("if Evening show Compliments for that part of day", function () { it("if Evening show Compliments for that part of day", (done) => {
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
doTest(["Hello There", "Good Evening", "Evening test"]); doTest(done, ["Hello There", "Good Evening", "Evening test"]);
} else {
done();
} }
}); });
}); });
describe("Feature anytime in compliments module", function () { describe("Feature anytime in compliments module", () => {
describe("Set anytime and empty compliments for morning, evening and afternoon ", function () { describe("Set anytime and empty compliments for morning, evening and afternoon ", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/compliments/compliments_anytime.js"); helpers.startApplication("tests/configs/modules/compliments/compliments_anytime.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("Show anytime because if configure empty parts of day compliments and set anytime compliments", function () { it("Show anytime because if configure empty parts of day compliments and set anytime compliments", (done) => {
doTest(["Anytime here"]); doTest(done, ["Anytime here"]);
}); });
}); });
describe("Only anytime present in configuration compliments", function () { describe("Only anytime present in configuration compliments", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/compliments/compliments_only_anytime.js"); helpers.startApplication("tests/configs/modules/compliments/compliments_only_anytime.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("Show anytime compliments", function () { it("Show anytime compliments", (done) => {
doTest(["Anytime here"]); doTest(done, ["Anytime here"]);
}); });
}); });
}); });
describe("Feature date in compliments module", function () { describe("Feature date in compliments module", () => {
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", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/compliments/compliments_date.js"); helpers.startApplication("tests/configs/modules/compliments/compliments_date.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("Show happy new year compliment on new years day", function () { it("Show happy new year compliment on new years day", (done) => {
doTest(["Happy new year!"]); doTest(done, ["Happy new year!"]);
}); });
}); });
}); });

View File

@ -1,32 +1,34 @@
const helpers = require("../global-setup"); const helpers = require("../global-setup");
describe("Test helloworld module", function () { describe("Test helloworld module", () => {
afterAll(async function () { afterAll(async () => {
await helpers.stopApplication(); await helpers.stopApplication();
}); });
describe("helloworld set config text", function () { describe("helloworld set config text", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/helloworld/helloworld.js"); helpers.startApplication("tests/configs/modules/helloworld/helloworld.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("Test message helloworld module", function () { it("Test message helloworld module", (done) => {
helpers.waitForElement(".helloworld").then((elem) => { helpers.waitForElement(".helloworld").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Test HelloWorld Module"); expect(elem.textContent).toContain("Test HelloWorld Module");
}); });
}); });
}); });
describe("helloworld default config text", function () { describe("helloworld default config text", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/helloworld/helloworld_default.js"); helpers.startApplication("tests/configs/modules/helloworld/helloworld_default.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("Test message helloworld module", function () { it("Test message helloworld module", (done) => {
helpers.waitForElement(".helloworld").then((elem) => { helpers.waitForElement(".helloworld").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Hello World!"); expect(elem.textContent).toContain("Hello World!");
}); });

View File

@ -1,80 +1,88 @@
const helpers = require("../global-setup"); const helpers = require("../global-setup");
describe("Newsfeed module", function () { describe("Newsfeed module", () => {
afterAll(async function () { afterAll(async () => {
await helpers.stopApplication(); await helpers.stopApplication();
}); });
describe("Default configuration", function () { describe("Default configuration", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/newsfeed/default.js"); helpers.startApplication("tests/configs/modules/newsfeed/default.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should show the newsfeed title", function () { it("should show the newsfeed title", (done) => {
helpers.waitForElement(".newsfeed .newsfeed-source").then((elem) => { helpers.waitForElement(".newsfeed .newsfeed-source").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Rodrigo Ramirez Blog"); expect(elem.textContent).toContain("Rodrigo Ramirez Blog");
}); });
}); });
it("should show the newsfeed article", function () { it("should show the newsfeed article", (done) => {
helpers.waitForElement(".newsfeed .newsfeed-title").then((elem) => { helpers.waitForElement(".newsfeed .newsfeed-title").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toContain("QPanel"); expect(elem.textContent).toContain("QPanel");
}); });
}); });
it("should NOT show the newsfeed description", () => { it("should NOT show the newsfeed description", (done) => {
helpers.waitForElement(".newsfeed .newsfeed-desc").then((elem) => { helpers.waitForElement(".newsfeed").then((elem) => {
expect(elem).toBe(null); const element = document.querySelector(".newsfeed .newsfeed-desc");
done();
expect(element).toBe(null);
}); });
}); });
}); });
describe("Custom configuration", function () { describe("Custom configuration", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/newsfeed/prohibited_words.js"); helpers.startApplication("tests/configs/modules/newsfeed/prohibited_words.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should not show articles with prohibited words", function () { it("should not show articles with prohibited words", (done) => {
helpers.waitForElement(".newsfeed .newsfeed-title").then((elem) => { helpers.waitForElement(".newsfeed .newsfeed-title").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Problema VirtualBox"); expect(elem.textContent).toContain("Problema VirtualBox");
}); });
}); });
it("should show the newsfeed description", () => { it("should show the newsfeed description", (done) => {
helpers.waitForElement(".newsfeed .newsfeed-desc").then((elem) => { helpers.waitForElement(".newsfeed .newsfeed-desc").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent.length).not.toBe(0); expect(elem.textContent.length).not.toBe(0);
}); });
}); });
}); });
describe("Invalid configuration", function () { describe("Invalid configuration", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/newsfeed/incorrect_url.js"); helpers.startApplication("tests/configs/modules/newsfeed/incorrect_url.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should show malformed url warning", function () { it("should show malformed url warning", (done) => {
helpers.waitForElement(".newsfeed .small").then((elem) => { helpers.waitForElement(".newsfeed .small", "No news at the moment.").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Error in the Newsfeed module. Malformed url."); expect(elem.textContent).toContain("Error in the Newsfeed module. Malformed url.");
}); });
}); });
}); });
describe("Ignore items", function () { describe("Ignore items", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/newsfeed/ignore_items.js"); helpers.startApplication("tests/configs/modules/newsfeed/ignore_items.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
it("should show empty items info message", function () { it("should show empty items info message", (done) => {
helpers.waitForElement(".newsfeed .small").then((elem) => { helpers.waitForElement(".newsfeed .small").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toContain("No news at the moment."); expect(elem.textContent).toContain("No news at the moment.");
}); });

View File

@ -4,13 +4,15 @@ const path = require("path");
const fs = require("fs"); const fs = require("fs");
const { generateWeather, generateWeatherForecast } = require("./mocks"); const { generateWeather, generateWeatherForecast } = require("./mocks");
describe("Weather module", function () { describe("Weather module", () => {
/** /**
* @param {string} done test done
* @param {string} element css selector * @param {string} element css selector
* @param {string} result Expected text in given selector * @param {string} result Expected text in given selector
*/ */
function getText(element, result) { const getText = (done, element, result) => {
helpers.waitForElement(element).then((elem) => { helpers.waitForElement(element).then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect( expect(
elem.textContent elem.textContent
@ -19,14 +21,14 @@ describe("Weather module", function () {
.replace(/[ ]+/g, " ") .replace(/[ ]+/g, " ")
).toBe(result); ).toBe(result);
}); });
} };
/** /**
* @param {string} configFile path to configuration file * @param {string} configFile path to configuration file
* @param {string} additionalMockData special data for mocking * @param {string} additionalMockData special data for mocking
* @param {string} callback callback * @param {string} callback callback
*/ */
function startApp(configFile, additionalMockData, callback) { const startApp = (configFile, additionalMockData, callback) => {
let mockWeather; let mockWeather;
if (configFile.includes("forecast")) { if (configFile.includes("forecast")) {
mockWeather = generateWeatherForecast(additionalMockData); mockWeather = generateWeatherForecast(additionalMockData);
@ -38,94 +40,98 @@ describe("Weather module", function () {
fs.writeFileSync(path.resolve(__dirname + "../../../../config/config.js"), content); fs.writeFileSync(path.resolve(__dirname + "../../../../config/config.js"), content);
helpers.startApplication(""); helpers.startApplication("");
helpers.getDocument(callback); helpers.getDocument(callback);
} };
afterAll(async function () { afterAll(async () => {
await helpers.stopApplication(); await helpers.stopApplication();
}); });
describe("Current weather", function () { describe("Current weather", () => {
describe("Default configuration", function () { describe("Default configuration", () => {
beforeAll(function (done) { beforeAll((done) => {
startApp("tests/configs/modules/weather/currentweather_default.js", {}, done); startApp("tests/configs/modules/weather/currentweather_default.js", {}, done);
}); });
it("should render wind speed and wind direction", function () { it("should render wind speed and wind direction", (done) => {
getText(".weather .normal.medium span:nth-child(2)", "6 WSW"); getText(done, ".weather .normal.medium span:nth-child(2)", "6 WSW"); // now "12"
}); });
it("should render temperature with icon", function () { it("should render temperature with icon", (done) => {
getText(".weather .large.light span.bright", "1.5°"); getText(done, ".weather .large.light span.bright", "1.5°"); // now "1°C"
}); });
it("should render feels like temperature", function () { it("should render feels like temperature", (done) => {
getText(".weather .normal.medium.feelslike span.dimmed", "Feels like -5.6°"); getText(done, ".weather .normal.medium.feelslike span.dimmed", "Feels like -5.6°"); // now "Feels like -6°C"
}); });
}); });
describe("Default configuration with sunrise", function () { describe("Default configuration with sunrise", () => {
beforeAll(function (done) { beforeAll((done) => {
const sunrise = moment().startOf("day").unix(); const sunrise = moment().startOf("day").unix();
const sunset = moment().startOf("day").unix(); const sunset = moment().startOf("day").unix();
startApp("tests/configs/modules/weather/currentweather_default.js", { sys: { sunrise, sunset } }, done); startApp("tests/configs/modules/weather/currentweather_default.js", { sys: { sunrise, sunset } }, done);
}); });
it("should render sunrise", function () { it("should render sunrise", (done) => {
getText(".weather .normal.medium span:nth-child(4)", "12:00 am"); getText(done, ".weather .normal.medium span:nth-child(4)", "12:00 am");
}); });
}); });
describe("Default configuration with sunset", function () { describe("Default configuration with sunset", () => {
beforeAll(function (done) { beforeAll((done) => {
const sunrise = moment().startOf("day").unix(); const sunrise = moment().startOf("day").unix();
const sunset = moment().endOf("day").unix(); const sunset = moment().endOf("day").unix();
startApp("tests/configs/modules/weather/currentweather_default.js", { sys: { sunrise, sunset } }, done); startApp("tests/configs/modules/weather/currentweather_default.js", { sys: { sunrise, sunset } }, done);
}); });
it("should render sunset", function () { it("should render sunset", (done) => {
getText(".weather .normal.medium span:nth-child(4)", "11:59 pm"); getText(done, ".weather .normal.medium span:nth-child(4)", "11:59 pm");
}); });
}); });
}); });
describe("Compliments Integration", function () { describe("Compliments Integration", () => {
beforeAll(function (done) { beforeAll((done) => {
startApp("tests/configs/modules/weather/currentweather_compliments.js", {}, done); startApp("tests/configs/modules/weather/currentweather_compliments.js", {}, done);
}); });
it("should render a compliment based on the current weather", function () { it("should render a compliment based on the current weather", (done) => {
getText(".compliments .module-content span", "snow"); getText(done, ".compliments .module-content span", "snow");
}); });
}); });
describe("Configuration Options", function () { describe("Configuration Options", () => {
beforeAll(function (done) { beforeAll((done) => {
startApp("tests/configs/modules/weather/currentweather_options.js", {}, done); startApp("tests/configs/modules/weather/currentweather_options.js", {}, done);
}); });
it("should render useBeaufort = false", function () { it("should render useBeaufort = false", (done) => {
getText(".weather .normal.medium span:nth-child(2)", "12"); getText(done, ".weather .normal.medium span:nth-child(2)", "12");
}); });
it("should render showWindDirectionAsArrow = true", function () { it("should render showWindDirectionAsArrow = true", (done) => {
helpers.waitForElement(".weather .normal.medium sup i.fa-long-arrow-alt-up").then((elem) => { helpers.waitForElement(".weather .normal.medium sup i.fa-long-arrow-alt-up").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.outerHTML).toContain("transform:rotate(250deg);"); expect(elem.outerHTML).toContain("transform:rotate(250deg);");
}); });
}); });
it("should render showHumidity = true", function () { it("should render showHumidity = true", (done) => {
getText(".weather .normal.medium span:nth-child(3)", "93.7"); getText(done, ".weather .normal.medium span:nth-child(3)", "93.7");
}); });
it("should render degreeLabel = true", function () { it("should render degreeLabel = true for temp", (done) => {
getText(".weather .large.light span.bright", "1°C"); getText(done, ".weather .large.light span.bright", "1°C");
getText(".weather .normal.medium.feelslike span.dimmed", "Feels like -6°C"); });
it("should render degreeLabel = true for feels like", (done) => {
getText(done, ".weather .normal.medium.feelslike span.dimmed", "Feels like -6°C");
}); });
}); });
describe("Current weather units", function () { describe("Current weather units", () => {
beforeAll(function (done) { beforeAll((done) => {
startApp( startApp(
"tests/configs/modules/weather/currentweather_units.js", "tests/configs/modules/weather/currentweather_units.js",
{ {
@ -142,98 +148,108 @@ describe("Weather module", function () {
); );
}); });
it("should render imperial units", function () { it("should render imperial units for wind", (done) => {
getText(".weather .normal.medium span:nth-child(2)", "6 WSW"); getText(done, ".weather .normal.medium span:nth-child(2)", "6 WSW");
getText(".weather .large.light span.bright", "34,7°");
getText(".weather .normal.medium.feelslike span.dimmed", "Feels like 22,0°");
}); });
it("should render custom decimalSymbol = ','", function () { it("should render imperial units for temp", (done) => {
getText(".weather .normal.medium span:nth-child(3)", "93,7"); getText(done, ".weather .large.light span.bright", "34,7°");
getText(".weather .large.light span.bright", "34,7°"); });
getText(".weather .normal.medium.feelslike span.dimmed", "Feels like 22,0°");
it("should render imperial units for feels like", (done) => {
getText(done, ".weather .normal.medium.feelslike span.dimmed", "Feels like 22,0°");
});
it("should render custom decimalSymbol = ',' for humidity", (done) => {
getText(done, ".weather .normal.medium span:nth-child(3)", "93,7");
});
it("should render custom decimalSymbol = ',' for temp", (done) => {
getText(done, ".weather .large.light span.bright", "34,7°");
});
it("should render custom decimalSymbol = ',' for feels like", (done) => {
getText(done, ".weather .normal.medium.feelslike span.dimmed", "Feels like 22,0°");
}); });
}); });
describe("Weather Forecast", function () { describe("Weather Forecast", () => {
describe("Default configuration", function () { describe("Default configuration", () => {
beforeAll(function (done) { beforeAll((done) => {
startApp("tests/configs/modules/weather/forecastweather_default.js", {}, done); startApp("tests/configs/modules/weather/forecastweather_default.js", {}, done);
}); });
it("should render days", function () {
const days = ["Today", "Tomorrow", "Sun", "Mon", "Tue"]; const days = ["Today", "Tomorrow", "Sun", "Mon", "Tue"];
for (const [index, day] of days.entries()) { for (const [index, day] of days.entries()) {
getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(1)`, day); it("should render day " + day, (done) => {
} getText(done, `.weather table.small tr:nth-child(${index + 1}) td:nth-child(1)`, day);
}); });
}
it("should render icons", function () {
const icons = ["day-cloudy", "rain", "day-sunny", "day-sunny", "day-sunny"]; const icons = ["day-cloudy", "rain", "day-sunny", "day-sunny", "day-sunny"];
for (const [index, icon] of icons.entries()) { for (const [index, icon] of icons.entries()) {
it("should render icon " + icon, (done) => {
helpers.waitForElement(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(2) span.wi-${icon}`).then((elem) => { helpers.waitForElement(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(2) span.wi-${icon}`).then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
}); });
}
}); });
it("should render max temperatures", function () {
const temperatures = ["24.4°", "21.0°", "22.9°", "23.4°", "20.6°"];
for (const [index, temp] of temperatures.entries()) {
getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(3)`, temp);
} }
const maxTemps = ["24.4°", "21.0°", "22.9°", "23.4°", "20.6°"];
for (const [index, temp] of maxTemps.entries()) {
it("should render max temperature " + temp, (done) => {
getText(done, `.weather table.small tr:nth-child(${index + 1}) td:nth-child(3)`, temp);
});
}
const minTemps = ["15.3°", "13.6°", "13.8°", "13.9°", "10.9°"];
for (const [index, temp] of minTemps.entries()) {
it("should render min temperature " + temp, (done) => {
getText(done, `.weather table.small tr:nth-child(${index + 1}) td:nth-child(4)`, temp);
}); });
it("should render min temperatures", function () {
const temperatures = ["15.3°", "13.6°", "13.8°", "13.9°", "10.9°"];
for (const [index, temp] of temperatures.entries()) {
getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(4)`, temp);
} }
});
it("should render fading of rows", function () {
const opacities = [1, 1, 0.8, 0.5333333333333333, 0.2666666666666667]; const opacities = [1, 1, 0.8, 0.5333333333333333, 0.2666666666666667];
for (const [index, opacity] of opacities.entries()) { for (const [index, opacity] of opacities.entries()) {
it("should render fading of rows with opacity=" + opacity, (done) => {
helpers.waitForElement(`.weather table.small tr:nth-child(${index + 1})`).then((elem) => { helpers.waitForElement(`.weather table.small tr:nth-child(${index + 1})`).then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.outerHTML).toContain(`<tr style="opacity: ${opacity};">`); expect(elem.outerHTML).toContain(`<tr style="opacity: ${opacity};">`);
}); });
});
} }
}); });
});
describe("Absolute configuration", function () { describe("Absolute configuration", () => {
beforeAll(function (done) { beforeAll((done) => {
startApp("tests/configs/modules/weather/forecastweather_absolute.js", {}, done); startApp("tests/configs/modules/weather/forecastweather_absolute.js", {}, done);
}); });
it("should render days", function () {
const days = ["Fri", "Sat", "Sun", "Mon", "Tue"]; const days = ["Fri", "Sat", "Sun", "Mon", "Tue"];
for (const [index, day] of days.entries()) { for (const [index, day] of days.entries()) {
getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(1)`, day); it("should render day " + day, (done) => {
getText(done, `.weather table.small tr:nth-child(${index + 1}) td:nth-child(1)`, day);
});
} }
}); });
});
describe("Configuration Options", function () { describe("Configuration Options", () => {
beforeAll(function (done) { beforeAll((done) => {
startApp("tests/configs/modules/weather/forecastweather_options.js", {}, done); startApp("tests/configs/modules/weather/forecastweather_options.js", {}, done);
}); });
it("should render custom table class", function () { it("should render custom table class", (done) => {
helpers.waitForElement(".weather table.myTableClass").then((elem) => { helpers.waitForElement(".weather table.myTableClass").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
}); });
}); });
it("should render colored rows", function () { it("should render colored rows", (done) => {
helpers.waitForElement(".weather table.myTableClass").then((table) => { helpers.waitForElement(".weather table.myTableClass").then((table) => {
done();
expect(table).not.toBe(null); expect(table).not.toBe(null);
expect(table.rows).not.toBe(null); expect(table.rows).not.toBe(null);
expect(table.rows.length).toBe(5); expect(table.rows.length).toBe(5);
@ -241,18 +257,17 @@ describe("Weather module", function () {
}); });
}); });
describe("Forecast weather units", function () { describe("Forecast weather units", () => {
beforeAll(function (done) { beforeAll((done) => {
startApp("tests/configs/modules/weather/forecastweather_units.js", {}, done); startApp("tests/configs/modules/weather/forecastweather_units.js", {}, done);
}); });
it("should render custom decimalSymbol = '_'", function () {
const temperatures = ["24_4°", "21_0°", "22_9°", "23_4°", "20_6°"]; const temperatures = ["24_4°", "21_0°", "22_9°", "23_4°", "20_6°"];
for (const [index, temp] of temperatures.entries()) { for (const [index, temp] of temperatures.entries()) {
getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(3)`, temp); it("should render custom decimalSymbol = '_' for temp " + temp, (done) => {
getText(done, `.weather table.small tr:nth-child(${index + 1}) td:nth-child(3)`, temp);
});
} }
}); });
}); });
}); });
});

View File

@ -1,24 +1,26 @@
const helpers = require("./global-setup"); const helpers = require("./global-setup");
describe("Display of modules", function () { describe("Display of modules", () => {
beforeAll(function (done) { beforeAll(function (done) {
helpers.startApplication("tests/configs/modules/display.js"); helpers.startApplication("tests/configs/modules/display.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
afterAll(async function () { afterAll(async () => {
await helpers.stopApplication(); await helpers.stopApplication();
}); });
it("should show the test header", function () { it("should show the test header", (done) => {
helpers.waitForElement("#module_0_helloworld .module-header").then((elem) => { helpers.waitForElement("#module_0_helloworld .module-header").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
// textContent gibt hier lowercase zurück, das uppercase wird durch css realisiert, was daher nicht in textContent landet // textContent gibt hier lowercase zurück, das uppercase wird durch css realisiert, was daher nicht in textContent landet
expect(elem.textContent).toBe("test_header"); expect(elem.textContent).toBe("test_header");
}); });
}); });
it("should show no header if no header text is specified", function () { it("should show no header if no header text is specified", (done) => {
helpers.waitForElement("#module_1_helloworld .module-header").then((elem) => { helpers.waitForElement("#module_1_helloworld .module-header").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toBe("undefined"); expect(elem.textContent).toBe("undefined");
}); });

View File

@ -1,11 +1,11 @@
const helpers = require("./global-setup"); const helpers = require("./global-setup");
describe("Position of modules", function () { describe("Position of modules", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/modules/positions.js"); helpers.startApplication("tests/configs/modules/positions.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
afterAll(async function () { afterAll(async () => {
await helpers.stopApplication(); await helpers.stopApplication();
}); });
@ -13,8 +13,9 @@ describe("Position of modules", function () {
for (const position of positions) { for (const position of positions) {
const className = position.replace("_", "."); const className = position.replace("_", ".");
it("should show text in " + position, function () { it("should show text in " + position, (done) => {
helpers.waitForElement("." + className).then((elem) => { helpers.waitForElement("." + className).then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toContain("Text in " + position); expect(elem.textContent).toContain("Text in " + position);
}); });

View File

@ -164,7 +164,7 @@ describe("Translations", function () {
dom.window.onload = function () { dom.window.onload = function () {
const { Translator } = dom.window; const { Translator } = dom.window;
Translator.load(mmm, translations.en, false, function () { Translator.load(mmm, translations.de, false, function () {
base = Object.keys(Translator.translations[mmm.name]).sort(); base = Object.keys(Translator.translations[mmm.name]).sort();
done(); done();
}); });
@ -175,8 +175,10 @@ describe("Translations", function () {
console.log(missing); console.log(missing);
}); });
// Using German as the base rather than English, since
// at least one translated word doesn't exist in English.
for (let language in translations) { for (let language in translations) {
if (language === "en") { if (language === "de") {
continue; continue;
} }

View File

@ -1,23 +1,25 @@
const helpers = require("./global-setup"); const helpers = require("./global-setup");
describe("Check configuration without modules", function () { describe("Check configuration without modules", () => {
beforeAll(function (done) { beforeAll((done) => {
helpers.startApplication("tests/configs/without_modules.js"); helpers.startApplication("tests/configs/without_modules.js");
helpers.getDocument(done); helpers.getDocument(done);
}); });
afterAll(async function () { afterAll(async () => {
await helpers.stopApplication(); await helpers.stopApplication();
}); });
it("Show the message MagicMirror² title", function () { it("Show the message MagicMirror² title", (done) => {
helpers.waitForElement("#module_1_helloworld .module-content").then((elem) => { helpers.waitForElement("#module_1_helloworld .module-content").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toContain("MagicMirror²"); expect(elem.textContent).toContain("MagicMirror²");
}); });
}); });
it("Show the text Michael's website", function () { it("Show the text Michael's website", (done) => {
helpers.waitForElement("#module_5_helloworld .module-content").then((elem) => { helpers.waitForElement("#module_5_helloworld .module-content").then((elem) => {
done();
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.textContent).toContain("www.michaelteeuw.nl"); expect(elem.textContent).toContain("www.michaelteeuw.nl");
}); });

View File

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Updatenotification custom module returns status information without hash 1`] = ` exports[`Updatenotification custom module returns status information without hash 1`] = `
Object { {
"behind": 7, "behind": 7,
"current": "master", "current": "master",
"hash": "", "hash": "",
@ -12,7 +12,7 @@ Object {
`; `;
exports[`Updatenotification default returns status information 1`] = ` exports[`Updatenotification default returns status information 1`] = `
Object { {
"behind": 5, "behind": 5,
"current": "develop", "current": "develop",
"hash": "332e429a41f1a2339afd4f0ae96dd125da6beada", "hash": "332e429a41f1a2339afd4f0ae96dd125da6beada",
@ -23,7 +23,7 @@ Object {
`; `;
exports[`Updatenotification default returns status information early if isBehindInStatus 1`] = ` exports[`Updatenotification default returns status information early if isBehindInStatus 1`] = `
Object { {
"behind": 5, "behind": 5,
"current": "develop", "current": "develop",
"hash": "332e429a41f1a2339afd4f0ae96dd125da6beada", "hash": "332e429a41f1a2339afd4f0ae96dd125da6beada",

View File

@ -3,7 +3,6 @@
"TODAY": "Today", "TODAY": "Today",
"TOMORROW": "Tomorrow", "TOMORROW": "Tomorrow",
"DAYAFTERTOMORROW": "In 2 days",
"RUNNING": "Ends in", "RUNNING": "Ends in",
"EMPTY": "No upcoming events.", "EMPTY": "No upcoming events.",
"WEEK": "Week {weekNumber}", "WEEK": "Week {weekNumber}",

42
vendor/package-lock.json generated vendored
View File

@ -7,18 +7,18 @@
"name": "magicmirror-vendors", "name": "magicmirror-vendors",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.1.1", "@fortawesome/fontawesome-free": "^6.2.0",
"moment": "^2.29.3", "moment": "^2.29.4",
"moment-timezone": "^0.5.34", "moment-timezone": "^0.5.37",
"nunjucks": "^3.2.3", "nunjucks": "^3.2.3",
"suncalc": "^1.9.0", "suncalc": "^1.9.0",
"weathericons": "^2.1.0" "weathericons": "^2.1.0"
} }
}, },
"node_modules/@fortawesome/fontawesome-free": { "node_modules/@fortawesome/fontawesome-free": {
"version": "6.1.1", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.1.1.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.0.tgz",
"integrity": "sha512-J/3yg2AIXc9wznaVqpHVX3Wa5jwKovVF0AMYSnbmcXTiL3PpRPfF58pzWucCwEiCJBp+hCNRLWClTomD8SseKg==", "integrity": "sha512-CNR7qRIfCwWHNN7FnKUniva94edPdyQzil/zCwk3v6k4R6rR2Fr8i4s3PM7n/lyfPA6Zfko9z5WDzFxG9SW1uQ==",
"hasInstallScript": true, "hasInstallScript": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@ -43,17 +43,17 @@
} }
}, },
"node_modules/moment": { "node_modules/moment": {
"version": "2.29.3", "version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==", "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"engines": { "engines": {
"node": "*" "node": "*"
} }
}, },
"node_modules/moment-timezone": { "node_modules/moment-timezone": {
"version": "0.5.34", "version": "0.5.37",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz", "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.37.tgz",
"integrity": "sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==", "integrity": "sha512-uEDzDNFhfaywRl+vwXxffjjq1q0Vzr+fcQpQ1bU0kbzorfS7zVtZnCnGc8mhWmF39d4g4YriF6kwA75mJKE/Zg==",
"dependencies": { "dependencies": {
"moment": ">= 2.9.0" "moment": ">= 2.9.0"
}, },
@ -98,9 +98,9 @@
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": { "@fortawesome/fontawesome-free": {
"version": "6.1.1", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.1.1.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.0.tgz",
"integrity": "sha512-J/3yg2AIXc9wznaVqpHVX3Wa5jwKovVF0AMYSnbmcXTiL3PpRPfF58pzWucCwEiCJBp+hCNRLWClTomD8SseKg==" "integrity": "sha512-CNR7qRIfCwWHNN7FnKUniva94edPdyQzil/zCwk3v6k4R6rR2Fr8i4s3PM7n/lyfPA6Zfko9z5WDzFxG9SW1uQ=="
}, },
"a-sync-waterfall": { "a-sync-waterfall": {
"version": "1.0.1", "version": "1.0.1",
@ -118,14 +118,14 @@
"integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="
}, },
"moment": { "moment": {
"version": "2.29.3", "version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==" "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
}, },
"moment-timezone": { "moment-timezone": {
"version": "0.5.34", "version": "0.5.37",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz", "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.37.tgz",
"integrity": "sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==", "integrity": "sha512-uEDzDNFhfaywRl+vwXxffjjq1q0Vzr+fcQpQ1bU0kbzorfS7zVtZnCnGc8mhWmF39d4g4YriF6kwA75mJKE/Zg==",
"requires": { "requires": {
"moment": ">= 2.9.0" "moment": ">= 2.9.0"
} }

6
vendor/package.json vendored Executable file → Normal file
View File

@ -10,9 +10,9 @@
"url": "https://github.com/MichMich/MagicMirror/issues" "url": "https://github.com/MichMich/MagicMirror/issues"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.1.1", "@fortawesome/fontawesome-free": "^6.2.0",
"moment": "^2.29.3", "moment": "^2.29.4",
"moment-timezone": "^0.5.34", "moment-timezone": "^0.5.37",
"nunjucks": "^3.2.3", "nunjucks": "^3.2.3",
"suncalc": "^1.9.0", "suncalc": "^1.9.0",
"weathericons": "^2.1.0" "weathericons": "^2.1.0"

0
vendor/vendor.js vendored Executable file → Normal file
View File