├── tests ├── configs │ ├── port_variable.env │ ├── without_modules.js │ ├── port_8090.js │ ├── port_variable.js.template │ ├── empty_ipWhiteList.js │ ├── noIpWhiteList.js │ ├── modules │ │ ├── clock │ │ │ ├── clock_24hr.js │ │ │ ├── clock_12hr.js │ │ │ ├── es │ │ │ │ ├── clock_24hr.js │ │ │ │ ├── clock_12hr.js │ │ │ │ ├── clock_showWeek.js │ │ │ │ ├── clock_showWeek_short.js │ │ │ │ └── clock_showPeriodUpper.js │ │ │ ├── clock_showWeek.js │ │ │ ├── clock_showTime.js │ │ │ ├── clock_showWeek_short.js │ │ │ ├── clock_showPeriodUpper.js │ │ │ ├── clock_displaySeconds_false.js │ │ │ ├── clock_showSunNoEvent.js │ │ │ ├── de │ │ │ │ ├── clock_showWeek.js │ │ │ │ └── clock_showWeek_short.js │ │ │ ├── clock_analog.js │ │ │ ├── clock_showSunMoon.js │ │ │ └── clock_showDateAnalog.js │ │ ├── helloworld │ │ │ ├── helloworld_default.js │ │ │ └── helloworld.js │ │ ├── alert │ │ │ ├── welcome_true.js │ │ │ ├── welcome_false.js │ │ │ └── welcome_string.js │ │ ├── compliments │ │ │ ├── compliments_remote.js │ │ │ ├── compliments_file.js │ │ │ ├── compliments_evening.js │ │ │ ├── compliments_only_anytime.js │ │ │ ├── compliments_e2e_cron_entry.js │ │ │ ├── compliments_cron_entry.js │ │ │ ├── compliments_anytime.js │ │ │ ├── compliments_date.js │ │ │ ├── compliments_animateCSS.js │ │ │ ├── compliments_animateCSS_fallbackToDefault.js │ │ │ ├── compliments_animateCSS_invertedAnimationName.js │ │ │ ├── compliments_file_change.js │ │ │ ├── compliments_specialDayUnique_false.js │ │ │ ├── compliments_specialDayUnique_true.js │ │ │ └── compliments_parts_day.js │ │ ├── newsfeed │ │ │ ├── incorrect_url.js │ │ │ ├── default.js │ │ │ ├── ignore_items.js │ │ │ └── prohibited_words.js │ │ ├── calendar │ │ │ ├── default.js │ │ │ ├── bad_rrule.js │ │ │ ├── recurring.js │ │ │ ├── fullday_until.js │ │ │ ├── old-basic-auth.js │ │ │ ├── auth-default.js │ │ │ ├── changed-port.js │ │ │ ├── symboltest.js │ │ │ ├── rrule_until.js │ │ │ ├── sliceMultiDayEvents.js │ │ │ ├── basic-auth.js │ │ │ ├── fail-basic-auth.js │ │ │ ├── single-fullday-event.js │ │ │ ├── long-fullday-event.js │ │ │ ├── berlin_multi.js │ │ │ ├── germany_at_end_of_day_repeating.js │ │ │ ├── chicago-looking-at-ny-recurring.js │ │ │ ├── end_of_day_berlin_moved.js │ │ │ ├── berlin_end_of_day_repeating.js │ │ │ ├── fullday_event_over_multiple_days_nonrepeating.js │ │ │ ├── exdate_and_recurrence_together.js │ │ │ ├── diff_tz_start_end.js │ │ │ ├── berlin_whole_day_event_moved_over_dst_change.js │ │ │ ├── chicago_late_in_timezone.js │ │ │ ├── exdate_la_at_midnight_dst.js │ │ │ ├── exdate_la_at_midnight_std.js │ │ │ ├── exdate_la_before_midnight.js │ │ │ ├── exdate_syd_at_midnight_dst.js │ │ │ ├── exdate_syd_at_midnight_std.js │ │ │ ├── exdate_syd_before_midnight.js │ │ │ ├── event_with_time_over_multiple_days_non_repeating_display_end.js │ │ │ ├── show-duplicates-in-calendar.js │ │ │ ├── custom.js │ │ │ ├── 3_move_first_allday_repeating_event.js │ │ │ ├── event_with_time_over_multiple_days_non_repeating_no_display_end.js │ │ │ └── countCalendarEvents.js │ │ ├── display.js │ │ ├── weather │ │ │ ├── hourlyweather_default.js │ │ │ ├── currentweather_default.js │ │ │ ├── forecastweather_default.js │ │ │ ├── currentweather_units.js │ │ │ ├── hourlyweather_options.js │ │ │ ├── forecastweather_absolute.js │ │ │ ├── forecastweather_units.js │ │ │ ├── hourlyweather_showPrecipitation.js │ │ │ ├── forecastweather_options.js │ │ │ ├── currentweather_options.js │ │ │ └── currentweather_compliments.js │ │ └── positions.js │ ├── default.js │ └── customregions.js ├── mocks │ ├── compliments_file.json │ ├── compliments_test.json │ ├── diff_tz_start_end.ics │ ├── event_with_time_over_multiple_days_non_repeating.ics │ ├── chicago-nyedge.ics │ ├── germany_at_end_of_day_repeating.ics │ ├── fullday_event_over_multiple_days_nonrepeating.ics │ ├── exdate_syd_at_midnight_dst.ics │ ├── exdate_syd_at_midnight_std.ics │ ├── exdate_syd_before_midnight.ics │ ├── exdate_la_at_midnight_dst.ics │ ├── exdate_la_at_midnight_std.ics │ ├── exdate_la_before_midnight.ics │ ├── chicago_late_in_timezone.ics │ ├── rrule_until.ics │ ├── whole_day_moved_over_dst_change_berlin.ics │ ├── RepeatingEvent.Oct21.ics │ ├── calendar_test_full_day_events.ics │ ├── weather_current.json │ ├── calendar_test_recurring.ics │ ├── calendar_test_multi_day_starting_today.ics │ ├── translation_test.json │ ├── fullday_until.ics │ ├── bad_rrule.ics │ ├── 3_move_first_allday_repeating_event.ics │ ├── end_of_day_berlin_moved.ics │ ├── sliceMultiDayEvents.ics │ ├── testNotification │ │ └── testNotification.js │ └── exdate_and_recurrence_together.ics ├── unit │ ├── helpers │ │ └── global-setup.js │ ├── classes │ │ ├── utils_spec.js │ │ └── deprecated_spec.js │ ├── global_vars │ │ ├── defaults_modules_spec.js │ │ └── root_path_spec.js │ ├── functions │ │ └── cmp_versions_spec.js │ └── modules │ │ └── default │ │ ├── calendar │ │ └── calendar_fetcher_utils_bad_rrule.js │ │ └── weather │ │ └── weather_object_spec.js ├── e2e │ ├── template_spec.js │ ├── vendor_spec.js │ ├── helpers │ │ ├── basic-auth.js │ │ └── weather-functions.js │ ├── modules_empty_spec.js │ ├── modules_position_spec.js │ ├── env_spec.js │ ├── ipWhitelist_spec.js │ ├── port_spec.js │ ├── modules_display_spec.js │ ├── fonts_spec.js │ ├── custom_module_regions_spec.js │ ├── modules │ │ ├── clock_de_spec.js │ │ └── helloworld_spec.js │ └── serveronly_spec.js ├── electron │ ├── helpers │ │ └── weather-setup.js │ ├── env_spec.js │ └── modules │ │ └── weather_spec.js └── utils │ ├── test_sequencer.js │ └── weather_mocker.js ├── .npmrc ├── mm2.png ├── .github ├── FUNDING.yaml ├── header.png ├── dependabot.yaml ├── workflows │ ├── dep-review.yaml │ ├── stale.yaml │ ├── spellcheck.yaml │ ├── electron-rebuild.yaml │ └── enforce-pullrequest-rules.yaml ├── ISSUE_TEMPLATE │ ├── config.yml │ └── change_request.yml ├── PULL_REQUEST_TEMPLATE.md └── CONTRIBUTING.md ├── .husky └── pre-commit ├── js ├── deprecated.js ├── module_functions.js ├── vendor.js └── socketclient.js ├── modules └── default │ ├── alert │ ├── styles │ │ ├── left.css │ │ ├── right.css │ │ └── center.css │ ├── translations │ │ ├── eo.json │ │ ├── hu.json │ │ ├── nl.json │ │ ├── en.json │ │ ├── bg.json │ │ ├── de.json │ │ ├── el.json │ │ ├── ru.json │ │ ├── th.json │ │ ├── da.json │ │ ├── es.json │ │ └── fr.json │ ├── README.md │ └── templates │ │ ├── notification.njk │ │ └── alert.njk │ ├── newsfeed │ ├── fullarticle.njk │ ├── oldconfig.njk │ ├── README.md │ └── newsfeed.css │ ├── updatenotification │ ├── updatenotification.css │ ├── README.md │ └── updatenotification.njk │ ├── helloworld │ ├── helloworld.njk │ ├── helloworld.js │ └── README.md │ ├── weather │ ├── providers │ │ └── README.md │ ├── README.md │ └── weather.css │ ├── calendar │ ├── calendar.css │ ├── README.md │ └── debug.js │ ├── compliments │ └── README.md │ ├── clock │ ├── README.md │ └── faces │ │ └── face-002.svg │ └── defaultmodules.js ├── .prettierignore ├── .markdownlint.json ├── css ├── font-awesome.css └── custom.css.sample ├── stylelint.config.mjs ├── .editorconfig ├── jsconfig.json ├── prettier.config.mjs ├── serveronly └── index.js ├── translations ├── ps.json ├── zh-cn.json ├── zh-tw.json ├── ko.json ├── ja.json ├── tlh.json ├── cv.json ├── he.json ├── ar.json ├── tr.json ├── en.json ├── hi.json ├── id.json ├── is.json ├── lt.json ├── gu.json ├── cs.json ├── cy.json ├── fy.json ├── hu.json ├── pl.json ├── da.json ├── fi.json ├── ms-my.json ├── hr.json ├── pt-br.json ├── ru.json └── sk.json ├── LICENSE.md ├── jest.config.js ├── module-types.ts ├── .gitattributes └── .gitignore /tests/configs/port_variable.env: -------------------------------------------------------------------------------- 1 | MM_PORT=8090 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | audit=false 3 | loglevel="error" 4 | -------------------------------------------------------------------------------- /mm2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicMirrorOrg/MagicMirror/HEAD/mm2.png -------------------------------------------------------------------------------- /tests/mocks/compliments_file.json: -------------------------------------------------------------------------------- 1 | { 2 | "anytime": ["test in morning"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yaml: -------------------------------------------------------------------------------- 1 | github: MichMich 2 | custom: ["https://magicmirror.builders/#donate"] 3 | -------------------------------------------------------------------------------- /.github/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MagicMirrorOrg/MagicMirror/HEAD/.github/header.png -------------------------------------------------------------------------------- /tests/mocks/compliments_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "anytime": ["Remote compliment file works!"] 3 | } 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if command -v npx &> /dev/null; then 4 | npx lint-staged 5 | fi 6 | -------------------------------------------------------------------------------- /js/deprecated.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | configs: ["kioskmode"], 3 | clock: ["secondsColor"] 4 | }; 5 | -------------------------------------------------------------------------------- /modules/default/alert/styles/left.css: -------------------------------------------------------------------------------- 1 | .ns-box { 2 | margin-right: auto; 3 | text-align: left; 4 | } 5 | -------------------------------------------------------------------------------- /modules/default/alert/styles/right.css: -------------------------------------------------------------------------------- 1 | .ns-box { 2 | margin-left: auto; 3 | text-align: right; 4 | } 5 | -------------------------------------------------------------------------------- /tests/unit/helpers/global-setup.js: -------------------------------------------------------------------------------- 1 | module.exports = async () => { 2 | process.env.TZ = "UTC"; 3 | }; 4 | -------------------------------------------------------------------------------- /modules/default/newsfeed/fullarticle.njk: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.mjs 3 | .husky/pre-commit 4 | .prettierignore 5 | /config 6 | /coverage 7 | package-lock.json 8 | **.ics 9 | -------------------------------------------------------------------------------- /modules/default/alert/styles/center.css: -------------------------------------------------------------------------------- 1 | .ns-box { 2 | margin-left: auto; 3 | margin-right: auto; 4 | text-align: center; 5 | } 6 | -------------------------------------------------------------------------------- /modules/default/alert/translations/eo.json: -------------------------------------------------------------------------------- 1 | { 2 | "sysTitle": "MagicMirror²-sciigo", 3 | "welcome": "Bonvenon, lanĉo sukcesis!" 4 | } 5 | -------------------------------------------------------------------------------- /modules/default/updatenotification/updatenotification.css: -------------------------------------------------------------------------------- 1 | .module.updatenotification a.difflink { 2 | text-decoration: none; 3 | } 4 | -------------------------------------------------------------------------------- /modules/default/alert/translations/hu.json: -------------------------------------------------------------------------------- 1 | { 2 | "sysTitle": "MagicMirror² értesítés", 3 | "welcome": "Üdvözöljük, indulás sikeres!" 4 | } 5 | -------------------------------------------------------------------------------- /modules/default/alert/translations/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "sysTitle": "MagicMirror² Notificatie", 3 | "welcome": "Welkom, Succesvol gestart!" 4 | } 5 | -------------------------------------------------------------------------------- /modules/default/alert/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "sysTitle": "MagicMirror² Notification", 3 | "welcome": "Welcome, start was successful!" 4 | } 5 | -------------------------------------------------------------------------------- /modules/default/newsfeed/oldconfig.njk: -------------------------------------------------------------------------------- 1 |
{{ "MODULE_CONFIG_CHANGED" | translate({MODULE_NAME: "Newsfeed"}) | safe }}
2 | -------------------------------------------------------------------------------- /modules/default/alert/translations/bg.json: -------------------------------------------------------------------------------- 1 | { 2 | "sysTitle": "MagicMirror² нотификация", 3 | "welcome": "Добре дошли, стартирането беше успешно" 4 | } 5 | -------------------------------------------------------------------------------- /modules/default/alert/translations/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "sysTitle": "MagicMirror² Benachrichtigung", 3 | "welcome": "Willkommen, Start war erfolgreich!" 4 | } 5 | -------------------------------------------------------------------------------- /modules/default/alert/translations/el.json: -------------------------------------------------------------------------------- 1 | { 2 | "sysTitle": "MagicMirror² Ειδοποίηση", 3 | "welcome": "Καλώς ήρθατε, η εκκίνηση ήταν επιτυχής!" 4 | } 5 | -------------------------------------------------------------------------------- /modules/default/alert/translations/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "sysTitle": "MagicMirror² Уведомление", 3 | "welcome": "Добро пожаловать, старт был успешным!" 4 | } 5 | -------------------------------------------------------------------------------- /modules/default/alert/translations/th.json: -------------------------------------------------------------------------------- 1 | { 2 | "sysTitle": "การแจ้งเตือน MagicMirror²", 3 | "welcome": "ยินดีต้อนรับ การเริ่มต้นสำเร็จแล้ว!" 4 | } 5 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "line_length": false, 3 | "no-duplicate-heading": false, 4 | "no-inline-html": false, 5 | "no-trailing-punctuation": false 6 | } 7 | -------------------------------------------------------------------------------- /modules/default/alert/translations/da.json: -------------------------------------------------------------------------------- 1 | { 2 | "sysTitle": "MagicMirror² Notifikation", 3 | "welcome": "Velkommen, modulet er succesfuldt startet!" 4 | } 5 | -------------------------------------------------------------------------------- /modules/default/alert/translations/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "sysTitle": "MagicMirror² Notificaciones", 3 | "welcome": "Bienvenido, ¡se iniciado correctamente!" 4 | } 5 | -------------------------------------------------------------------------------- /modules/default/alert/translations/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "sysTitle": "MagicMirror² Notification", 3 | "welcome": "Bienvenue, le démarrage a été un succès!" 4 | } 5 | -------------------------------------------------------------------------------- /css/font-awesome.css: -------------------------------------------------------------------------------- 1 | @import url("../node_modules/@fortawesome/fontawesome-free/css/all.min.css"); 2 | @import url("../node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css"); 3 | -------------------------------------------------------------------------------- /stylelint.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | extends: ["stylelint-config-standard", "stylelint-prettier/recommended"], 3 | root: true, 4 | rules: {} 5 | }; 6 | 7 | export default config; 8 | -------------------------------------------------------------------------------- /modules/default/helloworld/helloworld.njk: -------------------------------------------------------------------------------- 1 | 5 |
{{ text | safe }}
6 | -------------------------------------------------------------------------------- /tests/configs/without_modules.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [] 4 | }; 5 | 6 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 7 | if (typeof module !== "undefined") { 8 | module.exports = config; 9 | } 10 | -------------------------------------------------------------------------------- /modules/default/weather/providers/README.md: -------------------------------------------------------------------------------- 1 | # Weather Module Weather Provider Development Documentation 2 | 3 | For how to develop your own weather provider, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/development/weather-provider.html). 4 | -------------------------------------------------------------------------------- /tests/unit/classes/utils_spec.js: -------------------------------------------------------------------------------- 1 | const Utils = require("../../../js/utils"); 2 | 3 | describe("Utils", () => { 4 | it("should output system information", async () => { 5 | await expect(Utils.logSystemInformation()).resolves.toContain("platform: linux"); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /tests/configs/port_8090.js: -------------------------------------------------------------------------------- 1 | let config = require(`${process.cwd()}/tests/configs/default.js`).configFactory({ 2 | port: 8090 3 | }); 4 | 5 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 6 | if (typeof module !== "undefined") { 7 | module.exports = config; 8 | } 9 | -------------------------------------------------------------------------------- /tests/configs/port_variable.js.template: -------------------------------------------------------------------------------- 1 | let config = require(`${process.cwd()}/tests/configs/default.js`).configFactory({ 2 | port: ${MM_PORT} 3 | }); 4 | 5 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 6 | if (typeof module !== "undefined") { 7 | module.exports = config; 8 | } 9 | -------------------------------------------------------------------------------- /modules/default/helloworld/helloworld.js: -------------------------------------------------------------------------------- 1 | Module.register("helloworld", { 2 | // Default module config. 3 | defaults: { 4 | text: "Hello World!" 5 | }, 6 | 7 | getTemplate () { 8 | return "helloworld.njk"; 9 | }, 10 | 11 | getTemplateData () { 12 | return this.config; 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /modules/default/alert/README.md: -------------------------------------------------------------------------------- 1 | # Module: Alert 2 | 3 | The alert module is one of the default modules of the MagicMirror². This module displays notifications from other modules. 4 | 5 | For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/alert.html). 6 | -------------------------------------------------------------------------------- /modules/default/calendar/calendar.css: -------------------------------------------------------------------------------- 1 | .calendar .symbol { 2 | display: flex; 3 | flex-direction: row; 4 | justify-content: flex-end; 5 | gap: 5px; 6 | } 7 | 8 | .calendar .title { 9 | padding: 0 10px; 10 | } 11 | 12 | .calendar .time { 13 | padding-left: 20px; 14 | text-align: right; 15 | } 16 | -------------------------------------------------------------------------------- /tests/configs/empty_ipWhiteList.js: -------------------------------------------------------------------------------- 1 | let config = require(`${process.cwd()}/tests/configs/default.js`).configFactory({ 2 | ipWhitelist: [], 3 | port: 8282 4 | }); 5 | 6 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 7 | if (typeof module !== "undefined") { 8 | module.exports = config; 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | max_line_length = 250 11 | trim_trailing_whitespace = true 12 | 13 | [*.{js,json}] 14 | indent_size = 4 15 | indent_style = tab 16 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=759670 3 | // for the documentation about the jsconfig.json format 4 | "compilerOptions": { 5 | "target": "es6", 6 | "module": "commonjs", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "exclude": ["modules", "node_modules"] 10 | } 11 | -------------------------------------------------------------------------------- /tests/configs/noIpWhiteList.js: -------------------------------------------------------------------------------- 1 | let config = require(`${process.cwd()}/tests/configs/default.js`).configFactory({ 2 | ipWhitelist: ["x.x.x.x"], 3 | port: 8181 4 | }); 5 | 6 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 7 | if (typeof module !== "undefined") { 8 | module.exports = config; 9 | } 10 | -------------------------------------------------------------------------------- /modules/default/compliments/README.md: -------------------------------------------------------------------------------- 1 | # Module: Compliments 2 | 3 | The `compliments` module is one of the default modules of the MagicMirror². 4 | This module displays a random compliment. 5 | 6 | For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/compliments.html). 7 | -------------------------------------------------------------------------------- /modules/default/helloworld/README.md: -------------------------------------------------------------------------------- 1 | # Module: Hello World 2 | 3 | The `helloworld` module is one of the default modules of the MagicMirror². It is a simple way to display a static text on the mirror. 4 | 5 | For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/helloworld.html). 6 | -------------------------------------------------------------------------------- /modules/default/alert/templates/notification.njk: -------------------------------------------------------------------------------- 1 | {% if title %} 2 | {{ title if titleType == 'text' else title | safe }} 3 | {% endif %} 4 | {% if message %} 5 | {% if title %}
{% endif %} 6 | {{ message if messageType == 'text' else message | safe }} 7 | {% endif %} 8 | -------------------------------------------------------------------------------- /modules/default/clock/README.md: -------------------------------------------------------------------------------- 1 | # Module: Clock 2 | 3 | The `clock` module is one of the default modules of the MagicMirror². 4 | This module displays the current date and time. The information will be updated realtime. 5 | 6 | For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/clock.html). 7 | -------------------------------------------------------------------------------- /modules/default/weather/README.md: -------------------------------------------------------------------------------- 1 | # Weather Module 2 | 3 | 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. 4 | 5 | For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/weather.html). 6 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/clock_24hr.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "clock", 7 | position: "middle_center" 8 | } 9 | ] 10 | }; 11 | 12 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 13 | if (typeof module !== "undefined") { 14 | module.exports = config; 15 | } 16 | -------------------------------------------------------------------------------- /modules/default/calendar/README.md: -------------------------------------------------------------------------------- 1 | # Module: Calendar 2 | 3 | The `calendar` module is one of the default modules of the MagicMirror². 4 | This module displays events from a public .ical calendar. It can combine multiple calendars. 5 | 6 | For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/calendar.html). 7 | -------------------------------------------------------------------------------- /tests/configs/modules/helloworld/helloworld_default.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "helloworld", 7 | position: "bottom_bar" 8 | } 9 | ] 10 | }; 11 | 12 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 13 | if (typeof module !== "undefined") { 14 | module.exports = config; 15 | } 16 | -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["prettier-plugin-jinja-template"], 3 | overrides: [ 4 | { 5 | files: "*.md", 6 | options: { 7 | parser: "markdown" 8 | } 9 | }, 10 | { 11 | files: ["*.njk"], 12 | options: { 13 | parser: "jinja-template" 14 | } 15 | } 16 | ], 17 | trailingComma: "none" 18 | }; 19 | 20 | export default config; 21 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/clock_12hr.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "clock", 9 | position: "middle_center" 10 | } 11 | ] 12 | }; 13 | 14 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 15 | if (typeof module !== "undefined") { 16 | module.exports = config; 17 | } 18 | -------------------------------------------------------------------------------- /tests/mocks/diff_tz_start_end.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | BEGIN:VEVENT 3 | DTSTART:20241029T100000Z 4 | DTEND:20241030T230000Z 5 | DTSTAMP:20241022T203806Z 6 | UID:04ivnntdi20rqsk0iesabsdhmj@google.com 7 | CREATED:20241022T203738Z 8 | LAST-MODIFIED:20241022T203738Z 9 | SEQUENCE:0 10 | STATUS:CONFIRMED 11 | SUMMARY:start/end on diff tz 12 | TRANSP:OPAQUE 13 | END:VEVENT 14 | END:VCALENDAR 15 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/es/clock_24hr.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | language: "es", 5 | 6 | modules: [ 7 | { 8 | module: "clock", 9 | position: "middle_center" 10 | } 11 | ] 12 | }; 13 | 14 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 15 | if (typeof module !== "undefined") { 16 | module.exports = config; 17 | } 18 | -------------------------------------------------------------------------------- /serveronly/index.js: -------------------------------------------------------------------------------- 1 | const app = require("../js/app"); 2 | const Log = require("../js/logger"); 3 | 4 | app.start().then((config) => { 5 | const bindAddress = config.address ? config.address : "localhost"; 6 | const httpType = config.useHttps ? "https" : "http"; 7 | Log.info(`\n>>> Ready to go! Please point your browser to: ${httpType}://${bindAddress}:${global.mmPort || config.port} <<<`); 8 | }); 9 | -------------------------------------------------------------------------------- /modules/default/updatenotification/README.md: -------------------------------------------------------------------------------- 1 | # Module: Update Notification 2 | 3 | The `updatenotification` module is one of the default modules of the MagicMirror². 4 | This will display a message whenever a new version of the MagicMirror² application is available. 5 | 6 | For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/updatenotification.html). 7 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/es/clock_12hr.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | language: "es", 5 | timeFormat: 12, 6 | 7 | modules: [ 8 | { 9 | module: "clock", 10 | position: "middle_center" 11 | } 12 | ] 13 | }; 14 | 15 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 16 | if (typeof module !== "undefined") { 17 | module.exports = config; 18 | } 19 | -------------------------------------------------------------------------------- /tests/configs/modules/alert/welcome_true.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "alert", 7 | config: { 8 | display_time: 1000000, 9 | welcome_message: true 10 | } 11 | } 12 | ] 13 | }; 14 | 15 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 16 | if (typeof module !== "undefined") { 17 | module.exports = config; 18 | } 19 | -------------------------------------------------------------------------------- /tests/mocks/event_with_time_over_multiple_days_non_repeating.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | BEGIN:VEVENT 3 | DTSTART:20241026T010000Z 4 | DTEND:20241026T110000Z 5 | DTSTAMP:20241024T153358Z 6 | UID:4maud6s79m41a99pj2g7j5km0a@google.com 7 | CREATED:20241024T153313Z 8 | LAST-MODIFIED:20241024T153330Z 9 | SEQUENCE:0 10 | STATUS:CONFIRMED 11 | SUMMARY:Sleep over at Bobs 12 | TRANSP:OPAQUE 13 | END:VEVENT 14 | END:VCALENDAR 15 | -------------------------------------------------------------------------------- /tests/configs/modules/alert/welcome_false.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "alert", 7 | config: { 8 | display_time: 1000000, 9 | welcome_message: false 10 | } 11 | } 12 | ] 13 | }; 14 | 15 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 16 | if (typeof module !== "undefined") { 17 | module.exports = config; 18 | } 19 | -------------------------------------------------------------------------------- /tests/configs/modules/helloworld/helloworld.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "helloworld", 7 | position: "bottom_bar", 8 | config: { 9 | text: "Test HelloWorld Module" 10 | } 11 | } 12 | ] 13 | }; 14 | 15 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 16 | if (typeof module !== "undefined") { 17 | module.exports = config; 18 | } 19 | -------------------------------------------------------------------------------- /tests/configs/modules/alert/welcome_string.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "alert", 7 | config: { 8 | display_time: 1000000, 9 | welcome_message: "Custom welcome message!" 10 | } 11 | } 12 | ] 13 | }; 14 | 15 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 16 | if (typeof module !== "undefined") { 17 | module.exports = config; 18 | } 19 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/clock_showWeek.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "clock", 9 | position: "middle_center", 10 | config: { 11 | showWeek: true 12 | } 13 | } 14 | ] 15 | }; 16 | 17 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 18 | if (typeof module !== "undefined") { 19 | module.exports = config; 20 | } 21 | -------------------------------------------------------------------------------- /tests/configs/default.js: -------------------------------------------------------------------------------- 1 | if (typeof exports === "object") { 2 | // running in nodejs (not in browser) 3 | exports.configFactory = (options) => { 4 | return Object.assign( 5 | { 6 | electronOptions: { 7 | webPreferences: { 8 | nodeIntegration: true, 9 | enableRemoteModule: true, 10 | contextIsolation: false 11 | } 12 | }, 13 | 14 | modules: [] 15 | }, 16 | options 17 | ); 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/clock_showTime.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "clock", 9 | position: "middle_center", 10 | config: { 11 | showTime: false 12 | } 13 | } 14 | ] 15 | }; 16 | 17 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 18 | if (typeof module !== "undefined") { 19 | module.exports = config; 20 | } 21 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/clock_showWeek_short.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "clock", 9 | position: "middle_center", 10 | config: { 11 | showWeek: "short" 12 | } 13 | } 14 | ] 15 | }; 16 | 17 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 18 | if (typeof module !== "undefined") { 19 | module.exports = config; 20 | } 21 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/clock_showPeriodUpper.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "clock", 9 | position: "middle_center", 10 | config: { 11 | showPeriodUpper: true 12 | } 13 | } 14 | ] 15 | }; 16 | 17 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 18 | if (typeof module !== "undefined") { 19 | module.exports = config; 20 | } 21 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/clock_displaySeconds_false.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "clock", 9 | position: "middle_center", 10 | config: { 11 | displaySeconds: false 12 | } 13 | } 14 | ] 15 | }; 16 | 17 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 18 | if (typeof module !== "undefined") { 19 | module.exports = config; 20 | } 21 | -------------------------------------------------------------------------------- /modules/default/defaultmodules.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Default Modules List 3 | * Modules listed below can be loaded without the 'default/' prefix. Omitting the default folder name. 4 | */ 5 | const defaultModules = ["alert", "calendar", "clock", "compliments", "helloworld", "newsfeed", "updatenotification", "weather"]; 6 | 7 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 8 | if (typeof module !== "undefined") { 9 | module.exports = defaultModules; 10 | } 11 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/clock_showSunNoEvent.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "clock", 9 | position: "middle_center", 10 | config: { 11 | showSunTimes: "disableNextEvent" 12 | } 13 | } 14 | ] 15 | }; 16 | 17 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 18 | if (typeof module !== "undefined") { 19 | module.exports = config; 20 | } 21 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/de/clock_showWeek.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | language: "de", 5 | timeFormat: 12, 6 | 7 | modules: [ 8 | { 9 | module: "clock", 10 | position: "middle_center", 11 | config: { 12 | showWeek: true 13 | } 14 | } 15 | ] 16 | }; 17 | 18 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 19 | if (typeof module !== "undefined") { 20 | module.exports = config; 21 | } 22 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/es/clock_showWeek.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | language: "es", 5 | timeFormat: 12, 6 | 7 | modules: [ 8 | { 9 | module: "clock", 10 | position: "middle_center", 11 | config: { 12 | showWeek: true 13 | } 14 | } 15 | ] 16 | }; 17 | 18 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 19 | if (typeof module !== "undefined") { 20 | module.exports = config; 21 | } 22 | -------------------------------------------------------------------------------- /tests/mocks/chicago-nyedge.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VEVENT 2 | DTSTART;TZID=America/New_York:20240918T183000 3 | DTEND;TZID=America/New_York:20240918T203000 4 | RRULE:FREQ=WEEKLY;BYDAY=WE 5 | EXDATE;TZID=America/New_York:20241127T183000 6 | EXDATE;TZID=America/New_York:20241225T183000 7 | DTSTAMP:20250122T045443Z 8 | UID:_@google.com 9 | CREATED:20240916T131843Z 10 | LAST-MODIFIED:20241222T235014Z 11 | SEQUENCE:0 12 | STATUS:CONFIRMED 13 | SUMMARY:Derby 14 | TRANSP:OPAQUE 15 | END:VEVENT -------------------------------------------------------------------------------- /tests/configs/modules/clock/de/clock_showWeek_short.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | language: "de", 5 | timeFormat: 12, 6 | 7 | modules: [ 8 | { 9 | module: "clock", 10 | position: "middle_center", 11 | config: { 12 | showWeek: "short" 13 | } 14 | } 15 | ] 16 | }; 17 | 18 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 19 | if (typeof module !== "undefined") { 20 | module.exports = config; 21 | } 22 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/es/clock_showWeek_short.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | language: "es", 5 | timeFormat: 12, 6 | 7 | modules: [ 8 | { 9 | module: "clock", 10 | position: "middle_center", 11 | config: { 12 | showWeek: "short" 13 | } 14 | } 15 | ] 16 | }; 17 | 18 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 19 | if (typeof module !== "undefined") { 20 | module.exports = config; 21 | } 22 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/clock_analog.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "clock", 7 | position: "middle_center", 8 | config: { 9 | displayType: "analog", 10 | analogFace: "face-006", 11 | showDate: false 12 | } 13 | } 14 | ] 15 | }; 16 | 17 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 18 | if (typeof module !== "undefined") { 19 | module.exports = config; 20 | } 21 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/clock_showSunMoon.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "clock", 9 | position: "middle_center", 10 | config: { 11 | showSunTimes: true, 12 | showMoonTimes: true 13 | } 14 | } 15 | ] 16 | }; 17 | 18 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 19 | if (typeof module !== "undefined") { 20 | module.exports = config; 21 | } 22 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/es/clock_showPeriodUpper.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | language: "es", 5 | timeFormat: 12, 6 | 7 | modules: [ 8 | { 9 | module: "clock", 10 | position: "middle_center", 11 | config: { 12 | showPeriodUpper: true 13 | } 14 | } 15 | ] 16 | }; 17 | 18 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 19 | if (typeof module !== "undefined") { 20 | module.exports = config; 21 | } 22 | -------------------------------------------------------------------------------- /tests/mocks/germany_at_end_of_day_repeating.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | BEGIN:VEVENT 3 | DTSTART;TZID=Europe/Berlin:20241022T230000 4 | DTEND;TZID=Europe/Berlin:20241023T000000 5 | RRULE:FREQ=DAILY;WKST=MO;COUNT=4 6 | DTSTAMP:20241009T153220Z 7 | UID:2m6mt1p89l2anl74915ur3hsgm@google.com 8 | CREATED:20241009T153058Z 9 | LAST-MODIFIED:20241009T153205Z 10 | SEQUENCE:0 11 | STATUS:CONFIRMED 12 | SUMMARY:TestCal_AllDayRepeatingEvent 13 | TRANSP:TRANSPARENT 14 | END:VEVENT 15 | END:VCALENDAR -------------------------------------------------------------------------------- /modules/default/newsfeed/README.md: -------------------------------------------------------------------------------- 1 | # Module: News Feed 2 | 3 | The `newsfeed` module is one of the default modules of the MagicMirror². 4 | This module displays news headlines based on an RSS feed. Scrolling through news headlines happens time-based (`updateInterval`), but can also be controlled by sending news feed specific notifications to the module. 5 | 6 | For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/newsfeed.html). 7 | -------------------------------------------------------------------------------- /tests/configs/modules/compliments/compliments_remote.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "compliments", 7 | position: "middle_center", 8 | config: { 9 | remoteFile: "http://localhost:8080/tests/mocks/compliments_test.json" 10 | } 11 | } 12 | ] 13 | }; 14 | 15 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 16 | if (typeof module !== "undefined") { 17 | module.exports = config; 18 | } 19 | -------------------------------------------------------------------------------- /modules/default/newsfeed/newsfeed.css: -------------------------------------------------------------------------------- 1 | iframe.newsfeed-fullarticle { 2 | width: 100vw; 3 | 4 | /* very large height value to allow scrolling */ 5 | height: 3000px; 6 | top: 0; 7 | left: 0; 8 | border: none; 9 | z-index: 1; 10 | } 11 | 12 | .region.bottom.bar.newsfeed-fullarticle { 13 | bottom: inherit; 14 | top: -90px; 15 | } 16 | 17 | .newsfeed-list { 18 | list-style: none; 19 | } 20 | 21 | .newsfeed-list li { 22 | text-align: justify; 23 | margin-bottom: 0.5em; 24 | } 25 | -------------------------------------------------------------------------------- /tests/configs/modules/compliments/compliments_file.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "compliments", 7 | position: "bottom_bar", 8 | config: { 9 | updateInterval: 3000, 10 | remoteFile: "http://localhost:8080/tests/mocks/compliments_test.json" 11 | } 12 | } 13 | ] 14 | }; 15 | 16 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 17 | if (typeof module !== "undefined") { module.exports = config; } 18 | -------------------------------------------------------------------------------- /tests/mocks/fullday_event_over_multiple_days_nonrepeating.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | BEGIN:VEVENT 3 | DTSTART;VALUE=DATE:20241025 4 | DTEND;VALUE=DATE:20241031 5 | DTSTAMP:20241023T141110Z 6 | UID:60nobfcu0ct96jgsh5nhcia24b@google.com 7 | CREATED:20241023T141019Z 8 | DESCRIPTION:test for all day end viewing 9 | LAST-MODIFIED:20241023T141019Z 10 | SEQUENCE:0 11 | STATUS:CONFIRMED 12 | SUMMARY:simple all day event over many days (not repeating) 13 | TRANSP:TRANSPARENT 14 | END:VEVENT 15 | END:VCALENDAR 16 | -------------------------------------------------------------------------------- /tests/configs/modules/clock/clock_showDateAnalog.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "clock", 9 | position: "middle_center", 10 | config: { 11 | showTime: true, 12 | showDate: true, 13 | displayType: "analog" 14 | } 15 | } 16 | ] 17 | }; 18 | 19 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 20 | if (typeof module !== "undefined") { 21 | module.exports = config; 22 | } 23 | -------------------------------------------------------------------------------- /tests/configs/modules/compliments/compliments_evening.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "compliments", 9 | position: "middle_center", 10 | config: { 11 | compliments: { 12 | evening: ["Evening here"] 13 | } 14 | } 15 | } 16 | ] 17 | }; 18 | 19 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 20 | if (typeof module !== "undefined") { 21 | module.exports = config; 22 | } 23 | -------------------------------------------------------------------------------- /tests/mocks/exdate_syd_at_midnight_dst.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VEVENT 2 | DTSTART;TZID=Australia/Sydney:20230920T110000 3 | DTEND;TZID=Australia/Sydney:20230920T111000 4 | RRULE:FREQ=WEEKLY;BYDAY=WE 5 | EXDATE;TZID=Australia/Sydney:20230927T110000 6 | EXDATE;TZID=Australia/Sydney:20231004T110000 7 | DTSTAMP:20231025T233434Z 8 | UID:sdflbkasuhdb5fkauglkb@google.com 9 | CREATED:20230306T193128Z 10 | LAST-MODIFIED:20231024T222515Z 11 | SEQUENCE:0 12 | STATUS:CONFIRMED 13 | SUMMARY:My Event 14 | TRANSP:OPAQUE 15 | END:VEVENT -------------------------------------------------------------------------------- /tests/mocks/exdate_syd_at_midnight_std.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VEVENT 2 | DTSTART;TZID=Australia/Sydney:20230920T100000 3 | DTEND;TZID=Australia/Sydney:20230920T110000 4 | RRULE:FREQ=WEEKLY;BYDAY=WE 5 | EXDATE;TZID=Australia/Sydney:20230927T100000 6 | EXDATE;TZID=Australia/Sydney:20231004T100000 7 | DTSTAMP:20231025T233434Z 8 | UID:sdflbkasuhdb5fkauglkb@google.com 9 | CREATED:20230306T193128Z 10 | LAST-MODIFIED:20231024T222515Z 11 | SEQUENCE:0 12 | STATUS:CONFIRMED 13 | SUMMARY:My Event 14 | TRANSP:OPAQUE 15 | END:VEVENT -------------------------------------------------------------------------------- /tests/mocks/exdate_syd_before_midnight.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VEVENT 2 | DTSTART;TZID=Australia/Sydney:20230920T090000 3 | DTEND;TZID=Australia/Sydney:20230920T100000 4 | RRULE:FREQ=WEEKLY;BYDAY=WE 5 | EXDATE;TZID=Australia/Sydney:20230927T090000 6 | EXDATE;TZID=Australia/Sydney:20231004T090000 7 | DTSTAMP:20231025T233434Z 8 | UID:sdflbkasuhdb5fkauglkb@google.com 9 | CREATED:20230306T193128Z 10 | LAST-MODIFIED:20231024T222515Z 11 | SEQUENCE:0 12 | STATUS:CONFIRMED 13 | SUMMARY:My Event 14 | TRANSP:OPAQUE 15 | END:VEVENT -------------------------------------------------------------------------------- /tests/configs/modules/compliments/compliments_only_anytime.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "compliments", 9 | position: "middle_center", 10 | config: { 11 | compliments: { 12 | anytime: ["Anytime here"] 13 | } 14 | } 15 | } 16 | ] 17 | }; 18 | 19 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 20 | if (typeof module !== "undefined") { 21 | module.exports = config; 22 | } 23 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | target-branch: "develop" 8 | labels: 9 | - "Skip Changelog" 10 | - "dependencies" 11 | 12 | - package-ecosystem: "npm" 13 | directory: "/" 14 | schedule: 15 | interval: "monthly" 16 | target-branch: "develop" 17 | labels: 18 | - "Skip Changelog" 19 | - "dependencies" 20 | - "javascript" 21 | -------------------------------------------------------------------------------- /tests/mocks/exdate_la_at_midnight_dst.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VEVENT 2 | DTSTART;TZID=America/Los_Angeles:20231025T170000 3 | DTEND;TZID=America/Los_Angeles:20231025T180000 4 | RRULE:FREQ=WEEKLY;BYDAY=WE 5 | EXDATE;TZID=America/Los_Angeles:20231101T170000 6 | EXDATE;TZID=America/Los_Angeles:20231108T170000 7 | DTSTAMP:20231025T233434Z 8 | UID:sdflbkasuhdb5fkauglkb@google.com 9 | CREATED:20230306T193128Z 10 | LAST-MODIFIED:20231024T222515Z 11 | SEQUENCE:0 12 | STATUS:CONFIRMED 13 | SUMMARY:My Event 14 | TRANSP:OPAQUE 15 | END:VEVENT -------------------------------------------------------------------------------- /tests/mocks/exdate_la_at_midnight_std.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VEVENT 2 | DTSTART;TZID=America/Los_Angeles:20231025T160000 3 | DTEND;TZID=America/Los_Angeles:20231025T170000 4 | RRULE:FREQ=WEEKLY;BYDAY=WE 5 | EXDATE;TZID=America/Los_Angeles:20231101T160000 6 | EXDATE;TZID=America/Los_Angeles:20231108T160000 7 | DTSTAMP:20231025T233434Z 8 | UID:sdflbkasuhdb5fkauglkb@google.com 9 | CREATED:20230306T193128Z 10 | LAST-MODIFIED:20231024T222515Z 11 | SEQUENCE:0 12 | STATUS:CONFIRMED 13 | SUMMARY:My Event 14 | TRANSP:OPAQUE 15 | END:VEVENT -------------------------------------------------------------------------------- /tests/mocks/exdate_la_before_midnight.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VEVENT 2 | DTSTART;TZID=America/Los_Angeles:20231025T150000 3 | DTEND;TZID=America/Los_Angeles:20231025T160000 4 | RRULE:FREQ=WEEKLY;BYDAY=WE 5 | EXDATE;TZID=America/Los_Angeles:20231101T150000 6 | EXDATE;TZID=America/Los_Angeles:20231108T150000 7 | DTSTAMP:20231025T233434Z 8 | UID:sdflbkasuhdb5fkauglkb@google.com 9 | CREATED:20230306T193128Z 10 | LAST-MODIFIED:20231024T222515Z 11 | SEQUENCE:0 12 | STATUS:CONFIRMED 13 | SUMMARY:My Event 14 | TRANSP:OPAQUE 15 | END:VEVENT -------------------------------------------------------------------------------- /tests/configs/modules/compliments/compliments_e2e_cron_entry.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "compliments", 7 | position: "middle_center", 8 | config: { 9 | specialDayUnique: true, 10 | compliments: { 11 | anytime: ["just a test"], 12 | "* * * * *": ["anytime cron"] 13 | } 14 | } 15 | } 16 | ] 17 | }; 18 | 19 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 20 | if (typeof module !== "undefined") { module.exports = config; } 21 | -------------------------------------------------------------------------------- /tests/configs/modules/newsfeed/incorrect_url.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "newsfeed", 9 | position: "bottom_bar", 10 | config: { 11 | feeds: [ 12 | { 13 | title: "Incorrect Url", 14 | url: "this is not a valid url" 15 | } 16 | ] 17 | } 18 | } 19 | ] 20 | }; 21 | 22 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 23 | if (typeof module !== "undefined") { 24 | module.exports = config; 25 | } 26 | -------------------------------------------------------------------------------- /tests/configs/modules/compliments/compliments_cron_entry.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "compliments", 7 | position: "middle_center", 8 | config: { 9 | specialDayUnique: true, 10 | compliments: { 11 | anytime: ["just a test"], 12 | "00-10 16-19 * * fri": ["just pub time"] 13 | } 14 | } 15 | } 16 | ] 17 | }; 18 | 19 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 20 | if (typeof module !== "undefined") { module.exports = config; } 21 | -------------------------------------------------------------------------------- /tests/mocks/chicago_late_in_timezone.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VEVENT 2 | CREATED:20240904T053053Z 3 | DTEND;TZID=America/Chicago:20240910T211500 4 | DTSTAMP:20240925T005517Z 5 | DTSTART;TZID=America/Chicago:20240910T201500 6 | LAST-MODIFIED:20240925T005515Z 7 | LOCATION:Dance Class 8 | RELATED-TO;RELTYPE=X-CALENDARSERVER-RECURRENCE-SET:2D48CA37-FCE5-4E16-871 9 | 9-1F47160BDBA3 10 | RRULE:FREQ=WEEKLY;UNTIL=20250601T011500Z 11 | SEQUENCE:3 12 | SUMMARY:Wife Barre Class 13 | UID:39669340-7AFD-4685-9BD6-6CE4B715486E 14 | X-APPLE-CREATOR-IDENTITY:com.apple.mobilecal 15 | END:VEVENT -------------------------------------------------------------------------------- /tests/configs/modules/compliments/compliments_anytime.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "compliments", 9 | position: "middle_center", 10 | config: { 11 | compliments: { 12 | morning: [], 13 | afternoon: [], 14 | evening: [], 15 | anytime: ["Anytime here"] 16 | } 17 | } 18 | } 19 | ] 20 | }; 21 | 22 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 23 | if (typeof module !== "undefined") { 24 | module.exports = config; 25 | } 26 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/default.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | calendars: [ 12 | { 13 | maximumNumberOfDays: 10000, 14 | url: "http://localhost:8080/tests/mocks/calendar_test.ics" 15 | } 16 | ] 17 | } 18 | } 19 | ] 20 | }; 21 | 22 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 23 | if (typeof module !== "undefined") { 24 | module.exports = config; 25 | } 26 | -------------------------------------------------------------------------------- /tests/configs/modules/compliments/compliments_date.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "compliments", 9 | position: "middle_center", 10 | config: { 11 | compliments: { 12 | morning: [], 13 | afternoon: [], 14 | evening: [], 15 | "....-01-01": ["Happy new year!"] 16 | } 17 | } 18 | } 19 | ] 20 | }; 21 | 22 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 23 | if (typeof module !== "undefined") { 24 | module.exports = config; 25 | } 26 | -------------------------------------------------------------------------------- /tests/configs/modules/newsfeed/default.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "newsfeed", 9 | position: "bottom_bar", 10 | config: { 11 | feeds: [ 12 | { 13 | title: "Rodrigo Ramirez Blog", 14 | url: "http://localhost:8080/tests/mocks/newsfeed_test.xml" 15 | } 16 | ] 17 | } 18 | } 19 | ] 20 | }; 21 | 22 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 23 | if (typeof module !== "undefined") { 24 | module.exports = config; 25 | } 26 | -------------------------------------------------------------------------------- /tests/unit/classes/deprecated_spec.js: -------------------------------------------------------------------------------- 1 | const deprecated = require("../../../js/deprecated"); 2 | 3 | describe("Deprecated", () => { 4 | it("should be an object", () => { 5 | expect(typeof deprecated).toBe("object"); 6 | }); 7 | 8 | it("should contain configs array with deprecated options as strings", () => { 9 | expect(Array.isArray(["deprecated.configs"])).toBe(true); 10 | for (let option of deprecated.configs) { 11 | expect(typeof option).toBe("string"); 12 | } 13 | expect(deprecated.configs).toEqual(expect.arrayContaining(["kioskmode"])); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /tests/configs/modules/display.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "helloworld", 7 | position: "top_bar", 8 | header: "test_header", 9 | config: { 10 | text: "Test Display Header" 11 | } 12 | }, 13 | { 14 | module: "helloworld", 15 | position: "bottom_bar", 16 | config: { 17 | text: "Test Hide Header" 18 | } 19 | } 20 | ] 21 | }; 22 | 23 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 24 | if (typeof module !== "undefined") { 25 | module.exports = config; 26 | } 27 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/bad_rrule.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | logLevel: ["INFO", "LOG", "WARN", "ERROR", "DEBUG"], 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | calendars: [ 12 | { 13 | url: "http://localhost:8080/tests/mocks/bad_rrule.ics" 14 | } 15 | ] 16 | } 17 | } 18 | ] 19 | }; 20 | 21 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 22 | if (typeof module !== "undefined") { 23 | module.exports = config; 24 | } 25 | -------------------------------------------------------------------------------- /tests/configs/modules/compliments/compliments_animateCSS.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "compliments", 7 | position: "lower_third", 8 | animateIn: "flipInX", 9 | animateOut: "flipOutX", 10 | config: { 11 | compliments: { 12 | anytime: ["AnimateCSS Testing..."] 13 | }, 14 | updateInterval: 2000, 15 | fadeSpeed: 1000 16 | } 17 | } 18 | ] 19 | }; 20 | 21 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 22 | if (typeof module !== "undefined") { 23 | module.exports = config; 24 | } 25 | -------------------------------------------------------------------------------- /tests/configs/modules/weather/hourlyweather_default.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "weather", 9 | position: "bottom_bar", 10 | config: { 11 | type: "hourly", 12 | location: "Berlin", 13 | weatherProvider: "openweathermap", 14 | weatherEndpoint: "/onecall", 15 | mockData: '"#####WEATHERDATA#####"' 16 | } 17 | } 18 | ] 19 | }; 20 | 21 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 22 | if (typeof module !== "undefined") { 23 | module.exports = config; 24 | } 25 | -------------------------------------------------------------------------------- /tests/configs/modules/compliments/compliments_animateCSS_fallbackToDefault.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "compliments", 7 | position: "lower_third", 8 | animateIn: "foo", 9 | animateOut: "bar", 10 | config: { 11 | compliments: { 12 | anytime: ["AnimateCSS Testing..."] 13 | }, 14 | updateInterval: 2000, 15 | fadeSpeed: 1000 16 | } 17 | } 18 | ] 19 | }; 20 | 21 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 22 | if (typeof module !== "undefined") { 23 | module.exports = config; 24 | } 25 | -------------------------------------------------------------------------------- /tests/configs/modules/newsfeed/ignore_items.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "newsfeed", 9 | position: "bottom_bar", 10 | config: { 11 | feeds: [ 12 | { 13 | title: "Rodrigo Ramirez Blog", 14 | url: "http://localhost:8080/tests/mocks/newsfeed_test.xml" 15 | } 16 | ], 17 | ignoreOldItems: true 18 | } 19 | } 20 | ] 21 | }; 22 | 23 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 24 | if (typeof module !== "undefined") { 25 | module.exports = config; 26 | } 27 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/recurring.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | calendars: [ 12 | { 13 | maximumEntries: 6, 14 | maximumNumberOfDays: 3650, 15 | url: "http://localhost:8080/tests/mocks/calendar_test_recurring.ics" 16 | } 17 | ] 18 | } 19 | } 20 | ] 21 | }; 22 | 23 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 24 | if (typeof module !== "undefined") { 25 | module.exports = config; 26 | } 27 | -------------------------------------------------------------------------------- /tests/configs/modules/weather/currentweather_default.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "weather", 9 | position: "bottom_bar", 10 | config: { 11 | location: "Munich", 12 | showHumidity: "feelslike", 13 | weatherProvider: "openweathermap", 14 | weatherEndpoint: "/weather", 15 | mockData: '"#####WEATHERDATA#####"' 16 | } 17 | } 18 | ] 19 | }; 20 | 21 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 22 | if (typeof module !== "undefined") { 23 | module.exports = config; 24 | } 25 | -------------------------------------------------------------------------------- /tests/configs/modules/weather/forecastweather_default.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "weather", 9 | position: "bottom_bar", 10 | config: { 11 | type: "forecast", 12 | location: "Munich", 13 | weatherProvider: "openweathermap", 14 | weatherEndpoint: "/forecast/daily", 15 | mockData: '"#####WEATHERDATA#####"' 16 | } 17 | } 18 | ] 19 | }; 20 | 21 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 22 | if (typeof module !== "undefined") { 23 | module.exports = config; 24 | } 25 | -------------------------------------------------------------------------------- /tests/unit/global_vars/defaults_modules_spec.js: -------------------------------------------------------------------------------- 1 | const fs = require("node:fs"); 2 | const path = require("node:path"); 3 | 4 | const root_path = path.join(__dirname, "../../.."); 5 | 6 | describe("Default modules set in modules/default/defaultmodules.js", () => { 7 | const expectedDefaultModules = require(`${root_path}/modules/default/defaultmodules`); 8 | 9 | for (const defaultModule of expectedDefaultModules) { 10 | it(`contains a folder for modules/default/${defaultModule}"`, () => { 11 | expect(fs.existsSync(path.join(root_path, "modules/default", defaultModule))).toBe(true); 12 | }); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /js/module_functions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Schedule the timer for the next update 3 | * @param {object} timer The timer of the module 4 | * @param {bigint} intervalMS interval in milliseconds 5 | * @param {Promise} callback function to call when the timer expires 6 | */ 7 | const scheduleTimer = function (timer, intervalMS, callback) { 8 | if (process.env.JEST_WORKER_ID === undefined) { 9 | // only set timer when not running in jest 10 | let tmr = timer; 11 | clearTimeout(tmr); 12 | tmr = setTimeout(function () { 13 | callback(); 14 | }, intervalMS); 15 | } 16 | }; 17 | 18 | module.exports = { scheduleTimer }; 19 | -------------------------------------------------------------------------------- /tests/configs/modules/compliments/compliments_animateCSS_invertedAnimationName.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "compliments", 7 | position: "lower_third", 8 | animateIn: "flipOutX", 9 | animateOut: "flipInX", 10 | config: { 11 | compliments: { 12 | anytime: ["AnimateCSS Testing..."] 13 | }, 14 | updateInterval: 2000, 15 | fadeSpeed: 1000 16 | } 17 | } 18 | ] 19 | }; 20 | 21 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 22 | if (typeof module !== "undefined") { 23 | module.exports = config; 24 | } 25 | -------------------------------------------------------------------------------- /tests/configs/modules/compliments/compliments_file_change.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "compliments", 7 | position: "bottom_bar", 8 | config: { 9 | updateInterval: 3000, 10 | remoteFileRefreshInterval: 1500, 11 | remoteFile: "http://localhost:8080/tests/mocks/compliments_test.json", 12 | remoteFile2: "http://localhost:8080/tests/mocks/compliments_file.json" 13 | } 14 | } 15 | ] 16 | }; 17 | 18 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 19 | if (typeof module !== "undefined") { module.exports = config; } 20 | -------------------------------------------------------------------------------- /.github/workflows/dep-review.yaml: -------------------------------------------------------------------------------- 1 | # This workflow scans your pull requests for dependency changes, and will raise an error if any vulnerabilities or invalid licenses are being introduced. 2 | # For more information see: https://github.com/actions/dependency-review-action 3 | 4 | name: "Review Dependencies" 5 | 6 | on: [pull_request] 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | dependency-review: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: "Checkout code" 16 | uses: actions/checkout@v5 17 | - name: "Dependency Review" 18 | uses: actions/dependency-review-action@v4 19 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/fullday_until.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | hideDuplicates: false, 12 | maximumEntries: 100, 13 | calendars: [ 14 | { 15 | maximumEntries: 100, 16 | url: "http://localhost:8080/tests/mocks/fullday_until.ics" 17 | } 18 | ] 19 | } 20 | } 21 | ] 22 | }; 23 | 24 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 25 | if (typeof module !== "undefined") { 26 | module.exports = config; 27 | } 28 | -------------------------------------------------------------------------------- /tests/configs/modules/weather/currentweather_units.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | units: "imperial", 5 | 6 | modules: [ 7 | { 8 | module: "weather", 9 | position: "bottom_bar", 10 | config: { 11 | location: "Munich", 12 | weatherProvider: "openweathermap", 13 | weatherEndpoint: "/weather", 14 | mockData: '"#####WEATHERDATA#####"', 15 | decimalSymbol: ",", 16 | showHumidity: "wind" 17 | } 18 | } 19 | ] 20 | }; 21 | 22 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 23 | if (typeof module !== "undefined") { 24 | module.exports = config; 25 | } 26 | -------------------------------------------------------------------------------- /tests/configs/modules/weather/hourlyweather_options.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "weather", 9 | position: "bottom_bar", 10 | config: { 11 | type: "hourly", 12 | location: "Berlin", 13 | weatherProvider: "openweathermap", 14 | weatherEndpoint: "/onecall", 15 | mockData: '"#####WEATHERDATA#####"', 16 | hourlyForecastIncrements: 2 17 | } 18 | } 19 | ] 20 | }; 21 | 22 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 23 | if (typeof module !== "undefined") { 24 | module.exports = config; 25 | } 26 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/old-basic-auth.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | calendars: [ 12 | { 13 | maximumNumberOfDays: 10000, 14 | url: "http://localhost:8080/tests/mocks/calendar_test.ics", 15 | user: "MagicMirror", 16 | pass: "CallMeADog" 17 | } 18 | ] 19 | } 20 | } 21 | ] 22 | }; 23 | 24 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 25 | if (typeof module !== "undefined") { 26 | module.exports = config; 27 | } 28 | -------------------------------------------------------------------------------- /tests/configs/modules/weather/forecastweather_absolute.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "weather", 9 | position: "bottom_bar", 10 | config: { 11 | type: "forecast", 12 | location: "Munich", 13 | weatherProvider: "openweathermap", 14 | weatherEndpoint: "/forecast/daily", 15 | mockData: '"#####WEATHERDATA#####"', 16 | absoluteDates: true 17 | } 18 | } 19 | ] 20 | }; 21 | 22 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 23 | if (typeof module !== "undefined") { 24 | module.exports = config; 25 | } 26 | -------------------------------------------------------------------------------- /tests/e2e/template_spec.js: -------------------------------------------------------------------------------- 1 | const fs = require("node:fs"); 2 | const helpers = require("./helpers/global-setup"); 3 | 4 | describe("templated config with port variable", () => { 5 | beforeAll(async () => { 6 | await helpers.startApplication("tests/configs/port_variable.js"); 7 | }); 8 | afterAll(async () => { 9 | await helpers.stopApplication(); 10 | try { 11 | fs.unlinkSync("tests/configs/port_variable.js"); 12 | } catch (err) { 13 | // do nothing 14 | } 15 | }); 16 | 17 | it("should return 200", async () => { 18 | const res = await fetch("http://localhost:8090"); 19 | expect(res.status).toBe(200); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /tests/configs/modules/compliments/compliments_specialDayUnique_false.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "compliments", 7 | position: "middle_center", 8 | config: { 9 | specialDayUnique: false, 10 | compliments: { 11 | anytime: [ 12 | "Typical message 1", 13 | "Typical message 2", 14 | "Typical message 3" 15 | ], 16 | "....-..-..": ["Special day message"] 17 | } 18 | } 19 | } 20 | ] 21 | }; 22 | 23 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 24 | if (typeof module !== "undefined") { module.exports = config; } 25 | -------------------------------------------------------------------------------- /tests/configs/modules/compliments/compliments_specialDayUnique_true.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "compliments", 7 | position: "middle_center", 8 | config: { 9 | specialDayUnique: true, 10 | compliments: { 11 | anytime: [ 12 | "Typical message 1", 13 | "Typical message 2", 14 | "Typical message 3" 15 | ], 16 | "....-..-..": ["Special day message"] 17 | } 18 | } 19 | } 20 | ] 21 | }; 22 | 23 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 24 | if (typeof module !== "undefined") { module.exports = config; } 25 | -------------------------------------------------------------------------------- /tests/configs/modules/newsfeed/prohibited_words.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "newsfeed", 9 | position: "bottom_bar", 10 | config: { 11 | feeds: [ 12 | { 13 | title: "Rodrigo Ramirez Blog", 14 | url: "http://localhost:8080/tests/mocks/newsfeed_test.xml" 15 | } 16 | ], 17 | prohibitedWords: ["QPanel"], 18 | showDescription: true 19 | } 20 | } 21 | ] 22 | }; 23 | 24 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 25 | if (typeof module !== "undefined") { 26 | module.exports = config; 27 | } 28 | -------------------------------------------------------------------------------- /tests/configs/modules/compliments/compliments_parts_day.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "compliments", 9 | position: "middle_center", 10 | config: { 11 | compliments: { 12 | morning: ["Hi", "Good Morning", "Morning test"], 13 | afternoon: ["Hello", "Good Afternoon", "Afternoon test"], 14 | evening: ["Hello There", "Good Evening", "Evening test"] 15 | } 16 | } 17 | } 18 | ] 19 | }; 20 | 21 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 22 | if (typeof module !== "undefined") { 23 | module.exports = config; 24 | } 25 | -------------------------------------------------------------------------------- /tests/electron/helpers/weather-setup.js: -------------------------------------------------------------------------------- 1 | const { injectMockData } = require("../../utils/weather_mocker"); 2 | const helpers = require("./global-setup"); 3 | 4 | exports.getText = async (element, result) => { 5 | const elem = await helpers.getElement(element); 6 | await expect(elem).not.toBeNull(); 7 | const text = await elem.textContent(); 8 | await expect( 9 | text 10 | .trim() 11 | .replace(/(\r\n|\n|\r)/gm, "") 12 | .replace(/[ ]+/g, " ") 13 | ).toBe(result); 14 | return true; 15 | }; 16 | 17 | exports.startApp = async (configFileName, systemDate) => { 18 | await helpers.startApplication(injectMockData(configFileName), systemDate); 19 | }; 20 | -------------------------------------------------------------------------------- /js/vendor.js: -------------------------------------------------------------------------------- 1 | const vendor = { 2 | "moment.js": "node_modules/moment/min/moment-with-locales.js", 3 | "moment-timezone.js": "node_modules/moment-timezone/builds/moment-timezone-with-data.js", 4 | "weather-icons.css": "node_modules/weathericons/css/weather-icons.css", 5 | "weather-icons-wind.css": "node_modules/weathericons/css/weather-icons-wind.css", 6 | "font-awesome.css": "css/font-awesome.css", 7 | "nunjucks.js": "node_modules/nunjucks/browser/nunjucks.min.js", 8 | "suncalc.js": "node_modules/suncalc/suncalc.js", 9 | "croner.js": "node_modules/croner/dist/croner.umd.js" 10 | }; 11 | 12 | if (typeof module !== "undefined") { 13 | module.exports = vendor; 14 | } 15 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/auth-default.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | calendars: [ 12 | { 13 | maximumNumberOfDays: 10000, 14 | url: "http://localhost:8080/tests/mocks/calendar_test.ics", 15 | auth: { 16 | user: "MagicMirror", 17 | pass: "CallMeADog" 18 | } 19 | } 20 | ] 21 | } 22 | } 23 | ] 24 | }; 25 | 26 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 27 | if (typeof module !== "undefined") { 28 | module.exports = config; 29 | } 30 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/changed-port.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | calendars: [ 12 | { 13 | maximumNumberOfDays: 10000, 14 | url: "http://localhost:8010/tests/mocks/calendar_test.ics", 15 | auth: { 16 | user: "MagicMirror", 17 | pass: "CallMeADog" 18 | } 19 | } 20 | ] 21 | } 22 | } 23 | ] 24 | }; 25 | 26 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 27 | if (typeof module !== "undefined") { 28 | module.exports = config; 29 | } 30 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/symboltest.js: -------------------------------------------------------------------------------- 1 | 2 | let config = { 3 | address: "0.0.0.0", 4 | ipWhitelist: [], 5 | timeFormat: 12, 6 | 7 | modules: [ 8 | { 9 | module: "calendar", 10 | position: "bottom_bar", 11 | config: { 12 | maximumEntries: 1, 13 | calendars: [ 14 | { 15 | fetchInterval: 7 * 24 * 60 * 60 * 1000, 16 | symbol: ["calendar-check", "google"], 17 | url: "https://ics.calendarlabs.com/76/mm3137/US_Holidays.ics" 18 | } 19 | ] 20 | } 21 | } 22 | ] 23 | }; 24 | 25 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 26 | if (typeof module !== "undefined") { 27 | module.exports = config; 28 | } 29 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/rrule_until.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | hideDuplicates: false, 12 | maximumEntries: 100, 13 | calendars: [ 14 | { 15 | maximumEntries: 100, 16 | maximumNumberOfDays: 1, // Just today 17 | url: "http://localhost:8080/tests/mocks/rrule_until.ics" 18 | } 19 | ] 20 | } 21 | } 22 | ] 23 | }; 24 | 25 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 26 | if (typeof module !== "undefined") { 27 | module.exports = config; 28 | } 29 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/sliceMultiDayEvents.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | hideDuplicates: false, 12 | maximumEntries: 100, 13 | sliceMultiDayEvents: true, 14 | calendars: [ 15 | { 16 | maximumEntries: 100, 17 | url: "http://localhost:8080/tests/mocks/sliceMultiDayEvents.ics" 18 | } 19 | ] 20 | } 21 | } 22 | ] 23 | }; 24 | 25 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 26 | if (typeof module !== "undefined") { 27 | module.exports = config; 28 | } 29 | -------------------------------------------------------------------------------- /tests/configs/modules/weather/forecastweather_units.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | units: "imperial", 5 | 6 | modules: [ 7 | { 8 | module: "weather", 9 | position: "bottom_bar", 10 | config: { 11 | type: "forecast", 12 | location: "Munich", 13 | weatherProvider: "openweathermap", 14 | weatherEndpoint: "/forecast/daily", 15 | mockData: '"#####WEATHERDATA#####"', 16 | decimalSymbol: "_", 17 | showPrecipitationAmount: true 18 | } 19 | } 20 | ] 21 | }; 22 | 23 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 24 | if (typeof module !== "undefined") { 25 | module.exports = config; 26 | } 27 | -------------------------------------------------------------------------------- /tests/configs/modules/weather/hourlyweather_showPrecipitation.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "weather", 9 | position: "bottom_bar", 10 | config: { 11 | type: "hourly", 12 | location: "Berlin", 13 | weatherProvider: "openweathermap", 14 | weatherEndpoint: "/onecall", 15 | mockData: '"#####WEATHERDATA#####"', 16 | showPrecipitationAmount: true, 17 | showPrecipitationProbability: true 18 | } 19 | } 20 | ] 21 | }; 22 | 23 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 24 | if (typeof module !== "undefined") { 25 | module.exports = config; 26 | } 27 | -------------------------------------------------------------------------------- /tests/configs/customregions.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: 5 | // Using exotic content. This is why don't accept go to JSON configuration file 6 | (() => { 7 | let positions = ["row3_left", "top3_left1"]; 8 | let modules = Array(); 9 | for (let idx in positions) { 10 | modules.push({ 11 | module: "helloworld", 12 | position: positions[idx], 13 | config: { 14 | text: `Text in ${positions[idx]}` 15 | } 16 | }); 17 | } 18 | return modules; 19 | })() 20 | }; 21 | 22 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 23 | if (typeof module !== "undefined") { 24 | module.exports = config; 25 | } 26 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/basic-auth.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | calendars: [ 12 | { 13 | maximumNumberOfDays: 10000, 14 | url: "http://localhost:8080/tests/mocks/calendar_test.ics", 15 | auth: { 16 | user: "MagicMirror", 17 | pass: "CallMeADog", 18 | method: "basic" 19 | } 20 | } 21 | ] 22 | } 23 | } 24 | ] 25 | }; 26 | 27 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 28 | if (typeof module !== "undefined") { 29 | module.exports = config; 30 | } 31 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/fail-basic-auth.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | calendars: [ 12 | { 13 | maximumNumberOfDays: 10000, 14 | url: "http://localhost:8020/tests/mocks/calendar_test.ics", 15 | auth: { 16 | user: "MagicMirror", 17 | pass: "StairwayToHeaven", 18 | method: "basic" 19 | } 20 | } 21 | ] 22 | } 23 | } 24 | ] 25 | }; 26 | 27 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 28 | if (typeof module !== "undefined") { 29 | module.exports = config; 30 | } 31 | -------------------------------------------------------------------------------- /tests/configs/modules/weather/forecastweather_options.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "weather", 9 | position: "bottom_bar", 10 | config: { 11 | type: "forecast", 12 | location: "Munich", 13 | weatherProvider: "openweathermap", 14 | weatherEndpoint: "/forecast/daily", 15 | mockData: '"#####WEATHERDATA#####"', 16 | showPrecipitationAmount: true, 17 | colored: true, 18 | tableClass: "myTableClass" 19 | } 20 | } 21 | ] 22 | }; 23 | 24 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 25 | if (typeof module !== "undefined") { 26 | module.exports = config; 27 | } 28 | -------------------------------------------------------------------------------- /tests/configs/modules/weather/currentweather_options.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "weather", 7 | position: "bottom_bar", 8 | config: { 9 | location: "Munich", 10 | weatherProvider: "openweathermap", 11 | weatherEndpoint: "/weather", 12 | mockData: '"#####WEATHERDATA#####"', 13 | windUnits: "beaufort", 14 | showWindDirectionAsArrow: true, 15 | showSun: false, 16 | showHumidity: "wind", 17 | roundTemp: true, 18 | degreeLabel: true 19 | } 20 | } 21 | ] 22 | }; 23 | 24 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 25 | if (typeof module !== "undefined") { 26 | module.exports = config; 27 | } 28 | -------------------------------------------------------------------------------- /tests/e2e/vendor_spec.js: -------------------------------------------------------------------------------- 1 | const helpers = require("./helpers/global-setup"); 2 | 3 | describe("Vendors", () => { 4 | beforeAll(async () => { 5 | await helpers.startApplication("tests/configs/default.js"); 6 | }); 7 | afterAll(async () => { 8 | await helpers.stopApplication(); 9 | }); 10 | 11 | describe("Get list vendors", () => { 12 | const vendors = require(`${global.root_path}/js/vendor.js`); 13 | 14 | Object.keys(vendors).forEach((vendor) => { 15 | it(`should return 200 HTTP code for vendor "${vendor}"`, async () => { 16 | const urlVendor = `http://localhost:8080/${vendors[vendor]}`; 17 | const res = await fetch(urlVendor); 18 | expect(res.status).toBe(200); 19 | }); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/configs/modules/weather/currentweather_compliments.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: [ 5 | { 6 | module: "compliments", 7 | position: "top_bar", 8 | config: { 9 | compliments: { 10 | snow: ["snow"] 11 | }, 12 | updateInterval: 3000 13 | } 14 | }, 15 | { 16 | module: "weather", 17 | position: "bottom_bar", 18 | config: { 19 | location: "Munich", 20 | weatherProvider: "openweathermap", 21 | weatherEndpoint: "/weather", 22 | mockData: '"#####WEATHERDATA#####"' 23 | } 24 | } 25 | ] 26 | }; 27 | 28 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 29 | if (typeof module !== "undefined") { 30 | module.exports = config; 31 | } 32 | -------------------------------------------------------------------------------- /tests/mocks/rrule_until.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VEVENT 2 | DTSTART;TZID=America/Los_Angeles:20240229T160000 3 | DTEND;TZID=America/Los_Angeles:20240229T190000 4 | RRULE:FREQ=WEEKLY;WKST=MO;UNTIL=20240307T075959Z;BYDAY=TH 5 | DTSTAMP:20240307T180618Z 6 | CREATED:20231231T000501Z 7 | LAST-MODIFIED:20231231T005623Z 8 | SEQUENCE:2 9 | STATUS:CONFIRMED 10 | SUMMARY:My event 11 | TRANSP:OPAQUE 12 | END:VEVENT 13 | BEGIN:VEVENT 14 | DTSTART;TZID=America/Los_Angeles:20240307T160000 15 | DTEND;TZID=America/Los_Angeles:20240307T190000 16 | RRULE:FREQ=WEEKLY;WKST=MO;UNTIL=20240316T065959Z;BYDAY=TH 17 | DTSTAMP:20240307T180618Z 18 | CREATED:20231231T000501Z 19 | LAST-MODIFIED:20231231T005623Z 20 | SEQUENCE:3 21 | STATUS:CONFIRMED 22 | SUMMARY:My event 23 | TRANSP:OPAQUE 24 | END:VEVENT -------------------------------------------------------------------------------- /tests/configs/modules/calendar/single-fullday-event.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MagicMirror² Test config for fullday calendar entries over multiple days 3 | * 4 | * By Paranoid93 https://github.com/Paranoid93/ 5 | * MIT Licensed. 6 | */ 7 | let config = { 8 | address: "0.0.0.0", 9 | ipWhitelist: [], 10 | timeFormat: 12, 11 | 12 | modules: [ 13 | { 14 | module: "calendar", 15 | position: "bottom_bar", 16 | config: { 17 | calendars: [ 18 | { 19 | maximumNumberOfDays: 2, 20 | url: "http://localhost:8080/tests/mocks/calendar_test_full_day_events.ics" 21 | } 22 | ] 23 | } 24 | } 25 | ] 26 | }; 27 | 28 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 29 | if (typeof module !== "undefined") { 30 | module.exports = config; 31 | } 32 | -------------------------------------------------------------------------------- /tests/e2e/helpers/basic-auth.js: -------------------------------------------------------------------------------- 1 | const path = require("node:path"); 2 | const auth = require("express-basic-auth"); 3 | const express = require("express"); 4 | 5 | const app = express(); 6 | 7 | const basicAuth = auth({ 8 | realm: "MagicMirror² Area restricted.", 9 | users: { MagicMirror: "CallMeADog" } 10 | }); 11 | 12 | app.use(basicAuth); 13 | 14 | // Set available directories 15 | const directories = ["/tests/configs", "/tests/mocks"]; 16 | 17 | for (let directory of directories) { 18 | app.use(directory, express.static(path.resolve(`${global.root_path}/${directory}`))); 19 | } 20 | 21 | let server; 22 | 23 | exports.listen = (...args) => { 24 | server = app.listen.apply(app, args); 25 | }; 26 | 27 | exports.close = async () => { 28 | await server.close(); 29 | }; 30 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/long-fullday-event.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MagicMirror² Test config for fullday calendar entries over multiple days 3 | * 4 | * By Paranoid93 https://github.com/Paranoid93/ 5 | * MIT Licensed. 6 | */ 7 | let config = { 8 | address: "0.0.0.0", 9 | ipWhitelist: [], 10 | timeFormat: 12, 11 | 12 | modules: [ 13 | { 14 | module: "calendar", 15 | position: "bottom_bar", 16 | config: { 17 | calendars: [ 18 | { 19 | maximumNumberOfDays: 2, 20 | url: "http://localhost:8080/tests/mocks/calendar_test_multi_day_starting_today.ics" 21 | } 22 | ] 23 | } 24 | } 25 | ] 26 | }; 27 | 28 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 29 | if (typeof module !== "undefined") { 30 | module.exports = config; 31 | } 32 | -------------------------------------------------------------------------------- /modules/default/alert/templates/alert.njk: -------------------------------------------------------------------------------- 1 | {% if imageUrl or imageFA %} 2 | {% set imageHeight = imageHeight if imageHeight else "80px" %} 3 | {% if imageUrl %} 4 | 5 | {% else %} 6 | 11 | {% endif %} 12 |
13 | {% endif %} 14 | {% if title %} 15 | {{ title if titleType == 'text' else title | safe }} 16 | {% endif %} 17 | {% if message %} 18 | {% if title %}
{% endif %} 19 | {{ message if messageType == 'text' else message | safe }} 20 | {% endif %} 21 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/berlin_multi.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | 5 | timeFormat: 24, 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | fade: false, 12 | urgency: 0, 13 | dateFormat: "Do.MMM, HH:mm", 14 | fullDayEventDateFormat: "Do.MMM", 15 | timeFormat: "absolute", 16 | getRelative: 0, 17 | maximumNumberOfDays: 28, 18 | showEnd: true, 19 | calendars: [ 20 | { 21 | maximumEntries: 100, 22 | url: "http://localhost:8080/tests/mocks/RepeatingEvent.Oct21.ics" 23 | } 24 | ] 25 | } 26 | } 27 | ] 28 | }; 29 | 30 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 31 | if (typeof module !== "undefined") { 32 | module.exports = config; 33 | } 34 | -------------------------------------------------------------------------------- /tests/e2e/helpers/weather-functions.js: -------------------------------------------------------------------------------- 1 | const { injectMockData, cleanupMockData } = require("../../utils/weather_mocker"); 2 | const helpers = require("./global-setup"); 3 | 4 | exports.getText = async (element, result) => { 5 | const elem = await helpers.waitForElement(element); 6 | expect(elem).not.toBeNull(); 7 | expect( 8 | elem.textContent 9 | .trim() 10 | .replace(/(\r\n|\n|\r)/gm, "") 11 | .replace(/[ ]+/g, " ") 12 | ).toBe(result); 13 | return true; 14 | }; 15 | 16 | exports.startApplication = async (configFileName, additionalMockData) => { 17 | await helpers.startApplication(injectMockData(configFileName, additionalMockData)); 18 | await helpers.getDocument(); 19 | }; 20 | 21 | exports.stopApplication = async () => { 22 | await helpers.stopApplication(); 23 | cleanupMockData(); 24 | }; 25 | -------------------------------------------------------------------------------- /tests/mocks/whole_day_moved_over_dst_change_berlin.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | BEGIN:VEVENT 3 | DTSTART;VALUE=DATE:20241027 4 | DTEND;VALUE=DATE:20241028 5 | RRULE:FREQ=DAILY;WKST=SU;COUNT=3 6 | DTSTAMP:20241020T152634Z 7 | UID:14nv8jl8d6dvdbl477lod4fftf@google.com 8 | CREATED:20241020T152434Z 9 | LAST-MODIFIED:20241020T152536Z 10 | SEQUENCE:1 11 | STATUS:CONFIRMED 12 | SUMMARY:test whole day moved 13 | TRANSP:TRANSPARENT 14 | END:VEVENT 15 | BEGIN:VEVENT 16 | DTSTART;VALUE=DATE:20241030 17 | DTEND;VALUE=DATE:20241031 18 | DTSTAMP:20241020T152634Z 19 | UID:14nv8jl8d6dvdbl477lod4fftf@google.com 20 | RECURRENCE-ID;VALUE=DATE:20241028 21 | CREATED:20241020T152434Z 22 | LAST-MODIFIED:20241020T152536Z 23 | SEQUENCE:2 24 | STATUS:CONFIRMED 25 | SUMMARY:test whole day moved 26 | TRANSP:TRANSPARENT 27 | END:VEVENT 28 | END:VCALENDAR 29 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/germany_at_end_of_day_repeating.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | 5 | timeFormat: 12, 6 | 7 | modules: [ 8 | { 9 | module: "calendar", 10 | position: "bottom_bar", 11 | config: { 12 | hideDuplicates: false, 13 | maximumEntries: 100, 14 | sliceMultiDayEvents: true, 15 | dateFormat: "MMM Do, HH:mm", 16 | timeFormat: "absolute", 17 | getRelative: 0, 18 | urgency: 0, 19 | calendars: [ 20 | { 21 | maximumEntries: 100, 22 | url: "http://localhost:8080/tests/mocks/germany_at_end_of_day_repeating.ics" 23 | } 24 | ] 25 | } 26 | } 27 | ] 28 | }; 29 | 30 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 31 | if (typeof module !== "undefined") { 32 | module.exports = config; 33 | } 34 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/chicago-looking-at-ny-recurring.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | 5 | timeFormat: 24, 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | fade: false, 12 | urgency: 0, 13 | dateFormat: "Do.MMM, HH:mm", 14 | fullDayEventDateFormat: "Do.MMM", 15 | timeFormat: "absolute", 16 | getRelative: 0, 17 | maximumNumberOfDays: 28, 18 | showEnd: true, 19 | calendars: [ 20 | { 21 | maximumEntries: 100, 22 | url: "http://localhost:8080/tests/mocks/chicago-nyedge.ics" 23 | } 24 | ] 25 | } 26 | } 27 | ] 28 | }; 29 | 30 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 31 | if (typeof module !== "undefined") { 32 | module.exports = config; 33 | } 34 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/end_of_day_berlin_moved.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | 5 | timeFormat: 24, 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | fade: false, 12 | urgency: 0, 13 | dateFormat: "Do.MMM, HH:mm", 14 | fullDayEventDateFormat: "Do.MMM", 15 | timeFormat: "absolute", 16 | getRelative: 0, 17 | maximumNumberOfDays: 28, 18 | showEnd: true, 19 | calendars: [ 20 | { 21 | maximumEntries: 100, 22 | url: "http://localhost:8080/tests/mocks/end_of_day_berlin_moved.ics" 23 | } 24 | ] 25 | } 26 | } 27 | ] 28 | }; 29 | 30 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 31 | if (typeof module !== "undefined") { 32 | module.exports = config; 33 | } 34 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/berlin_end_of_day_repeating.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | 5 | timeFormat: 24, 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | fade: false, 12 | urgency: 0, 13 | dateFormat: "Do.MMM, HH:mm", 14 | fullDayEventDateFormat: "Do.MMM", 15 | timeFormat: "absolute", 16 | getRelative: 0, 17 | maximumNumberOfDays: 28, 18 | showEnd: true, 19 | calendars: [ 20 | { 21 | maximumEntries: 100, 22 | url: "http://localhost:8080/tests/mocks/end_of_day_berlin_moved.ics" 23 | } 24 | ] 25 | } 26 | } 27 | ] 28 | }; 29 | 30 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 31 | if (typeof module !== "undefined") { 32 | module.exports = config; 33 | } 34 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/fullday_event_over_multiple_days_nonrepeating.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | 5 | timeFormat: 24, 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | fade: false, 12 | urgency: 0, 13 | dateFormat: "Do.MMM, HH:mm", 14 | fullDayEventDateFormat: "Do.MMM", 15 | timeFormat: "absolute", 16 | getRelative: 0, 17 | showEnd: true, 18 | calendars: [ 19 | { 20 | maximumEntries: 100, 21 | url: "http://localhost:8080/tests/mocks/fullday_event_over_multiple_days_nonrepeating.ics" 22 | } 23 | ] 24 | } 25 | } 26 | ] 27 | }; 28 | 29 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 30 | if (typeof module !== "undefined") { 31 | module.exports = config; 32 | } 33 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/exdate_and_recurrence_together.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | 5 | timeFormat: 24, 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | fade: false, 12 | urgency: 0, 13 | dateFormat: "Do.MMM, HH:mm", 14 | fullDayEventDateFormat: "Do.MMM", 15 | timeFormat: "absolute", 16 | getRelative: 0, 17 | maximumNumberOfDays: 28, 18 | showEnd: true, 19 | calendars: [ 20 | { 21 | maximumEntries: 100, 22 | url: "http://localhost:8080/tests/mocks/exdate_and_recurrence_together.ics" 23 | } 24 | ] 25 | } 26 | } 27 | ] 28 | }; 29 | 30 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 31 | if (typeof module !== "undefined") { 32 | module.exports = config; 33 | } 34 | -------------------------------------------------------------------------------- /tests/mocks/RepeatingEvent.Oct21.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | BEGIN:VEVENT 3 | DTSTART;TZID=Europe/Berlin:20241028T000000 4 | DTEND;TZID=Europe/Berlin:20241028T010000 5 | RRULE:FREQ=DAILY;COUNT=3 6 | DTSTAMP:20241020T093758Z 7 | UID:053fdshnnibo92lu97rsoeqoti@google.com 8 | CREATED:20241020T093230Z 9 | LAST-MODIFIED:20241020T093230Z 10 | SEQUENCE:0 11 | STATUS:CONFIRMED 12 | SUMMARY:RepeatingEventWeekAfterToday 13 | TRANSP:OPAQUE 14 | END:VEVENT 15 | BEGIN:VEVENT 16 | DTSTART;TZID=Europe/Berlin:20241021T000000 17 | DTEND;TZID=Europe/Berlin:20241021T010000 18 | RRULE:FREQ=DAILY;COUNT=3 19 | DTSTAMP:20241020T093758Z 20 | UID:1a6kk47pp61k4td2h9rlf0lv69@google.com 21 | CREATED:20241020T093255Z 22 | LAST-MODIFIED:20241020T093437Z 23 | SEQUENCE:1 24 | STATUS:CONFIRMED 25 | SUMMARY:RepeatingEventDayAfterToday 26 | TRANSP:OPAQUE 27 | END:VEVENT 28 | END:VCALENDAR 29 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/diff_tz_start_end.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | 5 | timeFormat: 24, 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | fade: false, 12 | urgency: 0, 13 | dateFormat: "Do.MMM, HH:mm", 14 | dateEndFormat: "Do.MMM, HH:mm", 15 | fullDayEventDateFormat: "Do.MMM", 16 | timeFormat: "absolute", 17 | getRelative: 0, 18 | maximumNumberOfDays: 28, 19 | showEnd: true, 20 | calendars: [ 21 | { 22 | maximumEntries: 100, 23 | url: "http://localhost:8080/tests/mocks/diff_tz_start_end.ics" 24 | } 25 | ] 26 | } 27 | } 28 | ] 29 | }; 30 | 31 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 32 | if (typeof module !== "undefined") { 33 | module.exports = config; 34 | } 35 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/berlin_whole_day_event_moved_over_dst_change.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | 5 | timeFormat: 24, 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | fade: false, 12 | urgency: 0, 13 | dateFormat: "Do.MMM, HH:mm", 14 | fullDayEventDateFormat: "Do.MMM", 15 | timeFormat: "absolute", 16 | getRelative: 0, 17 | maximumNumberOfDays: 28, 18 | showEnd: true, 19 | calendars: [ 20 | { 21 | maximumEntries: 100, 22 | url: "http://localhost:8080/tests/mocks/whole_day_moved_over_dst_change_berlin.ics" 23 | } 24 | ] 25 | } 26 | } 27 | ] 28 | }; 29 | 30 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 31 | if (typeof module !== "undefined") { 32 | module.exports = config; 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/stale.yaml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues and PRs" 2 | 3 | on: 4 | workflow_dispatch: # needed for manually running this workflow 5 | schedule: 6 | - cron: "30 1 * * 6" # every Saturday at 1:30 7 | 8 | permissions: 9 | issues: write 10 | 11 | jobs: 12 | stale: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/stale@v10 16 | with: 17 | stale-issue-message: "This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions." 18 | days-before-issue-stale: 60 19 | days-before-issue-close: 7 20 | operations-per-run: 100 21 | stale-issue-label: "wontfix" 22 | exempt-issue-labels: "pinned,security,under investigation,pr welcome,ready (coming with next release)" 23 | -------------------------------------------------------------------------------- /tests/configs/modules/positions.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | modules: 5 | // Using exotic content. This is why don't accept go to JSON configuration file 6 | (() => { 7 | let 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"]; 8 | let modules = Array(); 9 | for (let idx in positions) { 10 | modules.push({ 11 | module: "helloworld", 12 | position: positions[idx], 13 | config: { 14 | text: `Text in ${positions[idx]}` 15 | } 16 | }); 17 | } 18 | return modules; 19 | })() 20 | }; 21 | 22 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 23 | if (typeof module !== "undefined") { 24 | module.exports = config; 25 | } 26 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/chicago_late_in_timezone.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | 5 | timeFormat: 24, 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | fade: false, 12 | urgency: 0, 13 | dateFormat: "Do.MMM, HH:mm", 14 | fullDayEventDateFormat: "Do.MMM", 15 | timeFormat: "absolute", 16 | getRelative: 0, 17 | maximumNumberOfDays: 20, 18 | calendars: [ 19 | { 20 | maximumEntries: 100, 21 | //url: "http://localhost:8080/tests/mocks/chicago_late_in_timezone.ics" 22 | url: "http://localhost:8080/tests/mocks/chicago_late_in_timezone.ics" 23 | } 24 | ] 25 | } 26 | } 27 | ] 28 | }; 29 | 30 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 31 | if (typeof module !== "undefined") { 32 | module.exports = config; 33 | } 34 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/exdate_la_at_midnight_dst.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MagicMirror² Test calendar exdate 3 | * 4 | * By jkriegshauser 5 | * MIT Licensed. 6 | * 7 | * See issue #3250 8 | * See tests/electron/modules/calendar_spec.js 9 | */ 10 | let config = { 11 | address: "0.0.0.0", 12 | ipWhitelist: [], 13 | timeFormat: 12, 14 | 15 | modules: [ 16 | { 17 | module: "calendar", 18 | position: "bottom_bar", 19 | config: { 20 | maximumEntries: 100, 21 | calendars: [ 22 | { 23 | maximumEntries: 100, 24 | maximumNumberOfDays: 28, // 4 weeks, 2 of which are skipped 25 | url: "http://localhost:8080/tests/mocks/exdate_la_at_midnight_dst.ics" 26 | } 27 | ] 28 | } 29 | } 30 | ] 31 | }; 32 | 33 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 34 | if (typeof module !== "undefined") { 35 | module.exports = config; 36 | } 37 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/exdate_la_at_midnight_std.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MagicMirror² Test calendar exdate 3 | * 4 | * By jkriegshauser 5 | * MIT Licensed. 6 | * 7 | * See issue #3250 8 | * See tests/electron/modules/calendar_spec.js 9 | */ 10 | let config = { 11 | address: "0.0.0.0", 12 | ipWhitelist: [], 13 | timeFormat: 12, 14 | 15 | modules: [ 16 | { 17 | module: "calendar", 18 | position: "bottom_bar", 19 | config: { 20 | maximumEntries: 100, 21 | calendars: [ 22 | { 23 | maximumEntries: 100, 24 | maximumNumberOfDays: 28, // 4 weeks, 2 of which are skipped 25 | url: "http://localhost:8080/tests/mocks/exdate_la_at_midnight_std.ics" 26 | } 27 | ] 28 | } 29 | } 30 | ] 31 | }; 32 | 33 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 34 | if (typeof module !== "undefined") { 35 | module.exports = config; 36 | } 37 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/exdate_la_before_midnight.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MagicMirror² Test calendar exdate 3 | * 4 | * By jkriegshauser 5 | * MIT Licensed. 6 | * 7 | * See issue #3250 8 | * See tests/electron/modules/calendar_spec.js 9 | */ 10 | let config = { 11 | address: "0.0.0.0", 12 | ipWhitelist: [], 13 | timeFormat: 12, 14 | 15 | modules: [ 16 | { 17 | module: "calendar", 18 | position: "bottom_bar", 19 | config: { 20 | maximumEntries: 100, 21 | calendars: [ 22 | { 23 | maximumEntries: 100, 24 | maximumNumberOfDays: 28, // 4 weeks, 2 of which are skipped 25 | url: "http://localhost:8080/tests/mocks/exdate_la_before_midnight.ics" 26 | } 27 | ] 28 | } 29 | } 30 | ] 31 | }; 32 | 33 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 34 | if (typeof module !== "undefined") { 35 | module.exports = config; 36 | } 37 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/exdate_syd_at_midnight_dst.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MagicMirror² Test calendar exdate 3 | * 4 | * By jkriegshauser 5 | * MIT Licensed. 6 | * 7 | * See issue #3250 8 | * See tests/electron/modules/calendar_spec.js 9 | */ 10 | let config = { 11 | address: "0.0.0.0", 12 | ipWhitelist: [], 13 | timeFormat: 12, 14 | 15 | modules: [ 16 | { 17 | module: "calendar", 18 | position: "bottom_bar", 19 | config: { 20 | maximumEntries: 100, 21 | calendars: [ 22 | { 23 | maximumEntries: 100, 24 | maximumNumberOfDays: 28, // 4 weeks, 2 of which are skipped 25 | url: "http://localhost:8080/tests/mocks/exdate_syd_at_midnight_dst.ics" 26 | } 27 | ] 28 | } 29 | } 30 | ] 31 | }; 32 | 33 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 34 | if (typeof module !== "undefined") { 35 | module.exports = config; 36 | } 37 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/exdate_syd_at_midnight_std.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MagicMirror² Test calendar exdate 3 | * 4 | * By jkriegshauser 5 | * MIT Licensed. 6 | * 7 | * See issue #3250 8 | * See tests/electron/modules/calendar_spec.js 9 | */ 10 | let config = { 11 | address: "0.0.0.0", 12 | ipWhitelist: [], 13 | timeFormat: 12, 14 | 15 | modules: [ 16 | { 17 | module: "calendar", 18 | position: "bottom_bar", 19 | config: { 20 | maximumEntries: 100, 21 | calendars: [ 22 | { 23 | maximumEntries: 100, 24 | maximumNumberOfDays: 28, // 4 weeks, 2 of which are skipped 25 | url: "http://localhost:8080/tests/mocks/exdate_syd_at_midnight_std.ics" 26 | } 27 | ] 28 | } 29 | } 30 | ] 31 | }; 32 | 33 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 34 | if (typeof module !== "undefined") { 35 | module.exports = config; 36 | } 37 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/exdate_syd_before_midnight.js: -------------------------------------------------------------------------------- 1 | /* 2 | * MagicMirror² Test calendar exdate 3 | * 4 | * By jkriegshauser 5 | * MIT Licensed. 6 | * 7 | * See issue #3250 8 | * See tests/electron/modules/calendar_spec.js 9 | */ 10 | let config = { 11 | address: "0.0.0.0", 12 | ipWhitelist: [], 13 | timeFormat: 12, 14 | 15 | modules: [ 16 | { 17 | module: "calendar", 18 | position: "bottom_bar", 19 | config: { 20 | maximumEntries: 100, 21 | calendars: [ 22 | { 23 | maximumEntries: 100, 24 | maximumNumberOfDays: 28, // 4 weeks, 2 of which are skipped 25 | url: "http://localhost:8080/tests/mocks/exdate_syd_before_midnight.ics" 26 | } 27 | ] 28 | } 29 | } 30 | ] 31 | }; 32 | 33 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 34 | if (typeof module !== "undefined") { 35 | module.exports = config; 36 | } 37 | -------------------------------------------------------------------------------- /css/custom.css.sample: -------------------------------------------------------------------------------- 1 | /* Custom CSS Sample 2 | * 3 | * Change color and fonts here. 4 | * 5 | * Beware that properties cannot be unitless, so for example write '--gap-body: 0px;' instead of just '--gap-body: 0;' 6 | */ 7 | 8 | /* Uncomment and adjust accordingly if you want to import another font from the google-fonts-api: */ 9 | /* @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@100;300;400;700&display=swap'); */ 10 | 11 | :root { 12 | --color-text: #999; 13 | --color-text-dimmed: #666; 14 | --color-text-bright: #fff; 15 | --color-background: black; 16 | 17 | --font-primary: "Roboto Condensed"; 18 | --font-secondary: "Roboto"; 19 | 20 | --font-size: 20px; 21 | --font-size-small: 0.75rem; 22 | 23 | --gap-body-top: 60px; 24 | --gap-body-right: 60px; 25 | --gap-body-bottom: 60px; 26 | --gap-body-left: 60px; 27 | 28 | --gap-modules: 30px; 29 | } 30 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/event_with_time_over_multiple_days_non_repeating_display_end.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | 5 | timeFormat: 24, 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | fade: false, 12 | urgency: 0, 13 | dateFormat: "Do.MMM, HH:mm", 14 | dateEndFormat: "Do.MMM, HH:mm", 15 | fullDayEventDateFormat: "Do.MMM", 16 | timeFormat: "absolute", 17 | getRelative: 0, 18 | showEnd: true, 19 | calendars: [ 20 | { 21 | maximumEntries: 100, 22 | url: "http://localhost:8080/tests/mocks/event_with_time_over_multiple_days_non_repeating.ics" 23 | } 24 | ] 25 | } 26 | } 27 | ] 28 | }; 29 | 30 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 31 | if (typeof module !== "undefined") { 32 | module.exports = config; 33 | } 34 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/show-duplicates-in-calendar.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | maximumEntries: 30, 12 | hideDuplicates: false, 13 | calendars: [ 14 | { 15 | maximumEntries: 15, 16 | maximumNumberOfDays: 10000, 17 | url: "http://localhost:8080/tests/mocks/calendar_test.ics" // contains 11 events 18 | }, 19 | { 20 | maximumEntries: 15, 21 | maximumNumberOfDays: 10000, 22 | url: "http://localhost:8080/tests/mocks/calendar_test_clone.ics" // clone of upper calendar 23 | } 24 | ] 25 | } 26 | } 27 | ] 28 | }; 29 | 30 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 31 | if (typeof module !== "undefined") { 32 | module.exports = config; 33 | } 34 | -------------------------------------------------------------------------------- /tests/e2e/modules_empty_spec.js: -------------------------------------------------------------------------------- 1 | const helpers = require("./helpers/global-setup"); 2 | 3 | describe("Check configuration without modules", () => { 4 | beforeAll(async () => { 5 | await helpers.startApplication("tests/configs/without_modules.js"); 6 | await helpers.getDocument(); 7 | }); 8 | afterAll(async () => { 9 | await helpers.stopApplication(); 10 | }); 11 | 12 | it("shows the message MagicMirror² title", async () => { 13 | const elem = await helpers.waitForElement("#module_1_helloworld .module-content"); 14 | expect(elem).not.toBeNull(); 15 | expect(elem.textContent).toContain("MagicMirror²"); 16 | }); 17 | 18 | it("shows the project URL", async () => { 19 | const elem = await helpers.waitForElement("#module_5_helloworld .module-content"); 20 | expect(elem).not.toBeNull(); 21 | expect(elem.textContent).toContain("https://magicmirror.builders/"); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /.github/workflows/spellcheck.yaml: -------------------------------------------------------------------------------- 1 | # This workflow will run a spellcheck on the codebase. 2 | # It runs a few days before each release. At 00:00 on day-of-month 27 in March, June, September, and December. 3 | 4 | name: Run Spellcheck 5 | 6 | on: 7 | schedule: 8 | - cron: "0 0 27 3,6,9,12 *" 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | spellcheck: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v5 19 | with: 20 | ref: develop 21 | - name: Set up Node.js 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: lts/* 25 | check-latest: true 26 | cache: "npm" 27 | - name: Install dependencies 28 | run: | 29 | node --run install-mm:dev 30 | - name: Run Spellcheck 31 | run: node --run test:spelling 32 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/custom.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | customEvents: [{ keyword: "CustomEvent", symbol: "dice", eventClass: "undo" }], 12 | forceUseCurrentTime: true, 13 | calendars: [ 14 | { 15 | maximumEntries: 5, 16 | pastDaysCount: 5, 17 | broadcastPastEvents: true, 18 | maximumNumberOfDays: 10000, 19 | symbol: "birthday-cake", 20 | fullDaySymbol: "calendar-day", 21 | recurringSymbol: "undo", 22 | url: "http://localhost:8080/tests/mocks/calendar_test_icons.ics" 23 | } 24 | ] 25 | } 26 | } 27 | ] 28 | }; 29 | 30 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 31 | if (typeof module !== "undefined") { 32 | module.exports = config; 33 | } 34 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/3_move_first_allday_repeating_event.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | 5 | timeFormat: 24, 6 | units: "metric", 7 | modules: [ 8 | { 9 | module: "calendar", 10 | position: "bottom_bar", 11 | config: { 12 | fade: false, 13 | hideDuplicates: false, 14 | maximumEntries: 100, 15 | urgency: 0, 16 | dateFormat: "Do.MMM, HH:mm", 17 | fullDayEventDateFormat: "Do.MMM", 18 | timeFormat: "absolute", 19 | getRelative: 0, 20 | maximumNumberOfDays: 28, 21 | calendars: [ 22 | { 23 | maximumEntries: 100, 24 | url: "http://localhost:8080/tests/mocks/3_move_first_allday_repeating_event.ics" 25 | } 26 | ] 27 | } 28 | } 29 | ] 30 | }; 31 | 32 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 33 | if (typeof module !== "undefined") { 34 | module.exports = config; 35 | } 36 | -------------------------------------------------------------------------------- /tests/mocks/calendar_test_full_day_events.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//ical.marudot.com//iCal Event Maker 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:Europe/Berlin 7 | LAST-MODIFIED:20231222T233358Z 8 | TZURL:https://www.tzurl.org/zoneinfo-outlook/Europe/Berlin 9 | X-LIC-LOCATION:Europe/Berlin 10 | BEGIN:DAYLIGHT 11 | TZNAME:CEST 12 | TZOFFSETFROM:+0100 13 | TZOFFSETTO:+0200 14 | DTSTART:19700329T020000 15 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 16 | END:DAYLIGHT 17 | BEGIN:STANDARD 18 | TZNAME:CET 19 | TZOFFSETFROM:+0200 20 | TZOFFSETTO:+0100 21 | DTSTART:19701025T030000 22 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 23 | END:STANDARD 24 | END:VTIMEZONE 25 | BEGIN:VEVENT 26 | DTSTAMP:20240306T225415Z 27 | UID:1709765647426-75770@ical.marudot.com 28 | DTSTART;VALUE=DATE:20240306 29 | RRULE:FREQ=DAILY 30 | DTEND;VALUE=DATE:20240307 31 | SUMMARY:daily full days 32 | END:VEVENT 33 | END:VCALENDAR 34 | -------------------------------------------------------------------------------- /tests/mocks/weather_current.json: -------------------------------------------------------------------------------- 1 | { 2 | "coord": { 3 | "lon": 11.58, 4 | "lat": 48.14 5 | }, 6 | "weather": [ 7 | { 8 | "id": 615, 9 | "main": "Snow", 10 | "description": "light rain and snow", 11 | "icon": "13d" 12 | }, 13 | { 14 | "id": 500, 15 | "main": "Rain", 16 | "description": "light rain", 17 | "icon": "10d" 18 | } 19 | ], 20 | "base": "stations", 21 | "main": { 22 | "temp": 1.49, 23 | "pressure": 1005, 24 | "humidity": 93.7, 25 | "temp_min": 1, 26 | "temp_max": 2 27 | }, 28 | "visibility": 7000, 29 | "wind": { 30 | "speed": 11.8, 31 | "deg": 250 32 | }, 33 | "clouds": { 34 | "all": 75 35 | }, 36 | "dt": 1547387400, 37 | "sys": { 38 | "type": 1, 39 | "id": 1267, 40 | "message": 0.0031, 41 | "country": "DE", 42 | "sunrise": 1547362817, 43 | "sunset": 1547394301 44 | }, 45 | "id": 2867714, 46 | "name": "Munich", 47 | "cod": 200 48 | } 49 | -------------------------------------------------------------------------------- /tests/mocks/calendar_test_recurring.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | PRODID:-//Google Inc//Google Calendar 70.9054//EN 3 | VERSION:2.0 4 | CALSCALE:GREGORIAN 5 | METHOD:PUBLISH 6 | X-WR-CALNAME:xxx@gmail.com 7 | X-WR-TIMEZONE:Europe/Zurich 8 | BEGIN:VTIMEZONE 9 | TZID:Etc/UTC 10 | X-LIC-LOCATION:Etc/UTC 11 | BEGIN:STANDARD 12 | TZOFFSETFROM:+0000 13 | TZOFFSETTO:+0000 14 | TZNAME:GMT 15 | DTSTART:19700101T000000 16 | END:STANDARD 17 | END:VTIMEZONE 18 | BEGIN:VEVENT 19 | DTSTART;VALUE=DATE:20210325 20 | DTEND;VALUE=DATE:20210326 21 | RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1 22 | DTSTAMP:20210421T154106Z 23 | UID:zzz@google.com 24 | REATED:20200831T200244Z 25 | DESCRIPTION: 26 | LAST-MODIFIED:20200831T200244Z 27 | LOCATION: 28 | SEQUENCE:0 29 | STATUS:CONFIRMED 30 | SUMMARY:Birthday 31 | TRANSP:OPAQUE 32 | BEGIN:VALARM 33 | ACTION:DISPLAY 34 | DESCRIPTION:This is an event reminder 35 | TRIGGER:-P0DT7H0M0S 36 | END:VALARM 37 | END:VEVENT 38 | -------------------------------------------------------------------------------- /tests/mocks/calendar_test_multi_day_starting_today.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | VERSION:2.0 3 | PRODID:-//ical.marudot.com//iCal Event Maker 4 | CALSCALE:GREGORIAN 5 | BEGIN:VTIMEZONE 6 | TZID:Europe/Berlin 7 | LAST-MODIFIED:20231222T233358Z 8 | TZURL:https://www.tzurl.org/zoneinfo-outlook/Europe/Berlin 9 | X-LIC-LOCATION:Europe/Berlin 10 | BEGIN:DAYLIGHT 11 | TZNAME:CEST 12 | TZOFFSETFROM:+0100 13 | TZOFFSETTO:+0200 14 | DTSTART:19700329T020000 15 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU 16 | END:DAYLIGHT 17 | BEGIN:STANDARD 18 | TZNAME:CET 19 | TZOFFSETFROM:+0200 20 | TZOFFSETTO:+0100 21 | DTSTART:19701025T030000 22 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU 23 | END:STANDARD 24 | END:VTIMEZONE 25 | BEGIN:VEVENT 26 | DTSTAMP:20240306T222634Z 27 | UID:1709763965312-82782@ical.marudot.com 28 | DTSTART;VALUE=DATE:20240301 29 | RRULE:FREQ=DAILY 30 | DTEND;VALUE=DATE:20240303 31 | SUMMARY:2 day events 32 | END:VEVENT 33 | END:VCALENDAR 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 📚 Documentation 4 | url: https://github.com/MagicMirrorOrg/MagicMirror-Documentation/issues 5 | about: This issue tracker is not for documentation issues. Please file documentation issues on the docs repo. 6 | - name: 🤔 Support Question 7 | url: https://forum.magicmirror.builders/ 8 | about: Problems installing or configuring your MagicMirror? Please post your question on the MagicMirror² Forum. 9 | - name: 💬 Exchange of ideas 10 | url: https://discord.gg/AmGBBwPph5 11 | about: This issue tracker is not for general discussion. Please use the Discord channel. 12 | - name: 📦 Issues with a 3rd-party module 13 | url: https://kristjanesperanto.github.io/MagicMirror-3rd-Party-Modules/ 14 | about: This issue tracker is not for 3rd-party module issues. Please file 3rd-party module issues on the module's repo. 15 | -------------------------------------------------------------------------------- /tests/mocks/translation_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "LOADING": "Loading …", 3 | 4 | "TODAY": "Today", 5 | "TOMORROW": "Tomorrow", 6 | "DAYAFTERTOMORROW": "In 2 days", 7 | "RUNNING": "Ends in", 8 | "EMPTY": "No upcoming events.", 9 | 10 | "WEEK": "Week {weekNumber}", 11 | 12 | "N": "N", 13 | "NNE": "NNE", 14 | "NE": "NE", 15 | "ENE": "ENE", 16 | "E": "E", 17 | "ESE": "ESE", 18 | "SE": "SE", 19 | "SSE": "SSE", 20 | "S": "S", 21 | "SSW": "SSW", 22 | "SW": "SW", 23 | "WSW": "WSW", 24 | "W": "W", 25 | "WNW": "WNW", 26 | "NW": "NW", 27 | "NNW": "NNW", 28 | 29 | "UPDATE_NOTIFICATION": "MagicMirror² update available.", 30 | "UPDATE_NOTIFICATION_MODULE": "Update available for MODULE_NAME module.", 31 | "UPDATE_INFO_SINGLE": "The current installation is COMMIT_COUNT commit behind on the BRANCH_NAME branch.", 32 | "UPDATE_INFO_MULTIPLE": "The current installation is COMMIT_COUNT commits behind on the BRANCH_NAME branch." 33 | } 34 | -------------------------------------------------------------------------------- /tests/configs/modules/calendar/event_with_time_over_multiple_days_non_repeating_no_display_end.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | 5 | timeFormat: 24, 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | config: { 11 | fade: false, 12 | urgency: 0, 13 | dateFormat: "Do.MMM, HH:mm", 14 | dateEndFormat: "Do.MMM, HH:mm", 15 | fullDayEventDateFormat: "Do.MMM", 16 | timeFormat: "absolute", 17 | getRelative: 0, 18 | showEnd: true, 19 | showEndsOnlyWithDuration: true, 20 | calendars: [ 21 | { 22 | maximumEntries: 100, 23 | url: "http://localhost:8080/tests/mocks/event_with_time_over_multiple_days_non_repeating.ics" 24 | } 25 | ] 26 | } 27 | } 28 | ] 29 | }; 30 | 31 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 32 | if (typeof module !== "undefined") { 33 | module.exports = config; 34 | } 35 | -------------------------------------------------------------------------------- /tests/e2e/modules_position_spec.js: -------------------------------------------------------------------------------- 1 | const helpers = require("./helpers/global-setup"); 2 | 3 | describe("Position of modules", () => { 4 | beforeAll(async () => { 5 | await helpers.startApplication("tests/configs/modules/positions.js"); 6 | await helpers.getDocument(); 7 | }); 8 | afterAll(async () => { 9 | await helpers.stopApplication(); 10 | }); 11 | 12 | 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"]; 13 | 14 | for (const position of positions) { 15 | const className = position.replace("_", "."); 16 | it(`should show text in ${position}`, async () => { 17 | const elem = await helpers.waitForElement(`.${className}`); 18 | expect(elem).not.toBeNull(); 19 | expect(elem.textContent).toContain(`Text in ${position}`); 20 | }); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /tests/mocks/fullday_until.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | BEGIN:VEVENT 3 | DESCRIPTION:\n 4 | RRULE:FREQ=YEARLY;UNTIL=20250504T230000Z;INTERVAL=1;BYMONTHDAY=5;BYMONTH=5 5 | UID:040000008200E00074C5B7101A82E00800000000DAEF6ED30D9FDA01000000000000000 6 | 010000000D37F812F0777844A93E97B96AD2D278B 7 | SUMMARY:Person A's Birthday 8 | DTSTART;VALUE=DATE:20250505 9 | DTEND;VALUE=DATE:20250506 10 | CLASS:PUBLIC 11 | PRIORITY:5 12 | DTSTAMP:20250428T133000Z 13 | TRANSP:TRANSPARENT 14 | STATUS:CONFIRMED 15 | SEQUENCE:0 16 | LOCATION: 17 | X-MICROSOFT-CDO-APPT-SEQUENCE:0 18 | X-MICROSOFT-CDO-BUSYSTATUS:FREE 19 | X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY 20 | X-MICROSOFT-CDO-ALLDAYEVENT:TRUE 21 | X-MICROSOFT-CDO-IMPORTANCE:1 22 | X-MICROSOFT-CDO-INSTTYPE:1 23 | X-MICROSOFT-DONOTFORWARDMEETING:FALSE 24 | X-MICROSOFT-DISALLOW-COUNTER:FALSE 25 | X-MICROSOFT-REQUESTEDATTENDANCEMODE:DEFAULT 26 | X-MICROSOFT-ISRESPONSEREQUESTED:FALSE 27 | END:VEVENT 28 | END:VCALENDAR 29 | -------------------------------------------------------------------------------- /tests/mocks/bad_rrule.ics: -------------------------------------------------------------------------------- 1 | BEGIN:VCALENDAR 2 | BEGIN:VEVENT 3 | DTSTAMP:20210413T203456Z 4 | UID:E689AEB8C02C4E2CADD8C7D3D303CEAD0 5 | DTSTART;TZID="Amsterdam, Belgrade, Berlin, Brussels, Budapest, Madrid, Paris, Prague, Stockholm":20210415T190000 6 | DTEND;TZID="Amsterdam, Belgrade, Berlin, Brussels, Budapest, Madrid, Paris, Prague, Stockholm":20210415T210000 7 | CLASS:PUBLIC 8 | LOCATION:albert heijn 9 | SUMMARY:xxx xxxx 10 | SEQUENCE:10 11 | RRULE:FREQ=DAILY;UNTIL=20210418T170000Z 12 | EXDATE;TZID="Amsterdam, Belgrade, Berlin, Brussels, Budapest, Madrid, Paris, Prague, Stockholm":20210417T190000 13 | EXDATE;TZID="Amsterdam, Belgrade, Berlin, Brussels, Budapest, Madrid, Paris, Prague, Stockholm":20210416T190000 14 | EXDATE;TZID="Amsterdam, Belgrade, Berlin, Brussels, Budapest, Madrid, Paris, Prague, Stockholm":20210415T190000 15 | BEGIN:VALARM 16 | ACTION:DISPLAY 17 | TRIGGER;RELATED=START:-PT15M 18 | END:VALARM 19 | END:VEVENT 20 | END:VCALENDAR -------------------------------------------------------------------------------- /tests/configs/modules/calendar/countCalendarEvents.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | address: "0.0.0.0", 3 | ipWhitelist: [], 4 | timeFormat: 12, 5 | foreignModulesDir: "tests/mocks", 6 | modules: [ 7 | { 8 | module: "calendar", 9 | position: "bottom_bar", 10 | 11 | config: { 12 | maximumEntries: 1, 13 | calendars: [ 14 | { 15 | fetchInterval: 10000, //7 * 24 * 60 * 60 * 1000, 16 | symbol: ["calendar-check", "google"], 17 | url: "http://localhost:8080/tests/mocks/12_events.ics" 18 | } 19 | ] 20 | } 21 | }, 22 | { 23 | module: "testNotification", 24 | position: "bottom_bar", 25 | config: { 26 | debug: true, 27 | match: { 28 | matchtype: "count", 29 | notificationID: "CALENDAR_EVENTS" 30 | } 31 | } 32 | } 33 | ] 34 | }; 35 | 36 | /*************** DO NOT EDIT THE LINE BELOW ***************/ 37 | if (typeof module !== "undefined") { 38 | module.exports = config; 39 | } 40 | -------------------------------------------------------------------------------- /tests/e2e/env_spec.js: -------------------------------------------------------------------------------- 1 | const helpers = require("./helpers/global-setup"); 2 | 3 | describe("App environment", () => { 4 | beforeAll(async () => { 5 | await helpers.startApplication("tests/configs/default.js"); 6 | await helpers.getDocument(); 7 | }); 8 | afterAll(async () => { 9 | await helpers.stopApplication(); 10 | }); 11 | 12 | it("get request from http://localhost:8080 should return 200", async () => { 13 | const res = await fetch("http://localhost:8080"); 14 | expect(res.status).toBe(200); 15 | }); 16 | 17 | it("get request from http://localhost:8080/nothing should return 404", async () => { 18 | const res = await fetch("http://localhost:8080/nothing"); 19 | expect(res.status).toBe(404); 20 | }); 21 | 22 | it("should show the title MagicMirror²", async () => { 23 | const elem = await helpers.waitForElement("title"); 24 | expect(elem).not.toBeNull(); 25 | expect(elem.textContent).toBe("MagicMirror²"); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /modules/default/weather/weather.css: -------------------------------------------------------------------------------- 1 | .weather .weathericon, 2 | .weather .fa-home { 3 | font-size: 75%; 4 | } 5 | 6 | .weather .humidity-icon { 7 | padding-right: 4px; 8 | } 9 | 10 | .weather .humidity-padding { 11 | padding-bottom: 6px; 12 | } 13 | 14 | .weather .day { 15 | padding-left: 0; 16 | padding-right: 25px; 17 | } 18 | 19 | .weather .weather-icon { 20 | padding-right: 30px; 21 | text-align: center; 22 | } 23 | 24 | .weather .min-temp { 25 | padding-left: 20px; 26 | padding-right: 0; 27 | } 28 | 29 | .weather .precipitation-amount, 30 | .weather .precipitation-prob, 31 | .weather .humidity-hourly, 32 | .weather .uv-index { 33 | padding-left: 20px; 34 | padding-right: 0; 35 | } 36 | 37 | .weather tr.colored .min-temp { 38 | color: #bcddff; 39 | } 40 | 41 | .weather tr.colored .max-temp { 42 | color: #ff8e99; 43 | } 44 | 45 | .weather .type-temp { 46 | display: flex; 47 | align-items: baseline; 48 | gap: 10px; 49 | } 50 | -------------------------------------------------------------------------------- /tests/e2e/ipWhitelist_spec.js: -------------------------------------------------------------------------------- 1 | const helpers = require("./helpers/global-setup"); 2 | 3 | describe("ipWhitelist directive configuration", () => { 4 | describe("Set ipWhitelist without access", () => { 5 | beforeAll(async () => { 6 | await helpers.startApplication("tests/configs/noIpWhiteList.js"); 7 | }); 8 | afterAll(async () => { 9 | await helpers.stopApplication(); 10 | }); 11 | 12 | it("should return 403", async () => { 13 | const res = await fetch("http://localhost:8181"); 14 | expect(res.status).toBe(403); 15 | }); 16 | }); 17 | 18 | describe("Set ipWhitelist []", () => { 19 | beforeAll(async () => { 20 | await helpers.startApplication("tests/configs/empty_ipWhiteList.js"); 21 | }); 22 | afterAll(async () => { 23 | await helpers.stopApplication(); 24 | }); 25 | 26 | it("should return 200", async () => { 27 | const res = await fetch("http://localhost:8282"); 28 | expect(res.status).toBe(200); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /tests/e2e/port_spec.js: -------------------------------------------------------------------------------- 1 | const helpers = require("./helpers/global-setup"); 2 | 3 | describe("port directive configuration", () => { 4 | describe("Set port 8090", () => { 5 | beforeAll(async () => { 6 | await helpers.startApplication("tests/configs/port_8090.js"); 7 | }); 8 | afterAll(async () => { 9 | await helpers.stopApplication(); 10 | }); 11 | 12 | it("should return 200", async () => { 13 | const res = await fetch("http://localhost:8090"); 14 | expect(res.status).toBe(200); 15 | }); 16 | }); 17 | 18 | describe("Set port 8100 on environment variable MM_PORT", () => { 19 | beforeAll(async () => { 20 | await helpers.startApplication("tests/configs/port_8090.js", (process.env.MM_PORT = 8100)); 21 | }); 22 | afterAll(async () => { 23 | await helpers.stopApplication(); 24 | }); 25 | 26 | it("should return 200", async () => { 27 | const res = await fetch("http://localhost:8100"); 28 | expect(res.status).toBe(200); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /tests/e2e/modules_display_spec.js: -------------------------------------------------------------------------------- 1 | const helpers = require("./helpers/global-setup"); 2 | 3 | describe("Display of modules", () => { 4 | beforeAll(async () => { 5 | await helpers.startApplication("tests/configs/modules/display.js"); 6 | await helpers.getDocument(); 7 | }); 8 | afterAll(async () => { 9 | await helpers.stopApplication(); 10 | }); 11 | 12 | it("should show the test header", async () => { 13 | const elem = await helpers.waitForElement("#module_0_helloworld .module-header"); 14 | expect(elem).not.toBeNull(); 15 | // textContent returns lowercase here, the uppercase is realized by CSS, which therefore does not end up in textContent 16 | expect(elem.textContent).toBe("test_header"); 17 | }); 18 | 19 | it("should show no header if no header text is specified", async () => { 20 | const elem = await helpers.waitForElement("#module_1_helloworld .module-header"); 21 | expect(elem).not.toBeNull(); 22 | expect(elem.textContent).toBe("undefined"); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /translations/ps.json: -------------------------------------------------------------------------------- 1 | { 2 | "LOADING": "پیلېدل", 3 | 4 | "DAYBEFOREYESTERDAY": "پرون ورځ", 5 | "YESTERDAY": "پرون", 6 | "TODAY": "نن", 7 | "TOMORROW": "سبا", 8 | "DAYAFTERTOMORROW": "بل سبا", 9 | "RUNNING": "روان", 10 | "EMPTY": "تش", 11 | "WEEK": "{weekNumber}. اونۍ", 12 | 13 | "N": "N", 14 | "NNE": "NNO", 15 | "NE": "NO", 16 | "ENE": "ONO", 17 | "E": "O", 18 | "ESE": "OSO", 19 | "SE": "SO", 20 | "SSE": "SSO", 21 | "S": "S", 22 | "SSW": "SSW", 23 | "SW": "SW", 24 | "WSW": "WSW", 25 | "W": "W", 26 | "WNW": "WNW", 27 | "NW": "NW", 28 | "NNW": "NNW", 29 | 30 | "FEELS": "حس کېږی {DEGREE}", 31 | 32 | "MODULE_CONFIG_CHANGED": "د {MODULE_NAME} بڼی تغیر کړی دی. \n هیله دی چی اسناد و ګوری!", 33 | 34 | "UPDATE_NOTIFICATION": "د MagicMirror² نوې نسخه سته ", 35 | "UPDATE_NOTIFICATION_MODULE": "د {MODULE_NAME} نوی نسخه سته", 36 | "UPDATE_INFO_SINGLE": "اوسنی برخه {COMMIT_COUNT} د {BRANCH_NAME} څخه وروسته پاته ده", 37 | "UPDATE_INFO_MULTIPLE": "اوسنی برخه {COMMIT_COUNT} د {BRANCH_NAME} څخه وروسته پاته ده" 38 | } 39 | -------------------------------------------------------------------------------- /tests/unit/functions/cmp_versions_spec.js: -------------------------------------------------------------------------------- 1 | const path = require("node:path"); 2 | const { JSDOM } = require("jsdom"); 3 | 4 | describe("Test function cmpVersions in js/module.js", () => { 5 | let cmp; 6 | 7 | beforeAll(() => { 8 | return new Promise((done) => { 9 | const dom = new JSDOM( 10 | `\ 11 |