mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-10-21 12:03:03 +00:00
Compare commits
126 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
9e0293047f | ||
|
298e585361 | ||
|
21a6d1bcea | ||
|
bbe17b9b01 | ||
|
4381fc6695 | ||
|
818fd7b490 | ||
|
4c6c6f9ed3 | ||
|
2338a90191 | ||
|
6cad0e191b | ||
|
f23e604ed4 | ||
|
0c1abad9df | ||
|
fb96cc3c72 | ||
|
e917f40542 | ||
|
29d467715f | ||
|
b791a3761f | ||
|
02201d9f15 | ||
|
b8dbf95497 | ||
|
65022f3ce1 | ||
|
44854d6a4f | ||
|
203014c654 | ||
|
d3e53586fd | ||
|
9dd343054e | ||
|
11d17dd2c0 | ||
|
4cc3e481cc | ||
|
174da38cc8 | ||
|
0a35505e8d | ||
|
032f7ac299 | ||
|
ca906c4b36 | ||
|
50f72f09ac | ||
|
02cf9b37e2 | ||
|
6f273d76b3 | ||
|
0a1067ec7d | ||
|
48756e8774 | ||
|
4915ad8fc7 | ||
|
ba128cbae9 | ||
|
2a6e51493e | ||
|
ef678f9f8a | ||
|
e997ee7071 | ||
|
1273390af3 | ||
|
0f6eb4a244 | ||
|
2c0c62c89b | ||
|
ec13c952d9 | ||
|
6fc71cf6f8 | ||
|
bd3137a3dc | ||
|
b26da4f97f | ||
|
cde0adc28e | ||
|
2813f101b8 | ||
|
4d8fb8c176 | ||
|
9ae62d60f7 | ||
|
17c14e137e | ||
|
bc239f6608 | ||
|
6493fad8a4 | ||
|
d539f459ca | ||
|
2cca6a2f39 | ||
|
80e07bae0d | ||
|
d4c4f6e1a5 | ||
|
d24fe4e983 | ||
|
aaa9042810 | ||
|
a4bb1cefb9 | ||
|
c3339b47bb | ||
|
0c1e5ea881 | ||
|
3fbd9006ad | ||
|
be9761146c | ||
|
5aa9e7e0f6 | ||
|
65e87aea52 | ||
|
66fffc932c | ||
|
1e934e16af | ||
|
82fbb7e32d | ||
|
8bf9b9bef9 | ||
|
2d15e4f976 | ||
|
055ce56a57 | ||
|
f1f2a61dc8 | ||
|
39c1b37726 | ||
|
5b1b25fa86 | ||
|
54ab0b1bf0 | ||
|
5507e9ffe9 | ||
|
30d5bfe59e | ||
|
b716ec33d9 | ||
|
e25209d9f9 | ||
|
1f4ac82495 | ||
|
0baf58f3fd | ||
|
9a769203e3 | ||
|
1435efaea7 | ||
|
c411ac821e | ||
|
da0489fc0c | ||
|
c82de1e314 | ||
|
604a555e14 | ||
|
87e011ff96 | ||
|
9b2051827c | ||
|
47aefb0c82 | ||
|
70da1d6f5c | ||
|
c0743ce9de | ||
|
fe7b4044e0 | ||
|
701ce8ad47 | ||
|
591f907134 | ||
|
4f1db749c0 | ||
|
ae72ed8c67 | ||
|
ab7934fa98 | ||
|
a4c77f0c9e | ||
|
0023c64d59 | ||
|
b55b3bd63b | ||
|
cbda20f67e | ||
|
e598dbb206 | ||
|
094881fc5c | ||
|
b13414cab8 | ||
|
3e65ac653b | ||
|
05621c9876 | ||
|
938619ce0c | ||
|
8e5267d44f | ||
|
3b55886c45 | ||
|
ed3aceb427 | ||
|
c3b2aaec69 | ||
|
bb1d3431cc | ||
|
fe83fe338a | ||
|
6504b5e818 | ||
|
8578900bfb | ||
|
d730dd04bf | ||
|
1d90c5e1fe | ||
|
0f39b7733c | ||
|
038b6765e7 | ||
|
58569a648c | ||
|
df0f048ecc | ||
|
288a008e72 | ||
|
439690b981 | ||
|
b4e75f6844 | ||
|
3f6a5a7772 |
@@ -16,7 +16,7 @@
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 2018,
|
||||
"ecmaVersion": 2020,
|
||||
"ecmaFeatures": {
|
||||
"globalReturn": true
|
||||
}
|
||||
|
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
@@ -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
|
||||
|
||||
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:
|
||||
|
||||
- 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)
|
||||
[https://gitlab.com/khassel/magicmirror](https://gitlab.com/khassel/magicmirror)
|
||||
|
||||
---
|
||||
|
||||
|
6
.github/dependabot.yaml
vendored
Normal file
6
.github/dependabot.yaml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
0
.github/stale.yml → .github/stale.yaml
vendored
0
.github/stale.yml → .github/stale.yaml
vendored
@@ -9,18 +9,21 @@ on:
|
||||
pull_request:
|
||||
branches: [master, develop]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x, 16.x, 17.x]
|
||||
node-version: [14.x, 16.x, 18.x]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "npm"
|
||||
@@ -28,7 +31,7 @@ jobs:
|
||||
run: |
|
||||
Xvfb :99 -screen 0 1024x768x16 &
|
||||
export DISPLAY=:99
|
||||
npm install
|
||||
npm run install-mm:dev
|
||||
touch css/custom.css
|
||||
npm run test:prettier
|
||||
npm run test:js
|
@@ -1,4 +1,5 @@
|
||||
# This workflow runs the automated test and uploads the coverage results to codecov.io
|
||||
# For more information see: https://github.com/codecov/codecov-action
|
||||
|
||||
name: "Run Codecov Tests"
|
||||
|
||||
@@ -8,13 +9,16 @@ on:
|
||||
pull_request:
|
||||
branches: [master, develop]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
run-and-upload-coverage-report:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Install dependencies and run coverage
|
||||
run: |
|
||||
Xvfb :99 -screen 0 1024x768x16 &
|
||||
@@ -23,7 +27,7 @@ jobs:
|
||||
touch css/custom.css
|
||||
npm run test:coverage
|
||||
- name: Upload coverage results to codecov
|
||||
uses: codecov/codecov-action@v2
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./coverage/lcov.info
|
||||
fail_ci_if_error: true
|
@@ -1,4 +1,5 @@
|
||||
# This workflow enforces the update of a changelog file on every pull request
|
||||
# For more information see: https://github.com/dangoslen/changelog-enforcer
|
||||
|
||||
name: "Enforce Changelog"
|
||||
|
||||
@@ -11,10 +12,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Enforce changelog️
|
||||
uses: dangoslen/changelog-enforcer@v2
|
||||
uses: dangoslen/changelog-enforcer@v3
|
||||
with:
|
||||
changeLogPath: "CHANGELOG.md"
|
||||
skipLabels: "Skip Changelog"
|
121
CHANGELOG.md
121
CHANGELOG.md
@@ -5,6 +5,60 @@ 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².
|
||||
|
||||
## [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
|
||||
|
||||
Special thanks to the following contributors: @eouia, @khassel, @kolbyjack, @KristjanESPERANTO, @nathannaveen, @naveensrinivasan, @rejas, @rohitdharavath and @sdetweil.
|
||||
|
||||
### Added
|
||||
|
||||
- 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).
|
||||
- Added the notification emitting from the weather module on information updated.
|
||||
- Use recommended file extension for YAML files (#2864).
|
||||
|
||||
### Updated
|
||||
|
||||
- Use latest node 18 when running tests on github actions.
|
||||
- Updated `electron` to v19 and other dependencies.
|
||||
- Use internal fetch function of node instead external `node-fetch` library if used node version >= `v18`.
|
||||
- Include duplicate events in broadcasts.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix problems with non latin fonds caused by updating to fontsource (fixes #2835).
|
||||
|
||||
## [2.19.0] - 2022-04-01
|
||||
|
||||
Special thanks to the following contributors: @10bias, @CFenner, @JHWelch, @k1rd3rf, @khassel, @kolbyjack, @krekos, @KristjanESPERANTO, @Nerfzooka, @oraclesean, @oscarb, @philnagel, @rejas, @sdetweil, @shin10, @SiderealArt and @Tom-Hirschberger.
|
||||
@@ -12,9 +66,9 @@ Special thanks to the following contributors: @10bias, @CFenner, @JHWelch, @k1rd
|
||||
### Added
|
||||
|
||||
- Added a config option under the weather module, `absoluteDates`, providing an option to format weather forecast date output with either absolute or relative dates.
|
||||
- Added test for new weather forecast `absoluteDates` porperty.
|
||||
- Added test for new weather forecast `absoluteDates` property.
|
||||
- The modules get a class hidden added/removed if they get hidden/shown which will also toggle pointer-events.
|
||||
- Added new config option `showTitleAsUrl` to newsfeed module. If set, the diplayed title is a link to the article which is useful when running in a browser and you want to read this article.
|
||||
- Added new config option `showTitleAsUrl` to newsfeed module. If set, the displayed title is a link to the article which is useful when running in a browser and you want to read this article.
|
||||
- Added internal cors proxy to get weather providers working without public proxies (fixes #2714). The new url `http(s)://address:port/cors?url=https://whatever-to-proxy` can be used in other modules too.
|
||||
- Added a WeatherProvider for Weatherflow.
|
||||
- Added new env var `ELECTRON_DISABLE_GPU` which disable gpu under electron if set (fixes #2831).
|
||||
@@ -23,12 +77,12 @@ Special thanks to the following contributors: @10bias, @CFenner, @JHWelch, @k1rd
|
||||
### Updated
|
||||
|
||||
- Deprecated roboto fonts package `roboto-fontface-bower` replaced with `fontsource`.
|
||||
- Update `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 `electron` to v17, `helmet` to v5 (use defaults of v4) and other dependencies
|
||||
- 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.
|
||||
- Include all calendar events from the configured date range when broadcasting.
|
||||
- Update 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 Danish and German translation.
|
||||
- Updated `node-ical` to v0.15 and added `luxon` as dependency for not breaking the "no-optional" install (see #2718 and #2824).
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -39,6 +93,7 @@ Special thanks to the following contributors: @10bias, @CFenner, @JHWelch, @k1rd
|
||||
- Don't adjust startDate for full day events if endDate is in the past.
|
||||
- Fix windspeed conversion error in openweathermap provider. (#2812)
|
||||
- Fix conflicting parms turning off showEnd for full day events. (#2629)
|
||||
- Fix regression, calendar.maximumEntries not used to filter calendar level entries (#2868)
|
||||
|
||||
## [2.18.0] - 2022-01-01
|
||||
|
||||
@@ -53,8 +108,8 @@ Special thanks to the following contributors: @AmpioRosso, @eouia, @fewieden, @j
|
||||
- ESLint version supports now ECMAScript 2018.
|
||||
- Cleaned up `updatenotification` module and switched to nunjuck template.
|
||||
- Moved calendar tests from category `electron` to `e2e`.
|
||||
- Update missed translations for Korean language (ko.json).
|
||||
- Update missed translations for Dutch language (nl.json).
|
||||
- Updated missed translations for Korean language (ko.json).
|
||||
- Updated missed translations for Dutch language (nl.json).
|
||||
- Cleaned up `alert` module and switched to nunjuck template.
|
||||
- Moved weather tests from category `electron` to `e2e`.
|
||||
- Updated github actions.
|
||||
@@ -105,14 +160,14 @@ Special thanks to the following contributors: @apiontek, @eouia, @jupadin, @khas
|
||||
- Refactor test configs, use default test config for all tests.
|
||||
- Updated github templates.
|
||||
- Actually test all js and css files when lint script is run.
|
||||
- Update jsdocs and print warnings during testing too.
|
||||
- Update weathergov provider to try fetching not just current, but also foreacst, when API URLs available.
|
||||
- Updated jsdocs and print warnings during testing too.
|
||||
- Updated weathergov provider to try fetching not just current, but also foreacst, when API URLs available.
|
||||
- Refactored clock layout.
|
||||
- Refactored methods from weatherproviders into weatherobject (isDaytime, updateSunTime).
|
||||
- Use of `logger.js` in jest tests.
|
||||
- Run prettier over all relevant files.
|
||||
- Move tests needing electron in new category `electron`, use `server only` mode in `e2e` tests.
|
||||
- Update dependencies in package.json.
|
||||
- Updated dependencies in package.json.
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -146,13 +201,13 @@ Special thanks to the following contributors: @210954, @B1gG, @codac, @Crazylegs
|
||||
- Refactor code into es6 where possible (e.g. var -> let/const).
|
||||
- Use node v16 in github workflow (replacing node v10).
|
||||
- Moved some files into better suited directories.
|
||||
- Update 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, require node >= v12, remove `rrule-alt` and `rrule`.
|
||||
- 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.
|
||||
- Updated default WEATHER module such that a provider can optionally set a custom unit-of-measure for precipitation (`weatherObject.precipitationUnits`).
|
||||
- Update documentation.
|
||||
- Update jest tests: Reset changes on js/logger.js, mock logger.js in global_vars tests.
|
||||
- Update dependencies in package.json.
|
||||
- Updated documentation.
|
||||
- Updated jest tests: Reset changes on js/logger.js, mock logger.js in global_vars tests.
|
||||
- Updated dependencies in package.json.
|
||||
|
||||
### Removed
|
||||
|
||||
@@ -264,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.
|
||||
- Weather module - forecast now show TODAY and TOMORROW instead of weekday, to make it easier to understand.
|
||||
- Update dependencies to latest versions.
|
||||
- Update dependencies eslint, feedme, simple-git and socket.io to latest versions.
|
||||
- Update lithuanian translation.
|
||||
- Update config sample.
|
||||
- Updated dependencies to latest versions.
|
||||
- Updated dependencies eslint, feedme, simple-git and socket.io to latest versions.
|
||||
- Updated lithuanian translation.
|
||||
- Updated config sample.
|
||||
- Highlight required version mismatch.
|
||||
- No select Text for TouchScreen use.
|
||||
- Corrected logic for timeFormat "relative" and "absolute".
|
||||
@@ -295,12 +350,12 @@ Special thanks to the following contributors: @Alvinger, @AndyPoms, @ashishtank,
|
||||
- Catch errors when parsing calendar data with ical. (#2022)
|
||||
- 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)
|
||||
- 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)
|
||||
- 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)
|
||||
- Update 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 node-ical version again, 0.12.7, change RRULE fix (#2371, #2379), node-ical now throws error (which we catch)
|
||||
- Updated simple-git version to 2.31 unhandled promise rejection (#2383)
|
||||
|
||||
## [2.13.0] - 2020-10-01
|
||||
|
||||
@@ -525,10 +580,10 @@ Special thanks to @sdetweil for all his great contributions!
|
||||
|
||||
- English translation for "Feels" to "Feels like"
|
||||
- Fixed the example calendar url in `config.js.sample`
|
||||
- Update `ical.js` to solve various calendar issues.
|
||||
- Update weather city list url [#1676](https://github.com/MichMich/MagicMirror/issues/1676)
|
||||
- Updated `ical.js` to solve various calendar issues.
|
||||
- Updated weather city list url [#1676](https://github.com/MichMich/MagicMirror/issues/1676)
|
||||
- Only update clock once per minute when seconds aren't shown
|
||||
- Update weatherprovider documentation.
|
||||
- Updated weatherprovider documentation.
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -548,7 +603,7 @@ Special thanks to @sdetweil for all his great contributions!
|
||||
- use current username vs hardcoded 'pi' to support non-pi install
|
||||
- check for npm installed. node install doesn't do npm anymore
|
||||
- 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
|
||||
|
||||
## [2.7.1] - 2019-04-02
|
||||
@@ -756,7 +811,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
|
||||
- Add types for module.
|
||||
- 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)
|
||||
- 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 integration tests translations
|
||||
- Add advanced filtering to the excludedEvents configuration of the default calendar module
|
||||
@@ -768,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.
|
||||
- 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
|
||||
- Changed Electron dependency to v1.7.13.
|
||||
|
||||
@@ -975,7 +1030,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
|
||||
|
||||
### Fixed
|
||||
|
||||
- Update .gitignore to not ignore default modules folder.
|
||||
- Updated .gitignore to not ignore default modules folder.
|
||||
- Remove white flash on boot up.
|
||||
- Added `update` in Raspberry Pi installation script.
|
||||
- Fix an issue where the analog clock looked scrambled. ([#611](https://github.com/MichMich/MagicMirror/issues/611))
|
||||
@@ -1060,8 +1115,8 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
|
||||
### Updated
|
||||
|
||||
- Force fullscreen when kioskmode is active.
|
||||
- Update the .github templates and information with more modern information.
|
||||
- Update the Gruntfile with a more functional StyleLint implementation.
|
||||
- Updated the .github templates and information with more modern information.
|
||||
- Updated the Gruntfile with a more functional StyleLint implementation.
|
||||
|
||||
## [2.0.4] - 2016-08-07
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# The MIT License (MIT)
|
||||
|
||||
Copyright © 2016-2021 Michael Teeuw
|
||||
Copyright © 2016-2022 Michael Teeuw
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
|
18
css/main.css
18
css/main.css
@@ -8,7 +8,11 @@
|
||||
--font-secondary: "Roboto";
|
||||
|
||||
--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-right: 60px;
|
||||
@@ -60,27 +64,27 @@ body {
|
||||
}
|
||||
|
||||
.xsmall {
|
||||
font-size: var(--font-size-small);
|
||||
font-size: var(--font-size-xsmall);
|
||||
line-height: 1.275;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 1rem;
|
||||
font-size: var(--font-size-small);
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.medium {
|
||||
font-size: 1.5rem;
|
||||
font-size: var(--font-size-medium);
|
||||
line-height: 1.225;
|
||||
}
|
||||
|
||||
.large {
|
||||
font-size: 3.25rem;
|
||||
font-size: var(--font-size-large);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.xlarge {
|
||||
font-size: 3.75rem;
|
||||
font-size: var(--font-size-xlarge);
|
||||
line-height: 1;
|
||||
letter-spacing: -3px;
|
||||
}
|
||||
@@ -115,7 +119,7 @@ body {
|
||||
|
||||
header {
|
||||
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-weight: 400;
|
||||
border-bottom: 1px solid var(--color-text-dimmed);
|
||||
|
28
fonts/package-lock.json
generated
28
fonts/package-lock.json
generated
@@ -7,31 +7,31 @@
|
||||
"name": "magicmirror-fonts",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": "^4.5.5",
|
||||
"@fontsource/roboto-condensed": "^4.5.6"
|
||||
"@fontsource/roboto": "^4.5.8",
|
||||
"@fontsource/roboto-condensed": "^4.5.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/roboto": {
|
||||
"version": "4.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.5.tgz",
|
||||
"integrity": "sha512-Pe1p+gAO6K0aLxBXlLoJRHVx352tVc/v/7DOnvM3t+FYXb+KUga9aCD1NpnDfd0kKnWXqrZyAXguyyFWDDuphw=="
|
||||
"version": "4.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz",
|
||||
"integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA=="
|
||||
},
|
||||
"node_modules/@fontsource/roboto-condensed": {
|
||||
"version": "4.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-4.5.6.tgz",
|
||||
"integrity": "sha512-WDN0RlwXa3ADUDJxrB9HEIhrwYdJFLTNw4YemEYSeIPURU5DAHSx2VFXFv69PbM7kV8At5yMTp8bQkL5TBUP3w=="
|
||||
"version": "4.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-4.5.9.tgz",
|
||||
"integrity": "sha512-ql4sQq+h8puBVildZ5ssjYf8DWDONYDe3PD3Bu/p1ZW9GnRETRNPPcCTs/q62HIl3QimwwkiKWynn6wZhQaetg=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": {
|
||||
"version": "4.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.5.tgz",
|
||||
"integrity": "sha512-Pe1p+gAO6K0aLxBXlLoJRHVx352tVc/v/7DOnvM3t+FYXb+KUga9aCD1NpnDfd0kKnWXqrZyAXguyyFWDDuphw=="
|
||||
"version": "4.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-4.5.8.tgz",
|
||||
"integrity": "sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA=="
|
||||
},
|
||||
"@fontsource/roboto-condensed": {
|
||||
"version": "4.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-4.5.6.tgz",
|
||||
"integrity": "sha512-WDN0RlwXa3ADUDJxrB9HEIhrwYdJFLTNw4YemEYSeIPURU5DAHSx2VFXFv69PbM7kV8At5yMTp8bQkL5TBUP3w=="
|
||||
"version": "4.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto-condensed/-/roboto-condensed-4.5.9.tgz",
|
||||
"integrity": "sha512-ql4sQq+h8puBVildZ5ssjYf8DWDONYDe3PD3Bu/p1ZW9GnRETRNPPcCTs/q62HIl3QimwwkiKWynn6wZhQaetg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@
|
||||
"url": "https://github.com/MichMich/MagicMirror/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": "^4.5.5",
|
||||
"@fontsource/roboto-condensed": "^4.5.6"
|
||||
"@fontsource/roboto": "^4.5.8",
|
||||
"@fontsource/roboto-condensed": "^4.5.9"
|
||||
}
|
||||
}
|
||||
|
@@ -2,57 +2,54 @@
|
||||
font-family: Roboto;
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: local("Roboto Thin"), local("Roboto-Thin"), url("node_modules/@fontsource/roboto/files/roboto-latin-100-normal.woff2") format("woff2"), url("node_modules/@fontsource/roboto/files/roboto-latin-100-normal.woff") format("woff");
|
||||
src: local("Roboto Thin"), local("Roboto-Thin"), url("node_modules/@fontsource/roboto/files/roboto-all-100-normal.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Roboto Condensed";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local("Roboto Condensed Light"), local("RobotoCondensed-Light"), url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-300-normal.woff2") format("woff2"),
|
||||
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-300-normal.woff") format("woff");
|
||||
src: local("Roboto Condensed Light"), local("RobotoCondensed-Light"), url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-all-300-normal.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Roboto Condensed";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local("Roboto Condensed"), local("RobotoCondensed-Regular"), url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-400-normal.woff2") format("woff2"),
|
||||
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-400-normal.woff") format("woff");
|
||||
src: local("Roboto Condensed"), local("RobotoCondensed-Regular"), url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-all-400-normal.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Roboto Condensed";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local("Roboto Condensed Bold"), local("RobotoCondensed-Bold"), url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-700-normal.woff2") format("woff2"),
|
||||
url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-latin-700-normal.woff") format("woff");
|
||||
src: local("Roboto Condensed Bold"), local("RobotoCondensed-Bold"), url("node_modules/@fontsource/roboto-condensed/files/roboto-condensed-all-700-normal.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local("Roboto"), local("Roboto-Regular"), url("node_modules/@fontsource/roboto/files/roboto-latin-400-normal.woff2") format("woff2"), url("node_modules/@fontsource/roboto/files/roboto-latin-400-normal.woff") format("woff");
|
||||
src: local("Roboto"), local("Roboto-Regular"), url("node_modules/@fontsource/roboto/files/roboto-all-400-normal.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: local("Roboto Medium"), local("Roboto-Medium"), url("node_modules/@fontsource/roboto/files/roboto-latin-500-normal.woff2") format("woff2"), url("node_modules/@fontsource/roboto/files/roboto-latin-500-normal.woff") format("woff");
|
||||
src: local("Roboto Medium"), local("Roboto-Medium"), url("node_modules/@fontsource/roboto/files/roboto-all-500-normal.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local("Roboto Bold"), local("Roboto-Bold"), url("node_modules/@fontsource/roboto/files/roboto-latin-700-normal.woff2") format("woff2"), url("node_modules/@fontsource/roboto/files/roboto-latin-700-normal.woff") format("woff");
|
||||
src: local("Roboto Bold"), local("Roboto-Bold"), url("node_modules/@fontsource/roboto/files/roboto-all-700-normal.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: local("Roboto Light"), local("Roboto-Light"), url("node_modules/@fontsource/roboto/files/roboto-latin-300-normal.woff2") format("woff2"), url("node_modules/@fontsource/roboto/files/roboto-latin-300-normal.woff") format("woff");
|
||||
src: local("Roboto Light"), local("Roboto-Light"), url("node_modules/@fontsource/roboto/files/roboto-all-300-normal.woff") format("woff");
|
||||
}
|
||||
|
@@ -25,6 +25,9 @@ const defaults = {
|
||||
units: "metric",
|
||||
zoom: 1,
|
||||
customCss: "css/custom.css",
|
||||
// httpHeaders used by helmet, see https://helmetjs.github.io/. You can add other/more object values by overriding this in config.js,
|
||||
// e.g. you need to add `frameguard: false` for embedding MagicMirror in another website, see https://github.com/MichMich/MagicMirror/issues/2847
|
||||
httpHeaders: { contentSecurityPolicy: false, crossOriginOpenerPolicy: false, crossOriginEmbedderPolicy: false, crossOriginResourcePolicy: false, originAgentCluster: false },
|
||||
|
||||
modules: [
|
||||
{
|
||||
|
@@ -8,7 +8,7 @@ const Log = require("logger");
|
||||
let config = process.env.config ? JSON.parse(process.env.config) : {};
|
||||
// Module to control application life.
|
||||
const app = electron.app;
|
||||
// If ELECTRON_DISABLE_GPU is set electron is startet with --disable-gpu flag.
|
||||
// If ELECTRON_DISABLE_GPU is set electron is started with --disable-gpu flag.
|
||||
// See https://www.electronjs.org/docs/latest/tutorial/offscreen-rendering for more info.
|
||||
if (process.env.ELECTRON_DISABLE_GPU !== undefined) {
|
||||
app.disableHardwareAcceleration();
|
||||
|
20
js/fetch.js
Normal file
20
js/fetch.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* fetch
|
||||
*
|
||||
* @param {string} url to be fetched
|
||||
* @param {object} options object e.g. for headers
|
||||
* @class
|
||||
*/
|
||||
async function fetch(url, options) {
|
||||
const nodeVersion = process.version.match(/^v(\d+)\.*/)[1];
|
||||
if (nodeVersion >= 18) {
|
||||
// node version >= 18
|
||||
return global.fetch(url, options);
|
||||
} else {
|
||||
// node version < 18
|
||||
const nodefetch = require("node-fetch");
|
||||
return nodefetch(url, options);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = fetch;
|
@@ -22,39 +22,38 @@ const NodeHelper = Class.extend({
|
||||
Log.log(`Starting module helper: ${this.name}`);
|
||||
},
|
||||
|
||||
/* stop()
|
||||
/**
|
||||
* Called when the MagicMirror² server receives a `SIGINT`
|
||||
* Close any open connections, stop any sub-processes and
|
||||
* gracefully exit the module.
|
||||
*
|
||||
*/
|
||||
stop() {
|
||||
Log.log(`Stopping module helper: ${this.name}`);
|
||||
},
|
||||
|
||||
/* socketNotificationReceived(notification, payload)
|
||||
/**
|
||||
* This method is called when a socket notification arrives.
|
||||
*
|
||||
* argument notification string - The identifier of the notification.
|
||||
* argument payload mixed - The payload of the notification.
|
||||
* @param {string} notification The identifier of the notification.
|
||||
* @param {*} payload The payload of the notification.
|
||||
*/
|
||||
socketNotificationReceived(notification, payload) {
|
||||
Log.log(`${this.name} received a socket notification: ${notification} - Payload: ${payload}`);
|
||||
},
|
||||
|
||||
/* setName(name)
|
||||
/**
|
||||
* Set the module name.
|
||||
*
|
||||
* argument name string - Module name.
|
||||
* @param {string} name Module name.
|
||||
*/
|
||||
setName(name) {
|
||||
this.name = name;
|
||||
},
|
||||
|
||||
/* setPath(path)
|
||||
/**
|
||||
* Set the module path.
|
||||
*
|
||||
* argument path string - Module path.
|
||||
* @param {string} path Module path.
|
||||
*/
|
||||
setPath(path) {
|
||||
this.path = path;
|
||||
|
@@ -10,7 +10,7 @@ const path = require("path");
|
||||
const ipfilter = require("express-ipfilter").IpFilter;
|
||||
const fs = require("fs");
|
||||
const helmet = require("helmet");
|
||||
const fetch = require("node-fetch");
|
||||
const fetch = require("fetch");
|
||||
|
||||
const Log = require("logger");
|
||||
const Utils = require("./utils.js");
|
||||
@@ -69,7 +69,7 @@ function Server(config, callback) {
|
||||
res.status(403).send("This device is not allowed to access your mirror. <br> Please check your config.js or config.js.sample to change this.");
|
||||
});
|
||||
});
|
||||
app.use(helmet({ contentSecurityPolicy: false, crossOriginOpenerPolicy: false, crossOriginEmbedderPolicy: false, crossOriginResourcePolicy: false, originAgentCluster: false }));
|
||||
app.use(helmet(config.httpHeaders));
|
||||
|
||||
app.use("/js", express.static(__dirname));
|
||||
|
||||
|
0
modules/default/calendar/README.md
Executable file → Normal file
0
modules/default/calendar/README.md
Executable file → Normal file
47
modules/default/calendar/calendar.js
Executable file → Normal file
47
modules/default/calendar/calendar.js
Executable file → Normal file
@@ -37,6 +37,7 @@ Module.register("calendar", {
|
||||
hidePrivate: false,
|
||||
hideOngoing: false,
|
||||
hideTime: false,
|
||||
showTimeToday: false,
|
||||
colored: false,
|
||||
coloredSymbolOnly: false,
|
||||
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.
|
||||
socketNotificationReceived: function (notification, payload) {
|
||||
if (notification === "FETCH_CALENDAR") {
|
||||
this.sendSocketNotification(notification, { url: payload.url, id: this.identifier });
|
||||
}
|
||||
|
||||
if (this.identifier !== payload.id) {
|
||||
return;
|
||||
}
|
||||
@@ -305,6 +310,12 @@ Module.register("calendar", {
|
||||
timeWrapper.className = "time light align-left " + this.timeClassForUrl(event.url);
|
||||
timeWrapper.style.paddingLeft = "2px";
|
||||
timeWrapper.innerHTML = moment(event.startDate, "x").format("LT");
|
||||
|
||||
// Add endDate to dataheaders if showEnd is enabled
|
||||
if (this.config.showEnd) {
|
||||
timeWrapper.innerHTML += " - " + moment(event.endDate, "x").format("LT");
|
||||
}
|
||||
|
||||
eventWrapper.appendChild(timeWrapper);
|
||||
titleWrapper.classList.add("align-right");
|
||||
}
|
||||
@@ -362,7 +373,7 @@ Module.register("calendar", {
|
||||
} else {
|
||||
timeWrapper.innerHTML = this.capFirst(
|
||||
moment(event.startDate, "x").calendar(null, {
|
||||
sameDay: "[" + this.translate("TODAY") + "]",
|
||||
sameDay: this.config.showTimeToday ? "LT" : "[" + this.translate("TODAY") + "]",
|
||||
nextDay: "[" + this.translate("TOMORROW") + "]",
|
||||
nextWeek: "dddd",
|
||||
sameElse: event.fullDayEvent ? this.config.fullDayEventDateFormat : this.config.dateFormat
|
||||
@@ -381,7 +392,7 @@ Module.register("calendar", {
|
||||
}
|
||||
}
|
||||
} else if (event.startDate - now < this.config.getRelative * oneHour) {
|
||||
// If event is within getRelative hours, display 'in xxx' time format or moment.fromNow()
|
||||
// If event is within getRelative hours, display 'in xxx' time format or moment.fromNow()
|
||||
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
|
||||
}
|
||||
} else {
|
||||
@@ -487,25 +498,27 @@ Module.register("calendar", {
|
||||
|
||||
for (const calendarUrl in this.calendarData) {
|
||||
const calendar = this.calendarData[calendarUrl];
|
||||
let remainingEntries = this.maximumEntriesForUrl(calendarUrl);
|
||||
for (const e in calendar) {
|
||||
const event = JSON.parse(JSON.stringify(calendar[e])); // clone object
|
||||
|
||||
if (event.endDate < now && limitNumberOfEntries) {
|
||||
if (this.config.hidePrivate && event.class === "PRIVATE") {
|
||||
// do not add the current event, skip it
|
||||
continue;
|
||||
}
|
||||
if (this.config.hidePrivate) {
|
||||
if (event.class === "PRIVATE") {
|
||||
// do not add the current event, skip it
|
||||
if (limitNumberOfEntries) {
|
||||
if (event.endDate < now) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (this.config.hideOngoing && limitNumberOfEntries) {
|
||||
if (event.startDate < now) {
|
||||
if (this.config.hideOngoing && event.startDate < now) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (this.listContainsEvent(events, event)) {
|
||||
continue;
|
||||
if (this.listContainsEvent(events, event)) {
|
||||
continue;
|
||||
}
|
||||
if (--remainingEntries < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
event.url = calendarUrl;
|
||||
event.today = event.startDate >= today && event.startDate < today + 24 * 60 * 60 * 1000;
|
||||
@@ -714,6 +727,16 @@ Module.register("calendar", {
|
||||
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.
|
||||
*
|
||||
|
@@ -8,7 +8,7 @@ const CalendarUtils = require("./calendarutils");
|
||||
const Log = require("logger");
|
||||
const NodeHelper = require("node_helper");
|
||||
const ical = require("node-ical");
|
||||
const fetch = require("node-fetch");
|
||||
const fetch = require("fetch");
|
||||
const digest = require("digest-fetch");
|
||||
const https = require("https");
|
||||
|
||||
|
@@ -333,9 +333,12 @@ const CalendarUtils = {
|
||||
// If the offset is negative (east of GMT), where the problem is
|
||||
if (dateoffset < 0) {
|
||||
if (dh < Math.abs(dateoffset / 60)) {
|
||||
// if the rrule byweekday WAS explicitly set , correct it
|
||||
// reduce the time by the offset
|
||||
// Apply the correction to the date/time to get it UTC relative
|
||||
date = new Date(date.getTime() - Math.abs(24 * 60) * 60000);
|
||||
if (curEvent.rrule.origOptions.byweekday !== undefined) {
|
||||
// Apply the correction to the date/time to get it UTC relative
|
||||
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..
|
||||
// fix it for this event entry
|
||||
//duration = 24 * 60 * 60 * 1000;
|
||||
@@ -346,8 +349,11 @@ const CalendarUtils = {
|
||||
//if (event.start.tz === moment.tz.guess()) {
|
||||
// if the date hour is less than the offset
|
||||
if (24 - dh <= Math.abs(dateoffset / 60)) {
|
||||
// apply the correction to the date/time back to right day
|
||||
date = new Date(date.getTime() + Math.abs(24 * 60) * 60000);
|
||||
// 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
|
||||
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..
|
||||
// fix it for this event entry
|
||||
//duration = 24 * 60 * 60 * 1000;
|
||||
@@ -361,9 +367,12 @@ const CalendarUtils = {
|
||||
if (dateoffset < 0) {
|
||||
// if the date hour is less than the offset
|
||||
if (dh <= Math.abs(dateoffset / 60)) {
|
||||
// Reduce the time by the offset:
|
||||
// Apply the correction to the date/time to get it UTC relative
|
||||
date = new Date(date.getTime() - Math.abs(24 * 60) * 60000);
|
||||
// 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
|
||||
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..
|
||||
// fix it for this event entry
|
||||
//duration = 24 * 60 * 60 * 1000;
|
||||
@@ -374,8 +383,11 @@ const CalendarUtils = {
|
||||
//if (event.start.tz === moment.tz.guess()) {
|
||||
// if the date hour is less than the offset
|
||||
if (24 - dh <= Math.abs(dateoffset / 60)) {
|
||||
// apply the correction to the date/time back to right day
|
||||
date = new Date(date.getTime() + Math.abs(24 * 60) * 60000);
|
||||
// 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
|
||||
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..
|
||||
// fix it for this event entry
|
||||
//duration = 24 * 60 * 60 * 1000;
|
||||
@@ -469,10 +481,6 @@ const CalendarUtils = {
|
||||
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 (fullDayEvent && startDate.format("x") === endDate.format("x")) {
|
||||
endDate = endDate.endOf("day");
|
||||
|
@@ -19,6 +19,14 @@ module.exports = NodeHelper.create({
|
||||
socketNotificationReceived: function (notification, payload) {
|
||||
if (notification === "ADD_CALENDAR") {
|
||||
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();
|
||||
}
|
||||
},
|
||||
|
||||
|
@@ -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";
|
||||
}
|
||||
});
|
@@ -4,7 +4,7 @@
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* 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 ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
|
@@ -7,8 +7,9 @@
|
||||
const Log = require("logger");
|
||||
const FeedMe = require("feedme");
|
||||
const NodeHelper = require("node_helper");
|
||||
const fetch = require("node-fetch");
|
||||
const fetch = require("fetch");
|
||||
const iconv = require("iconv-lite");
|
||||
const stream = require("stream");
|
||||
|
||||
/**
|
||||
* Responsible for requesting an update on the set interval and broadcasting the data.
|
||||
@@ -77,6 +78,19 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
|
||||
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 headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version,
|
||||
@@ -87,7 +101,13 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
|
||||
fetch(url, { headers: headers })
|
||||
.then(NodeHelper.checkFetchStatus)
|
||||
.then((response) => {
|
||||
response.body.pipe(iconv.decodeStream(encoding)).pipe(parser);
|
||||
let nodeStream;
|
||||
if (response.body instanceof stream.Readable) {
|
||||
nodeStream = response.body;
|
||||
} else {
|
||||
nodeStream = stream.Readable.fromWeb(response.body);
|
||||
}
|
||||
nodeStream.pipe(iconv.decodeStream(encoding)).pipe(parser);
|
||||
})
|
||||
.catch((error) => {
|
||||
fetchFailedCallback(this, error);
|
||||
|
2
modules/default/weather/README.md
Executable file → Normal file
2
modules/default/weather/README.md
Executable file → Normal file
@@ -1,5 +1,5 @@
|
||||
# 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).
|
||||
|
0
modules/default/weather/current.njk
Executable file → Normal file
0
modules/default/weather/current.njk
Executable file → Normal file
0
modules/default/weather/providers/README.md
Executable file → Normal file
0
modules/default/weather/providers/README.md
Executable file → Normal file
0
modules/default/weather/providers/darksky.js
Executable file → Normal file
0
modules/default/weather/providers/darksky.js
Executable file → Normal file
0
modules/default/weather/providers/openweathermap.js
Executable file → Normal file
0
modules/default/weather/providers/openweathermap.js
Executable file → Normal file
@@ -17,19 +17,20 @@ WeatherProvider.register("smhi", {
|
||||
defaults: {
|
||||
lat: 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() {
|
||||
this.fetchData(this.getURL())
|
||||
.then((data) => {
|
||||
let closest = this.getClosestToCurrentTime(data.timeSeries);
|
||||
let coordinates = this.resolveCoordinates(data);
|
||||
let weatherObject = this.convertWeatherDataToObject(closest, coordinates);
|
||||
this.setFetchedLocation(`(${coordinates.lat},${coordinates.lon})`);
|
||||
const closest = this.getClosestToCurrentTime(data.timeSeries);
|
||||
const coordinates = this.resolveCoordinates(data);
|
||||
const weatherObject = this.convertWeatherDataToObject(closest, coordinates);
|
||||
this.setFetchedLocation(this.config.location || `(${coordinates.lat},${coordinates.lon})`);
|
||||
this.setCurrentWeather(weatherObject);
|
||||
})
|
||||
.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.
|
||||
* Handling hourly forecast would be easy as not grouping by day but it seems really specific for one weather provider for now.
|
||||
* Implements method in interface for fetching a multi-day forecast.
|
||||
*/
|
||||
fetchWeatherForecast() {
|
||||
this.fetchData(this.getURL())
|
||||
.then((data) => {
|
||||
let coordinates = this.resolveCoordinates(data);
|
||||
let weatherObjects = this.convertWeatherDataGroupedByDay(data.timeSeries, coordinates);
|
||||
this.setFetchedLocation(`(${coordinates.lat},${coordinates.lon})`);
|
||||
const coordinates = this.resolveCoordinates(data);
|
||||
const weatherObjects = this.convertWeatherDataGroupedBy(data.timeSeries, coordinates);
|
||||
this.setFetchedLocation(this.config.location || `(${coordinates.lat},${coordinates.lon})`);
|
||||
this.setWeatherForecast(weatherObjects);
|
||||
})
|
||||
.catch((error) => Log.error("Could not load data: " + error.message))
|
||||
.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
|
||||
*
|
||||
@@ -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`;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* The returned units is always in metric system.
|
||||
@@ -114,6 +144,7 @@ WeatherProvider.register("smhi", {
|
||||
currentWeather.windSpeed = this.paramValue(weatherData, "ws");
|
||||
currentWeather.windDirection = this.paramValue(weatherData, "wd");
|
||||
currentWeather.weatherType = this.convertWeatherType(this.paramValue(weatherData, "Wsymb2"), currentWeather.isDayTime());
|
||||
currentWeather.feelsLikeTemp = this.calculateAT(weatherData);
|
||||
|
||||
// Determine the precipitation amount and category and update the
|
||||
// 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} 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
|
||||
*/
|
||||
convertWeatherDataGroupedByDay(allWeatherData, coordinates) {
|
||||
convertWeatherDataGroupedBy(allWeatherData, coordinates, groupBy = "day") {
|
||||
let currentWeather;
|
||||
let result = [];
|
||||
|
||||
@@ -157,10 +189,11 @@ WeatherProvider.register("smhi", {
|
||||
let dayWeatherTypes = [];
|
||||
|
||||
for (const weatherObject of allWeatherObjects) {
|
||||
//If its the first object or if a day change we need to reset the summary object
|
||||
if (!currentWeather || !currentWeather.date.isSame(weatherObject.date, "day")) {
|
||||
//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, groupBy)) {
|
||||
currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||
dayWeatherTypes = [];
|
||||
currentWeather.temperature = weatherObject.temperature;
|
||||
currentWeather.date = weatherObject.date;
|
||||
currentWeather.minTemperature = Infinity;
|
||||
currentWeather.maxTemperature = -Infinity;
|
||||
|
0
modules/default/weather/providers/ukmetoffice.js
Executable file → Normal file
0
modules/default/weather/providers/ukmetoffice.js
Executable file → Normal file
@@ -18,7 +18,6 @@ WeatherProvider.register("weatherbit", {
|
||||
// Set the default config properties that is specific to this provider
|
||||
defaults: {
|
||||
apiBase: "https://api.weatherbit.io/v2.0",
|
||||
weatherEndpoint: "/current",
|
||||
apiKey: "",
|
||||
lat: 0,
|
||||
lon: 0
|
||||
@@ -69,6 +68,31 @@ WeatherProvider.register("weatherbit", {
|
||||
.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.
|
||||
getUrl() {
|
||||
const units = this.units[this.config.units] || "auto";
|
||||
|
0
modules/default/weather/providers/weathergov.js
Executable file → Normal file
0
modules/default/weather/providers/weathergov.js
Executable file → Normal file
@@ -154,6 +154,15 @@ Module.register("weather", {
|
||||
if (this.weatherProvider.currentWeather()) {
|
||||
this.sendNotification("CURRENTWEATHER_TYPE", { type: this.weatherProvider.currentWeather().weatherType.replace("-", "_") });
|
||||
}
|
||||
|
||||
const notificationPayload = {
|
||||
currentWeather: this.weatherProvider?.currentWeatherObject?.simpleClone() ?? null,
|
||||
forecastArray: this.weatherProvider?.weatherForecastArray?.map((ar) => ar.simpleClone()) ?? [],
|
||||
hourlyArray: this.weatherProvider?.weatherHourlyArray?.map((ar) => ar.simpleClone()) ?? [],
|
||||
locationName: this.weatherProvider?.fetchedLocationName,
|
||||
providerName: this.weatherProvider.providerName
|
||||
};
|
||||
this.sendNotification("WEATHER_UPDATED", notificationPayload);
|
||||
},
|
||||
|
||||
scheduleUpdate: function (delay = null) {
|
||||
|
17
modules/default/weather/weatherobject.js
Executable file → Normal file
17
modules/default/weather/weatherobject.js
Executable file → Normal file
@@ -146,6 +146,23 @@ class WeatherObject {
|
||||
this.sunrise = moment(times.sunrise, "X");
|
||||
this.sunset = moment(times.sunset, "X");
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone to simple object to prevent mutating and deprecation of legacy library.
|
||||
*
|
||||
* Before being handed to other modules, mutable values must be cloned safely.
|
||||
* Especially 'moment' object is not immutable, so original 'date', 'sunrise', 'sunset' could be corrupted or changed by other modules.
|
||||
*
|
||||
* @returns {object} plained object clone of original weatherObject
|
||||
*/
|
||||
simpleClone() {
|
||||
const toFlat = ["date", "sunrise", "sunset"];
|
||||
let clone = { ...this };
|
||||
for (const prop of toFlat) {
|
||||
clone[prop] = clone?.[prop]?.valueOf() ?? clone?.[prop];
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
|
@@ -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";
|
||||
}
|
||||
});
|
6662
package-lock.json
generated
6662
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
55
package.json
55
package.json
@@ -1,15 +1,17 @@
|
||||
{
|
||||
"name": "magicmirror",
|
||||
"version": "2.19.0",
|
||||
"version": "2.21.0",
|
||||
"description": "The open source modular smart mirror platform.",
|
||||
"main": "js/electron.js",
|
||||
"scripts": {
|
||||
"start": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js",
|
||||
"start:dev": "DISPLAY=\"${DISPLAY:=:0}\" ./node_modules/.bin/electron js/electron.js dev",
|
||||
"server": "node ./serveronly",
|
||||
"install": "echo \"Installing vendor files ...\n\" && cd vendor && npm install --loglevel=error",
|
||||
"install-fonts": "echo \"Installing fonts ...\n\" && cd fonts && npm install --loglevel=error",
|
||||
"postinstall": "npm run install-fonts && echo \"MagicMirror² installation finished successfully! \n\"",
|
||||
"install-mm": "npm install --no-audit --no-fund --no-update-notifier --only=prod --omit=dev",
|
||||
"install-mm:dev": "npm install --no-audit --no-fund --no-update-notifier",
|
||||
"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:coverage": "NODE_ENV=test nyc --reporter=lcov --reporter=text jest -i --forceExit",
|
||||
"test:electron": "NODE_ENV=test jest --selectProjects electron -i --forceExit",
|
||||
@@ -48,48 +50,49 @@
|
||||
"homepage": "https://magicmirror.builders",
|
||||
"devDependencies": {
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-jest": "^26.1.3",
|
||||
"eslint-plugin-jsdoc": "^38.1.3",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-jest": "^27.0.4",
|
||||
"eslint-plugin-jsdoc": "^39.3.6",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"husky": "^7.0.4",
|
||||
"jest": "^27.5.1",
|
||||
"jsdom": "^19.0.0",
|
||||
"husky": "^8.0.1",
|
||||
"jest": "^29.0.3",
|
||||
"jsdom": "^20.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"nyc": "^15.1.0",
|
||||
"playwright": "^1.20.1",
|
||||
"prettier": "^2.6.1",
|
||||
"playwright": "^1.26.1",
|
||||
"prettier": "^2.7.1",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"sinon": "^13.0.1",
|
||||
"stylelint": "^14.6.1",
|
||||
"sinon": "^14.0.0",
|
||||
"stylelint": "^14.12.1",
|
||||
"stylelint-config-prettier": "^9.0.3",
|
||||
"stylelint-config-standard": "^25.0.0",
|
||||
"stylelint-config-standard": "^28.0.0",
|
||||
"stylelint-prettier": "^2.0.0",
|
||||
"suncalc": "^1.9.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"electron": "^17.2.0"
|
||||
"electron": "^19.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"colors": "^1.4.0",
|
||||
"console-stamp": "^3.0.4",
|
||||
"digest-fetch": "^1.2.1",
|
||||
"eslint": "^8.12.0",
|
||||
"express": "^4.17.3",
|
||||
"express-ipfilter": "^1.2.0",
|
||||
"console-stamp": "^3.0.6",
|
||||
"digest-fetch": "^1.3.0",
|
||||
"eslint": "^8.24.0",
|
||||
"express": "^4.18.1",
|
||||
"express-ipfilter": "^1.3.1",
|
||||
"feedme": "^2.0.2",
|
||||
"helmet": "^5.0.2",
|
||||
"helmet": "^6.0.0",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"luxon": "^1.21.3",
|
||||
"luxon": "^1.28.0",
|
||||
"module-alias": "^2.2.2",
|
||||
"moment": "^2.29.1",
|
||||
"moment": "^2.29.4",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-ical": "^0.15.1",
|
||||
"socket.io": "^4.4.1"
|
||||
"socket.io": "^4.5.2"
|
||||
},
|
||||
"_moduleAliases": {
|
||||
"node_helper": "js/node_helper.js",
|
||||
"logger": "js/logger.js"
|
||||
"logger": "js/logger.js",
|
||||
"fetch": "js/fetch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
|
@@ -12,7 +12,7 @@ BEGIN:STANDARD
|
||||
TZOFFSETFROM:+0000
|
||||
TZOFFSETTO:+0000
|
||||
TZNAME:GMT
|
||||
DTSTART:19700101T00000--äüüßßß-0
|
||||
DTSTART:19700101T000000
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
|
@@ -1,31 +1,32 @@
|
||||
const fetch = require("node-fetch");
|
||||
const fetch = require("fetch");
|
||||
const helpers = require("./global-setup");
|
||||
|
||||
describe("App environment", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("App environment", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/default.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
afterAll(async () => {
|
||||
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) => {
|
||||
done();
|
||||
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) => {
|
||||
expect(res.status).toBe(404);
|
||||
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) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.textContent).toBe("MagicMirror²");
|
||||
});
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const fetch = require("node-fetch");
|
||||
const fetch = require("fetch");
|
||||
const helpers = require("./global-setup");
|
||||
|
||||
describe("All font files from roboto.css should be downloadable", function () {
|
||||
@@ -17,8 +17,8 @@ describe("All font files from roboto.css should be downloadable", function () {
|
||||
beforeAll(function () {
|
||||
helpers.startApplication("tests/configs/without_modules.js");
|
||||
});
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
afterAll(async function () {
|
||||
await helpers.stopApplication();
|
||||
});
|
||||
|
||||
test.each(fontFiles)("should return 200 HTTP code for file '%s'", (fontFile, done) => {
|
||||
|
@@ -1,10 +1,8 @@
|
||||
const jsdom = require("jsdom");
|
||||
|
||||
exports.startApplication = function (configFilename, exec) {
|
||||
exports.startApplication = (configFilename, exec) => {
|
||||
jest.resetModules();
|
||||
if (global.app) {
|
||||
global.app.stop();
|
||||
}
|
||||
this.stopApplication();
|
||||
// Set config sample for use in test
|
||||
if (configFilename === "") {
|
||||
process.env.MM_CONFIG_FILE = "config/config.js";
|
||||
@@ -16,40 +14,60 @@ exports.startApplication = function (configFilename, exec) {
|
||||
global.app.start();
|
||||
};
|
||||
|
||||
exports.stopApplication = function () {
|
||||
exports.stopApplication = async () => {
|
||||
if (global.app) {
|
||||
global.app.stop();
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
};
|
||||
|
||||
exports.getDocument = function (callback) {
|
||||
exports.getDocument = (callback) => {
|
||||
const url = "http://" + (config.address || "localhost") + ":" + (config.port || "8080");
|
||||
jsdom.JSDOM.fromURL(url, { resources: "usable", runScripts: "dangerously" }).then((dom) => {
|
||||
dom.window.name = "jsdom";
|
||||
dom.window.onload = function () {
|
||||
global.MutationObserver = dom.window.MutationObserver;
|
||||
dom.window.onload = () => {
|
||||
global.document = dom.window.document;
|
||||
callback();
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
exports.waitForElement = function (selector) {
|
||||
exports.waitForElement = (selector, ignoreValue = "") => {
|
||||
return new Promise((resolve) => {
|
||||
if (document.querySelector(selector) && document.querySelector(selector).value !== undefined) {
|
||||
return resolve(document.querySelector(selector));
|
||||
}
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
if (document.querySelector(selector) && document.querySelector(selector).value !== undefined) {
|
||||
resolve(document.querySelector(selector));
|
||||
observer.disconnect();
|
||||
let oldVal = "dummy12345";
|
||||
const interval = setInterval(() => {
|
||||
const element = document.querySelector(selector);
|
||||
if (element) {
|
||||
let newVal = element.textContent;
|
||||
if (newVal === oldVal) {
|
||||
clearInterval(interval);
|
||||
resolve(element);
|
||||
} else {
|
||||
if (ignoreValue === "") {
|
||||
oldVal = newVal;
|
||||
} else {
|
||||
if (!newVal.includes(ignoreValue)) oldVal = newVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
}, 100);
|
||||
});
|
||||
};
|
||||
|
||||
exports.waitForAllElements = (selector) => {
|
||||
return new Promise((resolve) => {
|
||||
let oldVal = 999999;
|
||||
const interval = setInterval(() => {
|
||||
const element = document.querySelectorAll(selector);
|
||||
if (element) {
|
||||
let newVal = element.length;
|
||||
if (newVal === oldVal) {
|
||||
clearInterval(interval);
|
||||
resolve(element);
|
||||
} else {
|
||||
if (newVal !== 0) oldVal = newVal;
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
};
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const fetch = require("node-fetch");
|
||||
const fetch = require("fetch");
|
||||
const helpers = require("./global-setup");
|
||||
|
||||
describe("ipWhitelist directive configuration", function () {
|
||||
@@ -6,8 +6,8 @@ describe("ipWhitelist directive configuration", function () {
|
||||
beforeAll(function () {
|
||||
helpers.startApplication("tests/configs/noIpWhiteList.js");
|
||||
});
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
afterAll(async function () {
|
||||
await helpers.stopApplication();
|
||||
});
|
||||
|
||||
it("should return 403", function (done) {
|
||||
@@ -22,8 +22,8 @@ describe("ipWhitelist directive configuration", function () {
|
||||
beforeAll(function () {
|
||||
helpers.startApplication("tests/configs/empty_ipWhiteList.js");
|
||||
});
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
afterAll(async function () {
|
||||
await helpers.stopApplication();
|
||||
});
|
||||
|
||||
it("should return 200", function (done) {
|
||||
|
@@ -1,16 +1,17 @@
|
||||
const helpers = require("../global-setup");
|
||||
|
||||
describe("Alert module", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Alert module", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/alert/default.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
afterAll(async () => {
|
||||
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) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.textContent).toContain("Welcome, start was successful!");
|
||||
});
|
||||
|
@@ -1,14 +1,16 @@
|
||||
const helpers = require("../global-setup");
|
||||
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} result expected number
|
||||
* @param {string} not reverse result
|
||||
*/
|
||||
function testElementLength(element, result, not) {
|
||||
helpers.waitForElement(element).then((elem) => {
|
||||
const testElementLength = (done, element, result, not) => {
|
||||
helpers.waitForAllElements(element).then((elem) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
if (not === "not") {
|
||||
expect(elem.length).not.toBe(result);
|
||||
@@ -16,147 +18,148 @@ describe("Calendar module", function () {
|
||||
expect(elem.length).toBe(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const testTextContain = function (element, text) {
|
||||
helpers.waitForElement(element).then((elem) => {
|
||||
const testTextContain = (done, element, text) => {
|
||||
helpers.waitForElement(element, "undefinedLoading").then((elem) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.textContent).toContain(text);
|
||||
});
|
||||
};
|
||||
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
afterAll(async () => {
|
||||
await helpers.stopApplication();
|
||||
});
|
||||
|
||||
describe("Default configuration", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Default configuration", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/calendar/default.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it("should show the default maximumEntries of 10", () => {
|
||||
testElementLength(".calendar .event", 10);
|
||||
it("should show the default maximumEntries of 10", (done) => {
|
||||
testElementLength(done, ".calendar .event", 10);
|
||||
});
|
||||
|
||||
it("should show the default calendar symbol in each event", () => {
|
||||
testElementLength(".calendar .event .fa-calendar-alt", 0, "not");
|
||||
it("should show the default calendar symbol in each event", (done) => {
|
||||
testElementLength(done, ".calendar .event .fa-calendar-alt", 0, "not");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Custom configuration", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Custom configuration", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/calendar/custom.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it("should show the custom maximumEntries of 4", () => {
|
||||
testElementLength(".calendar .event", 4);
|
||||
it("should show the custom maximumEntries of 4", (done) => {
|
||||
testElementLength(done, ".calendar .event", 4);
|
||||
});
|
||||
|
||||
it("should show the custom calendar symbol in each event", () => {
|
||||
testElementLength(".calendar .event .fa-birthday-cake", 4);
|
||||
it("should show the custom calendar symbol in each event", (done) => {
|
||||
testElementLength(done, ".calendar .event .fa-birthday-cake", 4);
|
||||
});
|
||||
|
||||
it("should show two custom icons for repeating events", () => {
|
||||
testElementLength(".calendar .event .fa-undo", 2);
|
||||
it("should show two custom icons for repeating events", (done) => {
|
||||
testElementLength(done, ".calendar .event .fa-undo", 2);
|
||||
});
|
||||
|
||||
it("should show two custom icons for day events", () => {
|
||||
testElementLength(".calendar .event .fa-calendar-day", 2);
|
||||
it("should show two custom icons for day events", (done) => {
|
||||
testElementLength(done, ".calendar .event .fa-calendar-day", 2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Recurring event", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Recurring event", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/calendar/recurring.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it("should show the recurring birthday event 6 times", () => {
|
||||
testElementLength(".calendar .event", 6);
|
||||
it("should show the recurring birthday event 6 times", (done) => {
|
||||
testElementLength(done, ".calendar .event", 6);
|
||||
});
|
||||
});
|
||||
|
||||
process.setMaxListeners(0);
|
||||
for (let i = -12; i < 12; i++) {
|
||||
describe("Recurring event per timezone", function () {
|
||||
beforeAll(function (done) {
|
||||
Date.prototype.getTimezoneOffset = function () {
|
||||
describe("Recurring event per timezone", () => {
|
||||
beforeAll((done) => {
|
||||
Date.prototype.getTimezoneOffset = () => {
|
||||
return i * 60;
|
||||
};
|
||||
helpers.startApplication("tests/configs/modules/calendar/recurring.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it('should contain text "Mar 25th" in timezone UTC ' + -i, () => {
|
||||
testTextContain(".calendar", "Mar 25th");
|
||||
it('should contain text "Mar 25th" in timezone UTC ' + -i, (done) => {
|
||||
testTextContain(done, ".calendar", "Mar 25th");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe("Changed port", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Changed port", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/calendar/changed-port.js");
|
||||
serverBasicAuth.listen(8010);
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
afterAll(function (done) {
|
||||
afterAll((done) => {
|
||||
serverBasicAuth.close(done());
|
||||
});
|
||||
|
||||
it("should return TestEvents", function () {
|
||||
testElementLength(".calendar .event", 0, "not");
|
||||
it("should return TestEvents", (done) => {
|
||||
testElementLength(done, ".calendar .event", 0, "not");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Basic auth", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Basic auth", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/calendar/basic-auth.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it("should return TestEvents", function () {
|
||||
testElementLength(".calendar .event", 0, "not");
|
||||
it("should return TestEvents", (done) => {
|
||||
testElementLength(done, ".calendar .event", 0, "not");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Basic auth by default", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Basic auth by default", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/calendar/auth-default.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it("should return TestEvents", function () {
|
||||
testElementLength(".calendar .event", 0, "not");
|
||||
it("should return TestEvents", (done) => {
|
||||
testElementLength(done, ".calendar .event", 0, "not");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Basic auth backward compatibility configuration: DEPRECATED", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Basic auth backward compatibility configuration: DEPRECATED", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/calendar/old-basic-auth.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it("should return TestEvents", function () {
|
||||
testElementLength(".calendar .event", 0, "not");
|
||||
it("should return TestEvents", (done) => {
|
||||
testElementLength(done, ".calendar .event", 0, "not");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Fail Basic auth", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Fail Basic auth", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/calendar/fail-basic-auth.js");
|
||||
serverBasicAuth.listen(8020);
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
afterAll(function (done) {
|
||||
afterAll((done) => {
|
||||
serverBasicAuth.close(done());
|
||||
});
|
||||
|
||||
it("should show Unauthorized error", function () {
|
||||
testTextContain(".calendar", "Error in the calendar module. Authorization failed");
|
||||
it("should show Unauthorized error", (done) => {
|
||||
testTextContain(done, ".calendar", "Error in the calendar module. Authorization failed");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,72 +1,73 @@
|
||||
const helpers = require("../global-setup");
|
||||
|
||||
describe("Clock set to spanish language module", function () {
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
describe("Clock set to spanish language module", () => {
|
||||
afterAll(async () => {
|
||||
await helpers.stopApplication();
|
||||
});
|
||||
|
||||
const testMatch = function (element, regex) {
|
||||
const testMatch = (done, element, regex) => {
|
||||
helpers.waitForElement(element).then((elem) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.textContent).toMatch(regex);
|
||||
});
|
||||
};
|
||||
|
||||
describe("with default 24hr clock config", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("with default 24hr clock config", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/clock/es/clock_24hr.js");
|
||||
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}$/;
|
||||
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$/;
|
||||
testMatch(".clock .time", timeRegex);
|
||||
testMatch(done, ".clock .time", timeRegex);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with default 12hr clock config", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("with default 12hr clock config", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/clock/es/clock_12hr.js");
|
||||
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}$/;
|
||||
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$/;
|
||||
testMatch(".clock .time", timeRegex);
|
||||
testMatch(done, ".clock .time", timeRegex);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with showPeriodUpper config enabled", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("with showPeriodUpper config enabled", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/clock/es/clock_showPeriodUpper.js");
|
||||
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$/;
|
||||
testMatch(".clock .time", timeRegex);
|
||||
testMatch(done, ".clock .time", timeRegex);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with showWeek config enabled", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("with showWeek config enabled", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/clock/es/clock_showWeek.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it("shows week with correct format", function () {
|
||||
it("shows week with correct format", (done) => {
|
||||
const weekRegex = /^Semana [0-9]{1,2}$/;
|
||||
testMatch(".clock .week", weekRegex);
|
||||
testMatch(done, ".clock .week", weekRegex);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,118 +1,121 @@
|
||||
const helpers = require("../global-setup");
|
||||
const moment = require("moment");
|
||||
|
||||
describe("Clock module", function () {
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
describe("Clock module", () => {
|
||||
afterAll(async () => {
|
||||
await helpers.stopApplication();
|
||||
});
|
||||
|
||||
const testMatch = function (element, regex) {
|
||||
const testMatch = (done, element, regex) => {
|
||||
helpers.waitForElement(element).then((elem) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.textContent).toMatch(regex);
|
||||
});
|
||||
};
|
||||
|
||||
describe("with default 24hr clock config", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("with default 24hr clock config", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/clock/clock_24hr.js");
|
||||
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}$/;
|
||||
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$/;
|
||||
testMatch(".clock .time", timeRegex);
|
||||
testMatch(done, ".clock .time", timeRegex);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with default 12hr clock config", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("with default 12hr clock config", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/clock/clock_12hr.js");
|
||||
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}$/;
|
||||
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$/;
|
||||
testMatch(".clock .time", timeRegex);
|
||||
testMatch(done, ".clock .time", timeRegex);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with showPeriodUpper config enabled", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("with showPeriodUpper config enabled", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/clock/clock_showPeriodUpper.js");
|
||||
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$/;
|
||||
testMatch(".clock .time", timeRegex);
|
||||
testMatch(done, ".clock .time", timeRegex);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with displaySeconds config disabled", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("with displaySeconds config disabled", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/clock/clock_displaySeconds_false.js");
|
||||
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$/;
|
||||
testMatch(".clock .time", timeRegex);
|
||||
testMatch(done, ".clock .time", timeRegex);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with showTime config disabled", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("with showTime config disabled", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/clock/clock_showTime.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it("should show not show the time when digital clock is shown", function () {
|
||||
helpers.waitForElement(".clock .digital .time").then((elem) => {
|
||||
expect(elem).toBe(null);
|
||||
});
|
||||
it("should not show the time when digital clock is shown", (done) => {
|
||||
const elem = document.querySelector(".clock .digital .time");
|
||||
done();
|
||||
expect(elem).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with showWeek config enabled", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("with showWeek config enabled", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/clock/clock_showWeek.js");
|
||||
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}$/;
|
||||
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 weekToShow = "Week " + currentWeekNumber;
|
||||
helpers.waitForElement(".clock .week").then((elem) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.textContent).toBe(weekToShow);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("with analog clock face enabled", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("with analog clock face enabled", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/clock/clock_analog.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it("should show the analog clock face", () => {
|
||||
it("should show the analog clock face", (done) => {
|
||||
helpers.waitForElement(".clockCircle").then((elem) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
});
|
||||
});
|
||||
|
@@ -3,87 +3,95 @@ const helpers = require("../global-setup");
|
||||
/**
|
||||
* move similar tests in function doTest
|
||||
*
|
||||
* @param {string} done test done
|
||||
* @param {Array} complimentsArray The array of compliments.
|
||||
*/
|
||||
function doTest(complimentsArray) {
|
||||
const doTest = (done, complimentsArray) => {
|
||||
helpers.waitForElement(".compliments").then((elem) => {
|
||||
expect(elem).not.toBe(null);
|
||||
helpers.waitForElement(".module-content").then((elem) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(complimentsArray).toContain(elem.textContent);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
describe("Compliments module", function () {
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
describe("Compliments module", () => {
|
||||
afterAll(async () => {
|
||||
await helpers.stopApplication();
|
||||
});
|
||||
|
||||
describe("parts of days", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("parts of days", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/compliments/compliments_parts_day.js");
|
||||
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();
|
||||
if (hour >= 3 && hour < 12) {
|
||||
// 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();
|
||||
if (hour >= 12 && hour < 17) {
|
||||
// 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();
|
||||
if (!(hour >= 3 && hour < 12) && !(hour >= 12 && hour < 17)) {
|
||||
// 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("Set anytime and empty compliments for morning, evening and afternoon ", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Feature anytime in compliments module", () => {
|
||||
describe("Set anytime and empty compliments for morning, evening and afternoon ", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/compliments/compliments_anytime.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it("Show anytime because if configure empty parts of day compliments and set anytime compliments", function () {
|
||||
doTest(["Anytime here"]);
|
||||
it("Show anytime because if configure empty parts of day compliments and set anytime compliments", (done) => {
|
||||
doTest(done, ["Anytime here"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Only anytime present in configuration compliments", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Only anytime present in configuration compliments", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/compliments/compliments_only_anytime.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it("Show anytime compliments", function () {
|
||||
doTest(["Anytime here"]);
|
||||
it("Show anytime compliments", (done) => {
|
||||
doTest(done, ["Anytime here"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Feature date in compliments module", function () {
|
||||
describe("Set date and empty compliments for anytime, morning, evening and afternoon", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Feature date in compliments module", () => {
|
||||
describe("Set date and empty compliments for anytime, morning, evening and afternoon", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/compliments/compliments_date.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it("Show happy new year compliment on new years day", function () {
|
||||
doTest(["Happy new year!"]);
|
||||
it("Show happy new year compliment on new years day", (done) => {
|
||||
doTest(done, ["Happy new year!"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,32 +1,34 @@
|
||||
const helpers = require("../global-setup");
|
||||
|
||||
describe("Test helloworld module", function () {
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
describe("Test helloworld module", () => {
|
||||
afterAll(async () => {
|
||||
await helpers.stopApplication();
|
||||
});
|
||||
|
||||
describe("helloworld set config text", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("helloworld set config text", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/helloworld/helloworld.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it("Test message helloworld module", function () {
|
||||
it("Test message helloworld module", (done) => {
|
||||
helpers.waitForElement(".helloworld").then((elem) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.textContent).toContain("Test HelloWorld Module");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("helloworld default config text", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("helloworld default config text", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/helloworld/helloworld_default.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it("Test message helloworld module", function () {
|
||||
it("Test message helloworld module", (done) => {
|
||||
helpers.waitForElement(".helloworld").then((elem) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.textContent).toContain("Hello World!");
|
||||
});
|
||||
|
@@ -1,80 +1,88 @@
|
||||
const helpers = require("../global-setup");
|
||||
|
||||
describe("Newsfeed module", function () {
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
describe("Newsfeed module", () => {
|
||||
afterAll(async () => {
|
||||
await helpers.stopApplication();
|
||||
});
|
||||
|
||||
describe("Default configuration", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Default configuration", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/newsfeed/default.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it("should show the newsfeed title", function () {
|
||||
it("should show the newsfeed title", (done) => {
|
||||
helpers.waitForElement(".newsfeed .newsfeed-source").then((elem) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.textContent).toContain("Rodrigo Ramirez Blog");
|
||||
});
|
||||
});
|
||||
|
||||
it("should show the newsfeed article", function () {
|
||||
it("should show the newsfeed article", (done) => {
|
||||
helpers.waitForElement(".newsfeed .newsfeed-title").then((elem) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.textContent).toContain("QPanel");
|
||||
});
|
||||
});
|
||||
|
||||
it("should NOT show the newsfeed description", () => {
|
||||
helpers.waitForElement(".newsfeed .newsfeed-desc").then((elem) => {
|
||||
expect(elem).toBe(null);
|
||||
it("should NOT show the newsfeed description", (done) => {
|
||||
helpers.waitForElement(".newsfeed").then((elem) => {
|
||||
const element = document.querySelector(".newsfeed .newsfeed-desc");
|
||||
done();
|
||||
expect(element).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Custom configuration", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Custom configuration", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/newsfeed/prohibited_words.js");
|
||||
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) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
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) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.textContent.length).not.toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Invalid configuration", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Invalid configuration", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/newsfeed/incorrect_url.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
|
||||
it("should show malformed url warning", function () {
|
||||
helpers.waitForElement(".newsfeed .small").then((elem) => {
|
||||
it("should show malformed url warning", (done) => {
|
||||
helpers.waitForElement(".newsfeed .small", "No news at the moment.").then((elem) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.textContent).toContain("Error in the Newsfeed module. Malformed url.");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Ignore items", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Ignore items", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/newsfeed/ignore_items.js");
|
||||
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) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.textContent).toContain("No news at the moment.");
|
||||
});
|
||||
|
@@ -4,13 +4,15 @@ const path = require("path");
|
||||
const fs = require("fs");
|
||||
const { generateWeather, generateWeatherForecast } = require("./mocks");
|
||||
|
||||
describe("Weather module", function () {
|
||||
describe("Weather module", () => {
|
||||
/**
|
||||
* @param {string} done test done
|
||||
* @param {string} element css selector
|
||||
* @param {string} result Expected text in given selector
|
||||
*/
|
||||
function getText(element, result) {
|
||||
const getText = (done, element, result) => {
|
||||
helpers.waitForElement(element).then((elem) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(
|
||||
elem.textContent
|
||||
@@ -19,14 +21,14 @@ describe("Weather module", function () {
|
||||
.replace(/[ ]+/g, " ")
|
||||
).toBe(result);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} configFile path to configuration file
|
||||
* @param {string} additionalMockData special data for mocking
|
||||
* @param {string} callback callback
|
||||
*/
|
||||
function startApp(configFile, additionalMockData, callback) {
|
||||
const startApp = (configFile, additionalMockData, callback) => {
|
||||
let mockWeather;
|
||||
if (configFile.includes("forecast")) {
|
||||
mockWeather = generateWeatherForecast(additionalMockData);
|
||||
@@ -38,94 +40,98 @@ describe("Weather module", function () {
|
||||
fs.writeFileSync(path.resolve(__dirname + "../../../../config/config.js"), content);
|
||||
helpers.startApplication("");
|
||||
helpers.getDocument(callback);
|
||||
}
|
||||
};
|
||||
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
afterAll(async () => {
|
||||
await helpers.stopApplication();
|
||||
});
|
||||
|
||||
describe("Current weather", function () {
|
||||
describe("Default configuration", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Current weather", () => {
|
||||
describe("Default configuration", () => {
|
||||
beforeAll((done) => {
|
||||
startApp("tests/configs/modules/weather/currentweather_default.js", {}, done);
|
||||
});
|
||||
|
||||
it("should render wind speed and wind direction", function () {
|
||||
getText(".weather .normal.medium span:nth-child(2)", "6 WSW");
|
||||
it("should render wind speed and wind direction", (done) => {
|
||||
getText(done, ".weather .normal.medium span:nth-child(2)", "6 WSW"); // now "12"
|
||||
});
|
||||
|
||||
it("should render temperature with icon", function () {
|
||||
getText(".weather .large.light span.bright", "1.5°");
|
||||
it("should render temperature with icon", (done) => {
|
||||
getText(done, ".weather .large.light span.bright", "1.5°"); // now "1°C"
|
||||
});
|
||||
|
||||
it("should render feels like temperature", function () {
|
||||
getText(".weather .normal.medium.feelslike span.dimmed", "Feels like -5.6°");
|
||||
it("should render feels like temperature", (done) => {
|
||||
getText(done, ".weather .normal.medium.feelslike span.dimmed", "Feels like -5.6°"); // now "Feels like -6°C"
|
||||
});
|
||||
});
|
||||
|
||||
describe("Default configuration with sunrise", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Default configuration with sunrise", () => {
|
||||
beforeAll((done) => {
|
||||
const sunrise = moment().startOf("day").unix();
|
||||
const sunset = moment().startOf("day").unix();
|
||||
startApp("tests/configs/modules/weather/currentweather_default.js", { sys: { sunrise, sunset } }, done);
|
||||
});
|
||||
|
||||
it("should render sunrise", function () {
|
||||
getText(".weather .normal.medium span:nth-child(4)", "12:00 am");
|
||||
it("should render sunrise", (done) => {
|
||||
getText(done, ".weather .normal.medium span:nth-child(4)", "12:00 am");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Default configuration with sunset", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Default configuration with sunset", () => {
|
||||
beforeAll((done) => {
|
||||
const sunrise = moment().startOf("day").unix();
|
||||
const sunset = moment().endOf("day").unix();
|
||||
startApp("tests/configs/modules/weather/currentweather_default.js", { sys: { sunrise, sunset } }, done);
|
||||
});
|
||||
|
||||
it("should render sunset", function () {
|
||||
getText(".weather .normal.medium span:nth-child(4)", "11:59 pm");
|
||||
it("should render sunset", (done) => {
|
||||
getText(done, ".weather .normal.medium span:nth-child(4)", "11:59 pm");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Compliments Integration", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Compliments Integration", () => {
|
||||
beforeAll((done) => {
|
||||
startApp("tests/configs/modules/weather/currentweather_compliments.js", {}, done);
|
||||
});
|
||||
|
||||
it("should render a compliment based on the current weather", function () {
|
||||
getText(".compliments .module-content span", "snow");
|
||||
it("should render a compliment based on the current weather", (done) => {
|
||||
getText(done, ".compliments .module-content span", "snow");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Configuration Options", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Configuration Options", () => {
|
||||
beforeAll((done) => {
|
||||
startApp("tests/configs/modules/weather/currentweather_options.js", {}, done);
|
||||
});
|
||||
|
||||
it("should render useBeaufort = false", function () {
|
||||
getText(".weather .normal.medium span:nth-child(2)", "12");
|
||||
it("should render useBeaufort = false", (done) => {
|
||||
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) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.outerHTML).toContain("transform:rotate(250deg);");
|
||||
});
|
||||
});
|
||||
|
||||
it("should render showHumidity = true", function () {
|
||||
getText(".weather .normal.medium span:nth-child(3)", "93.7");
|
||||
it("should render showHumidity = true", (done) => {
|
||||
getText(done, ".weather .normal.medium span:nth-child(3)", "93.7");
|
||||
});
|
||||
|
||||
it("should render degreeLabel = true", function () {
|
||||
getText(".weather .large.light span.bright", "1°C");
|
||||
getText(".weather .normal.medium.feelslike span.dimmed", "Feels like -6°C");
|
||||
it("should render degreeLabel = true for temp", (done) => {
|
||||
getText(done, ".weather .large.light span.bright", "1°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 () {
|
||||
beforeAll(function (done) {
|
||||
describe("Current weather units", () => {
|
||||
beforeAll((done) => {
|
||||
startApp(
|
||||
"tests/configs/modules/weather/currentweather_units.js",
|
||||
{
|
||||
@@ -142,98 +148,108 @@ describe("Weather module", function () {
|
||||
);
|
||||
});
|
||||
|
||||
it("should render imperial units", function () {
|
||||
getText(".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 imperial units for wind", (done) => {
|
||||
getText(done, ".weather .normal.medium span:nth-child(2)", "6 WSW");
|
||||
});
|
||||
|
||||
it("should render custom decimalSymbol = ','", function () {
|
||||
getText(".weather .normal.medium span:nth-child(3)", "93,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 temp", (done) => {
|
||||
getText(done, ".weather .large.light span.bright", "34,7°");
|
||||
});
|
||||
|
||||
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("Default configuration", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Weather Forecast", () => {
|
||||
describe("Default configuration", () => {
|
||||
beforeAll((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()) {
|
||||
it("should render day " + day, (done) => {
|
||||
getText(done, `.weather table.small tr:nth-child(${index + 1}) td:nth-child(1)`, day);
|
||||
});
|
||||
}
|
||||
|
||||
for (const [index, day] of days.entries()) {
|
||||
getText(`.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"];
|
||||
|
||||
for (const [index, icon] of icons.entries()) {
|
||||
const icons = ["day-cloudy", "rain", "day-sunny", "day-sunny", "day-sunny"];
|
||||
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) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it("should render max temperatures", function () {
|
||||
const temperatures = ["24.4°", "21.0°", "22.9°", "23.4°", "20.6°"];
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
for (const [index, temp] of temperatures.entries()) {
|
||||
getText(`.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];
|
||||
|
||||
for (const [index, opacity] of opacities.entries()) {
|
||||
const opacities = [1, 1, 0.8, 0.5333333333333333, 0.2666666666666667];
|
||||
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) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.outerHTML).toContain(`<tr style="opacity: ${opacity};">`);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("Absolute configuration", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Absolute configuration", () => {
|
||||
beforeAll((done) => {
|
||||
startApp("tests/configs/modules/weather/forecastweather_absolute.js", {}, done);
|
||||
});
|
||||
|
||||
it("should render days", function () {
|
||||
const days = ["Fri", "Sat", "Sun", "Mon", "Tue"];
|
||||
|
||||
for (const [index, day] of days.entries()) {
|
||||
getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(1)`, day);
|
||||
}
|
||||
});
|
||||
const days = ["Fri", "Sat", "Sun", "Mon", "Tue"];
|
||||
for (const [index, day] of days.entries()) {
|
||||
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 () {
|
||||
beforeAll(function (done) {
|
||||
describe("Configuration Options", () => {
|
||||
beforeAll((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) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
it("should render colored rows", function () {
|
||||
it("should render colored rows", (done) => {
|
||||
helpers.waitForElement(".weather table.myTableClass").then((table) => {
|
||||
done();
|
||||
expect(table).not.toBe(null);
|
||||
expect(table.rows).not.toBe(null);
|
||||
expect(table.rows.length).toBe(5);
|
||||
@@ -241,18 +257,17 @@ describe("Weather module", function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Forecast weather units", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Forecast weather units", () => {
|
||||
beforeAll((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°"];
|
||||
|
||||
for (const [index, temp] of temperatures.entries()) {
|
||||
getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(3)`, temp);
|
||||
}
|
||||
});
|
||||
const temperatures = ["24_4°", "21_0°", "22_9°", "23_4°", "20_6°"];
|
||||
for (const [index, temp] of temperatures.entries()) {
|
||||
it("should render custom decimalSymbol = '_' for temp " + temp, (done) => {
|
||||
getText(done, `.weather table.small tr:nth-child(${index + 1}) td:nth-child(3)`, temp);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,24 +1,26 @@
|
||||
const helpers = require("./global-setup");
|
||||
|
||||
describe("Display of modules", function () {
|
||||
describe("Display of modules", () => {
|
||||
beforeAll(function (done) {
|
||||
helpers.startApplication("tests/configs/modules/display.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
afterAll(async () => {
|
||||
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) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
// textContent gibt hier lowercase zurück, das uppercase wird durch css realisiert, was daher nicht in textContent landet
|
||||
expect(elem.textContent).toBe("test_header");
|
||||
});
|
||||
});
|
||||
|
||||
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) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.textContent).toBe("undefined");
|
||||
});
|
||||
|
@@ -1,20 +1,21 @@
|
||||
const helpers = require("./global-setup");
|
||||
|
||||
describe("Position of modules", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Position of modules", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/modules/positions.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
afterAll(async () => {
|
||||
await helpers.stopApplication();
|
||||
});
|
||||
|
||||
const positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"];
|
||||
|
||||
for (const position of positions) {
|
||||
const className = position.replace("_", ".");
|
||||
it("should show text in " + position, function () {
|
||||
it("should show text in " + position, (done) => {
|
||||
helpers.waitForElement("." + className).then((elem) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.textContent).toContain("Text in " + position);
|
||||
});
|
||||
|
@@ -1,4 +1,4 @@
|
||||
const fetch = require("node-fetch");
|
||||
const fetch = require("fetch");
|
||||
const helpers = require("./global-setup");
|
||||
|
||||
describe("port directive configuration", function () {
|
||||
@@ -6,8 +6,8 @@ describe("port directive configuration", function () {
|
||||
beforeAll(function () {
|
||||
helpers.startApplication("tests/configs/port_8090.js");
|
||||
});
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
afterAll(async function () {
|
||||
await helpers.stopApplication();
|
||||
});
|
||||
|
||||
it("should return 200", function (done) {
|
||||
@@ -22,8 +22,8 @@ describe("port directive configuration", function () {
|
||||
beforeAll(function () {
|
||||
helpers.startApplication("tests/configs/port_8090.js", (process.env.MM_PORT = 8100));
|
||||
});
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
afterAll(async function () {
|
||||
await helpers.stopApplication();
|
||||
});
|
||||
|
||||
it("should return 200", function (done) {
|
||||
|
@@ -164,7 +164,7 @@ describe("Translations", function () {
|
||||
dom.window.onload = function () {
|
||||
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();
|
||||
done();
|
||||
});
|
||||
@@ -175,8 +175,10 @@ describe("Translations", function () {
|
||||
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) {
|
||||
if (language === "en") {
|
||||
if (language === "de") {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@@ -1,12 +1,12 @@
|
||||
const fetch = require("node-fetch");
|
||||
const fetch = require("fetch");
|
||||
const helpers = require("./global-setup");
|
||||
|
||||
describe("Vendors", function () {
|
||||
beforeAll(function () {
|
||||
helpers.startApplication("tests/configs/default.js");
|
||||
});
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
afterAll(async function () {
|
||||
await helpers.stopApplication();
|
||||
});
|
||||
|
||||
describe("Get list vendors", function () {
|
||||
|
@@ -1,23 +1,25 @@
|
||||
const helpers = require("./global-setup");
|
||||
|
||||
describe("Check configuration without modules", function () {
|
||||
beforeAll(function (done) {
|
||||
describe("Check configuration without modules", () => {
|
||||
beforeAll((done) => {
|
||||
helpers.startApplication("tests/configs/without_modules.js");
|
||||
helpers.getDocument(done);
|
||||
});
|
||||
afterAll(function () {
|
||||
helpers.stopApplication();
|
||||
afterAll(async () => {
|
||||
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) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
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) => {
|
||||
done();
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.textContent).toContain("www.michaelteeuw.nl");
|
||||
});
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Updatenotification custom module returns status information without hash 1`] = `
|
||||
Object {
|
||||
{
|
||||
"behind": 7,
|
||||
"current": "master",
|
||||
"hash": "",
|
||||
@@ -12,7 +12,7 @@ Object {
|
||||
`;
|
||||
|
||||
exports[`Updatenotification default returns status information 1`] = `
|
||||
Object {
|
||||
{
|
||||
"behind": 5,
|
||||
"current": "develop",
|
||||
"hash": "332e429a41f1a2339afd4f0ae96dd125da6beada",
|
||||
@@ -23,7 +23,7 @@ Object {
|
||||
`;
|
||||
|
||||
exports[`Updatenotification default returns status information early if isBehindInStatus 1`] = `
|
||||
Object {
|
||||
{
|
||||
"behind": 5,
|
||||
"current": "develop",
|
||||
"hash": "332e429a41f1a2339afd4f0ae96dd125da6beada",
|
||||
|
@@ -3,7 +3,6 @@
|
||||
|
||||
"TODAY": "Today",
|
||||
"TOMORROW": "Tomorrow",
|
||||
"DAYAFTERTOMORROW": "In 2 days",
|
||||
"RUNNING": "Ends in",
|
||||
"EMPTY": "No upcoming events.",
|
||||
"WEEK": "Week {weekNumber}",
|
||||
|
42
vendor/package-lock.json
generated
vendored
42
vendor/package-lock.json
generated
vendored
@@ -7,18 +7,18 @@
|
||||
"name": "magicmirror-vendors",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.1.1",
|
||||
"moment": "^2.29.1",
|
||||
"moment-timezone": "^0.5.34",
|
||||
"@fortawesome/fontawesome-free": "^6.2.0",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.37",
|
||||
"nunjucks": "^3.2.3",
|
||||
"suncalc": "^1.9.0",
|
||||
"weathericons": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-free": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.1.1.tgz",
|
||||
"integrity": "sha512-J/3yg2AIXc9wznaVqpHVX3Wa5jwKovVF0AMYSnbmcXTiL3PpRPfF58pzWucCwEiCJBp+hCNRLWClTomD8SseKg==",
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.0.tgz",
|
||||
"integrity": "sha512-CNR7qRIfCwWHNN7FnKUniva94edPdyQzil/zCwk3v6k4R6rR2Fr8i4s3PM7n/lyfPA6Zfko9z5WDzFxG9SW1uQ==",
|
||||
"hasInstallScript": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -43,17 +43,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.29.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/moment-timezone": {
|
||||
"version": "0.5.34",
|
||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz",
|
||||
"integrity": "sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==",
|
||||
"version": "0.5.37",
|
||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.37.tgz",
|
||||
"integrity": "sha512-uEDzDNFhfaywRl+vwXxffjjq1q0Vzr+fcQpQ1bU0kbzorfS7zVtZnCnGc8mhWmF39d4g4YriF6kwA75mJKE/Zg==",
|
||||
"dependencies": {
|
||||
"moment": ">= 2.9.0"
|
||||
},
|
||||
@@ -98,9 +98,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.1.1.tgz",
|
||||
"integrity": "sha512-J/3yg2AIXc9wznaVqpHVX3Wa5jwKovVF0AMYSnbmcXTiL3PpRPfF58pzWucCwEiCJBp+hCNRLWClTomD8SseKg=="
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.2.0.tgz",
|
||||
"integrity": "sha512-CNR7qRIfCwWHNN7FnKUniva94edPdyQzil/zCwk3v6k4R6rR2Fr8i4s3PM7n/lyfPA6Zfko9z5WDzFxG9SW1uQ=="
|
||||
},
|
||||
"a-sync-waterfall": {
|
||||
"version": "1.0.1",
|
||||
@@ -118,14 +118,14 @@
|
||||
"integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.29.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
|
||||
},
|
||||
"moment-timezone": {
|
||||
"version": "0.5.34",
|
||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz",
|
||||
"integrity": "sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==",
|
||||
"version": "0.5.37",
|
||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.37.tgz",
|
||||
"integrity": "sha512-uEDzDNFhfaywRl+vwXxffjjq1q0Vzr+fcQpQ1bU0kbzorfS7zVtZnCnGc8mhWmF39d4g4YriF6kwA75mJKE/Zg==",
|
||||
"requires": {
|
||||
"moment": ">= 2.9.0"
|
||||
}
|
||||
|
6
vendor/package.json
vendored
Executable file → Normal file
6
vendor/package.json
vendored
Executable file → Normal file
@@ -10,9 +10,9 @@
|
||||
"url": "https://github.com/MichMich/MagicMirror/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.1.1",
|
||||
"moment": "^2.29.1",
|
||||
"moment-timezone": "^0.5.34",
|
||||
"@fortawesome/fontawesome-free": "^6.2.0",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.37",
|
||||
"nunjucks": "^3.2.3",
|
||||
"suncalc": "^1.9.0",
|
||||
"weathericons": "^2.1.0"
|
||||
|
0
vendor/vendor.js
vendored
Executable file → Normal file
0
vendor/vendor.js
vendored
Executable file → Normal file
Reference in New Issue
Block a user