diff --git a/.gitignore b/.gitignore index ecb483e8..9b851b18 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,9 @@ jspm_modules .npm .node_repl_history +# Visual Studio Code ignoramuses. +.vscode/ + # Various Windows ignoramuses. Thumbs.db ehthumbs.db diff --git a/.travis.yml b/.travis.yml index cbf1dfa2..ec119b78 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: node_js node_js: + - "8" - "7" - "6" - "5.1" @@ -11,6 +12,9 @@ before_script: script: - grunt - npm run test:unit +- npm run test:e2e +after_script: + - npm list cache: directories: - node_modules diff --git a/CHANGELOG.md b/CHANGELOG.md index 714b7c4b..1543d067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,35 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [2.1.3] - Unreleased ### Changed +- Remove Roboto fonts files inside `fonts` and these are installed by npm install command. + ### Added -- Add ability for `currentweather` module to display indoor humidity via INDOOR_HUMIDITY notification +- Add `clientonly` script to start only the electron client for a remote server. +- Add symbol and color properties of event when `CALENDAR_EVENTS` notification is broadcasted from `default/calendar` module. +- Add `.vscode/` folder to `.gitignore` to keep custom Visual Studio Code config out of git. +- Add unit test the capitalizeFirstLetter function of newfeed module. +- Add new unit tests for function `shorten` in calendar module. +- Add new unit tests for function `getLocaleSpecification` in calendar module. +- Add unit test for js/class.js. +- Add unit tests for function `roundValue` in currentweather module. +- Add test e2e showWeek feature in spanish language. +- Add warning Log when is used old authentication method in the calendar module. +- Add test e2e for helloworld module with default config text ### Updated +- Changed 'default.js' - listen on all attached interfaces by default. +- Add execution of `npm list` after the test are ran in Travis CI. +- Change hooks for the vendors e2e tests. +- Add log when clientonly failed on starting. +- Add warning color when are using full ip whitelist. +- Set version of the `express-ipfilter` on 0.3.1. + ### Fixed +- Fixed issue with incorrect allignment of analog clock when displayed in the center column of the MM. +- Fixed ipWhitelist behaviour to make empty whitelist ([]) allow any and all hosts access to the MM. +- Fixed issue with calendar module where 'excludedEvents' count towards 'maximumEntries'. +- Fixed issue with calendar module where global configuration of maximumEntries was not overridden by calendar specific config (see module doc). +- Fixed issue where `this.file(filename)` returns a path with two hashes ## [2.1.2] - 2017-07-01 @@ -39,7 +63,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Updated - Added missing keys to Polish translation. - Added missing key to German translation. -- Added better translation with flexible word order to Finnish translation +- Added better translation with flexible word order to Finnish translation. ### Fixed - Fix instruction in README for using automatically installer script. @@ -47,8 +71,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Fix double message about port when server is starting - Corrected Swedish translations for TODAY/TOMORROW/DAYAFTERTOMORROW. - Removed unused import from js/electron.js -- Made calendar.js respect config.timeFormat irrespecive of locale setting -- Fixed alignment of analog clock when a large calendar is displayed in the same side bar +- Made calendar.js respect config.timeFormat irrespecive of locale setting. +- Fixed alignment of analog clock when a large calendar is displayed in the same side bar. ## [2.1.1] - 2017-04-01 @@ -56,18 +80,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Add `anytime` group for Compliments module. -- Compliments module can use remoteFile without default daytime arrays defined +- Compliments module can use remoteFile without default daytime arrays defined. - Installer: Use init config.js from config.js.sample. - Switched out `rrule` package for `rrule-alt` and fixes in `ical.js` in order to fix calendar issues. ([#565](https://github.com/MichMich/MagicMirror/issues/565)) - Make mouse events pass through the region fullscreen_above to modules below. - Scaled the splash screen down to make it a bit more subtle. - Replace HTML tables with markdown tables in README files. - Added `DAYAFTERTOMORROW`, `UPDATE_NOTIFICATION` and `UPDATE_NOTIFICATION_MODULE` to Finnish translations. -- Run `npm test` on Travis automatically +- Run `npm test` on Travis automatically. - Show the splash screen image even when is reboot or halted. - Added some missing translaton strings in the sv.json file. - Run task jsonlint to check translation files. -- Restructured Test Suite +- Restructured Test Suite. ### Added - Added Docker support (Pull Request [#673](https://github.com/MichMich/MagicMirror/pull/673)). diff --git a/README.md b/README.md index 8b0c66be..b7dea0fb 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,11 @@ bash -c "$(curl -sL https://raw.githubusercontent.com/MichMich/MagicMirror/maste ### Server Only In some cases, you want to start the application without an actual app window. In this case, you can start MagicMirror² in server only mode by manually running `node serveronly` or using Docker. This will start the server, after which you can open the application in your browser of choice. Detailed description below. +### Client Only +When you have a server running remotely and want to connect a standalone client to this instance, you can manually run `node clientonly --address 192.168.1.5 --port 8080`. (Specify the ip address and port number of the server) + +**Important:** Make sure that you whitelist the interface/ip in the server config where you want the client to connect to, otherwise it will not be allowed to connect to the server + #### Docker MagicMirror² in server only mode can be deployed using [Docker](https://docker.com). After a successful [Docker installation](https://docs.docker.com/engine/installation/) you just need to execute the following command in the shell: diff --git a/clientonly/index.js b/clientonly/index.js new file mode 100644 index 00000000..72495504 --- /dev/null +++ b/clientonly/index.js @@ -0,0 +1,104 @@ +/* jshint esversion: 6 */ + +"use strict"; + +// Use seperate scope to prevent global scope pollution +(function () { + var config = {}; + + // Helper function to get server address/hostname from either the commandline or env + function getServerAddress() { + // Helper function to get command line parameters + // Assumes that a cmdline parameter is defined with `--key [value]` + function getCommandLineParameter(key, defaultValue = undefined) { + var index = process.argv.indexOf(`--${key}`); + var value = index > -1 ? process.argv[index + 1] : undefined; + return value !== undefined ? String(value) : defaultValue; + } + + // Prefer command line arguments over environment variables + ["address", "port"].forEach((key) => { + config[key] = getCommandLineParameter(key, process.env[key.toUpperCase()]); + }) + } + + function getServerConfig(url) { + // Return new pending promise + return new Promise((resolve, reject) => { + // Select http or https module, depending on reqested url + const lib = url.startsWith("https") ? require("https") : require("http"); + const request = lib.get(url, (response) => { + var configData = ""; + + // Gather incomming data + response.on("data", function(chunk) { + configData += chunk; + }); + // Resolve promise at the end of the HTTP/HTTPS stream + response.on("end", function() { + resolve(JSON.parse(configData)); + }); + }); + + request.on("error", function(error) { + reject(new Error(`Unable to read config from server (${url} (${error.message}`)); + }); + }) + }; + + function fail(message, code = 1) { + if (message !== undefined && typeof message === "string") { + console.log(message); + } else { + console.log("Usage: 'node clientonly --address 192.168.1.10 --port 8080'"); + } + process.exit(code); + } + + getServerAddress(); + + (config.address && config.port) || fail(); + + // Only start the client if a non-local server was provided + if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) === -1) { + getServerConfig(`http://${config.address}:${config.port}/config/`) + .then(function (config) { + // Pass along the server config via an environment variable + var env = Object.create(process.env); + var options = { env: env }; + config.address = config.address; + config.port = config.port; + env.config = JSON.stringify(config); + + // Spawn electron application + const electron = require("electron"); + const child = require("child_process").spawn(electron, ["js/electron.js"], options); + + // Pipe all child process output to current stdout + child.stdout.on("data", function (buf) { + process.stdout.write(`Client: ${buf}`); + }); + + // Pipe all child process errors to current stderr + child.stderr.on("data", function (buf) { + process.stderr.write(`Client: ${buf}`); + }); + + child.on("error", function (err) { + process.stdout.write(`Client: ${err}`); + }); + + child.on('close', (code) => { + if (code != 0) { + console.log(`There something wrong. The clientonly is not running code ${code}`); + } + }); + + }) + .catch(function (reason) { + fail(`Unable to connect to server: (${reason})`); + }); + } else { + fail(); + } +}()); diff --git a/config/config.js.sample b/config/config.js.sample index b2eeee8a..a427a548 100644 --- a/config/config.js.sample +++ b/config/config.js.sample @@ -9,6 +9,11 @@ */ var config = { + address: "localhost", // Address to listen on, can be: + // - "localhost", "127.0.0.1", "::1" to listen on loopback interface + // - another specific IPv4/6 to listen on a specific interface + // - "", "0.0.0.0", "::" to listen on any interface + // Default, when address config is left out, is "localhost" port: 8080, ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], // Set [] to allow all IP addresses // or add a specific IPv4 of 192.168.1.5 : diff --git a/fonts/LICENSE.txt b/fonts/LICENSE.txt deleted file mode 100644 index 75b52484..00000000 --- a/fonts/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/fonts/Roboto-Black/Roboto-Black.ttf b/fonts/Roboto-Black/Roboto-Black.ttf deleted file mode 100644 index fbde625d..00000000 Binary files a/fonts/Roboto-Black/Roboto-Black.ttf and /dev/null differ diff --git a/fonts/Roboto-Black/Roboto-Black.woff b/fonts/Roboto-Black/Roboto-Black.woff deleted file mode 100644 index 94b19305..00000000 Binary files a/fonts/Roboto-Black/Roboto-Black.woff and /dev/null differ diff --git a/fonts/Roboto-Black/Roboto-Black.woff2 b/fonts/Roboto-Black/Roboto-Black.woff2 deleted file mode 100644 index c0cb786e..00000000 Binary files a/fonts/Roboto-Black/Roboto-Black.woff2 and /dev/null differ diff --git a/fonts/Roboto-BlackItalic/Roboto-BlackItalic.ttf b/fonts/Roboto-BlackItalic/Roboto-BlackItalic.ttf deleted file mode 100644 index 60f7782a..00000000 Binary files a/fonts/Roboto-BlackItalic/Roboto-BlackItalic.ttf and /dev/null differ diff --git a/fonts/Roboto-BlackItalic/Roboto-BlackItalic.woff b/fonts/Roboto-BlackItalic/Roboto-BlackItalic.woff deleted file mode 100644 index eb07f101..00000000 Binary files a/fonts/Roboto-BlackItalic/Roboto-BlackItalic.woff and /dev/null differ diff --git a/fonts/Roboto-BlackItalic/Roboto-BlackItalic.woff2 b/fonts/Roboto-BlackItalic/Roboto-BlackItalic.woff2 deleted file mode 100644 index 6ad4604f..00000000 Binary files a/fonts/Roboto-BlackItalic/Roboto-BlackItalic.woff2 and /dev/null differ diff --git a/fonts/Roboto-Bold/Roboto-Bold.ttf b/fonts/Roboto-Bold/Roboto-Bold.ttf deleted file mode 100644 index a355c27c..00000000 Binary files a/fonts/Roboto-Bold/Roboto-Bold.ttf and /dev/null differ diff --git a/fonts/Roboto-Bold/Roboto-Bold.woff b/fonts/Roboto-Bold/Roboto-Bold.woff deleted file mode 100644 index d1ef48b4..00000000 Binary files a/fonts/Roboto-Bold/Roboto-Bold.woff and /dev/null differ diff --git a/fonts/Roboto-Bold/Roboto-Bold.woff2 b/fonts/Roboto-Bold/Roboto-Bold.woff2 deleted file mode 100644 index 5658e9a7..00000000 Binary files a/fonts/Roboto-Bold/Roboto-Bold.woff2 and /dev/null differ diff --git a/fonts/Roboto-BoldItalic/Roboto-BoldItalic.ttf b/fonts/Roboto-BoldItalic/Roboto-BoldItalic.ttf deleted file mode 100644 index 3c9a7a37..00000000 Binary files a/fonts/Roboto-BoldItalic/Roboto-BoldItalic.ttf and /dev/null differ diff --git a/fonts/Roboto-BoldItalic/Roboto-BoldItalic.woff b/fonts/Roboto-BoldItalic/Roboto-BoldItalic.woff deleted file mode 100644 index 291c89dd..00000000 Binary files a/fonts/Roboto-BoldItalic/Roboto-BoldItalic.woff and /dev/null differ diff --git a/fonts/Roboto-BoldItalic/Roboto-BoldItalic.woff2 b/fonts/Roboto-BoldItalic/Roboto-BoldItalic.woff2 deleted file mode 100644 index 75123709..00000000 Binary files a/fonts/Roboto-BoldItalic/Roboto-BoldItalic.woff2 and /dev/null differ diff --git a/fonts/Roboto-Italic/Roboto-Italic.ttf b/fonts/Roboto-Italic/Roboto-Italic.ttf deleted file mode 100644 index ff6046d5..00000000 Binary files a/fonts/Roboto-Italic/Roboto-Italic.ttf and /dev/null differ diff --git a/fonts/Roboto-Italic/Roboto-Italic.woff b/fonts/Roboto-Italic/Roboto-Italic.woff deleted file mode 100644 index 3bd43138..00000000 Binary files a/fonts/Roboto-Italic/Roboto-Italic.woff and /dev/null differ diff --git a/fonts/Roboto-Italic/Roboto-Italic.woff2 b/fonts/Roboto-Italic/Roboto-Italic.woff2 deleted file mode 100644 index 77c81227..00000000 Binary files a/fonts/Roboto-Italic/Roboto-Italic.woff2 and /dev/null differ diff --git a/fonts/Roboto-Light/Roboto-Light.ttf b/fonts/Roboto-Light/Roboto-Light.ttf deleted file mode 100644 index 94c6bcc6..00000000 Binary files a/fonts/Roboto-Light/Roboto-Light.ttf and /dev/null differ diff --git a/fonts/Roboto-Light/Roboto-Light.woff b/fonts/Roboto-Light/Roboto-Light.woff deleted file mode 100644 index 1d1ad478..00000000 Binary files a/fonts/Roboto-Light/Roboto-Light.woff and /dev/null differ diff --git a/fonts/Roboto-Light/Roboto-Light.woff2 b/fonts/Roboto-Light/Roboto-Light.woff2 deleted file mode 100644 index 166c8218..00000000 Binary files a/fonts/Roboto-Light/Roboto-Light.woff2 and /dev/null differ diff --git a/fonts/Roboto-LightItalic/Roboto-LightItalic.ttf b/fonts/Roboto-LightItalic/Roboto-LightItalic.ttf deleted file mode 100644 index 04cc0023..00000000 Binary files a/fonts/Roboto-LightItalic/Roboto-LightItalic.ttf and /dev/null differ diff --git a/fonts/Roboto-LightItalic/Roboto-LightItalic.woff b/fonts/Roboto-LightItalic/Roboto-LightItalic.woff deleted file mode 100644 index 3662bf40..00000000 Binary files a/fonts/Roboto-LightItalic/Roboto-LightItalic.woff and /dev/null differ diff --git a/fonts/Roboto-LightItalic/Roboto-LightItalic.woff2 b/fonts/Roboto-LightItalic/Roboto-LightItalic.woff2 deleted file mode 100644 index 9ba7fbd2..00000000 Binary files a/fonts/Roboto-LightItalic/Roboto-LightItalic.woff2 and /dev/null differ diff --git a/fonts/Roboto-Medium/Roboto-Medium.ttf b/fonts/Roboto-Medium/Roboto-Medium.ttf deleted file mode 100644 index 39c63d74..00000000 Binary files a/fonts/Roboto-Medium/Roboto-Medium.ttf and /dev/null differ diff --git a/fonts/Roboto-Medium/Roboto-Medium.woff b/fonts/Roboto-Medium/Roboto-Medium.woff deleted file mode 100644 index 487ea69b..00000000 Binary files a/fonts/Roboto-Medium/Roboto-Medium.woff and /dev/null differ diff --git a/fonts/Roboto-Medium/Roboto-Medium.woff2 b/fonts/Roboto-Medium/Roboto-Medium.woff2 deleted file mode 100644 index 2622b475..00000000 Binary files a/fonts/Roboto-Medium/Roboto-Medium.woff2 and /dev/null differ diff --git a/fonts/Roboto-MediumItalic/Roboto-MediumItalic.ttf b/fonts/Roboto-MediumItalic/Roboto-MediumItalic.ttf deleted file mode 100644 index dc743f0a..00000000 Binary files a/fonts/Roboto-MediumItalic/Roboto-MediumItalic.ttf and /dev/null differ diff --git a/fonts/Roboto-MediumItalic/Roboto-MediumItalic.woff b/fonts/Roboto-MediumItalic/Roboto-MediumItalic.woff deleted file mode 100644 index 70dea35a..00000000 Binary files a/fonts/Roboto-MediumItalic/Roboto-MediumItalic.woff and /dev/null differ diff --git a/fonts/Roboto-MediumItalic/Roboto-MediumItalic.woff2 b/fonts/Roboto-MediumItalic/Roboto-MediumItalic.woff2 deleted file mode 100644 index 8b111d46..00000000 Binary files a/fonts/Roboto-MediumItalic/Roboto-MediumItalic.woff2 and /dev/null differ diff --git a/fonts/Roboto-Regular/Roboto-Regular.ttf b/fonts/Roboto-Regular/Roboto-Regular.ttf deleted file mode 100644 index 8c082c8d..00000000 Binary files a/fonts/Roboto-Regular/Roboto-Regular.ttf and /dev/null differ diff --git a/fonts/Roboto-Regular/Roboto-Regular.woff b/fonts/Roboto-Regular/Roboto-Regular.woff deleted file mode 100644 index 6ea53c20..00000000 Binary files a/fonts/Roboto-Regular/Roboto-Regular.woff and /dev/null differ diff --git a/fonts/Roboto-Regular/Roboto-Regular.woff2 b/fonts/Roboto-Regular/Roboto-Regular.woff2 deleted file mode 100644 index 10cf92b8..00000000 Binary files a/fonts/Roboto-Regular/Roboto-Regular.woff2 and /dev/null differ diff --git a/fonts/Roboto-Thin/Roboto-Thin.ttf b/fonts/Roboto-Thin/Roboto-Thin.ttf deleted file mode 100644 index d6955502..00000000 Binary files a/fonts/Roboto-Thin/Roboto-Thin.ttf and /dev/null differ diff --git a/fonts/Roboto-Thin/Roboto-Thin.woff b/fonts/Roboto-Thin/Roboto-Thin.woff deleted file mode 100644 index b0e7c557..00000000 Binary files a/fonts/Roboto-Thin/Roboto-Thin.woff and /dev/null differ diff --git a/fonts/Roboto-Thin/Roboto-Thin.woff2 b/fonts/Roboto-Thin/Roboto-Thin.woff2 deleted file mode 100644 index 5eee3b3e..00000000 Binary files a/fonts/Roboto-Thin/Roboto-Thin.woff2 and /dev/null differ diff --git a/fonts/Roboto-ThinItalic/Roboto-ThinItalic.ttf b/fonts/Roboto-ThinItalic/Roboto-ThinItalic.ttf deleted file mode 100644 index 07172ff6..00000000 Binary files a/fonts/Roboto-ThinItalic/Roboto-ThinItalic.ttf and /dev/null differ diff --git a/fonts/Roboto-ThinItalic/Roboto-ThinItalic.woff b/fonts/Roboto-ThinItalic/Roboto-ThinItalic.woff deleted file mode 100644 index 73f10ad4..00000000 Binary files a/fonts/Roboto-ThinItalic/Roboto-ThinItalic.woff and /dev/null differ diff --git a/fonts/Roboto-ThinItalic/Roboto-ThinItalic.woff2 b/fonts/Roboto-ThinItalic/Roboto-ThinItalic.woff2 deleted file mode 100644 index 292c86f4..00000000 Binary files a/fonts/Roboto-ThinItalic/Roboto-ThinItalic.woff2 and /dev/null differ diff --git a/fonts/RobotoCondensed-Bold/RobotoCondensed-Bold.ttf b/fonts/RobotoCondensed-Bold/RobotoCondensed-Bold.ttf deleted file mode 100644 index fc28868a..00000000 Binary files a/fonts/RobotoCondensed-Bold/RobotoCondensed-Bold.ttf and /dev/null differ diff --git a/fonts/RobotoCondensed-Bold/RobotoCondensed-Bold.woff b/fonts/RobotoCondensed-Bold/RobotoCondensed-Bold.woff deleted file mode 100644 index cf577ca5..00000000 Binary files a/fonts/RobotoCondensed-Bold/RobotoCondensed-Bold.woff and /dev/null differ diff --git a/fonts/RobotoCondensed-Bold/RobotoCondensed-Bold.woff2 b/fonts/RobotoCondensed-Bold/RobotoCondensed-Bold.woff2 deleted file mode 100644 index 8b2e28cd..00000000 Binary files a/fonts/RobotoCondensed-Bold/RobotoCondensed-Bold.woff2 and /dev/null differ diff --git a/fonts/RobotoCondensed-BoldItalic/RobotoCondensed-BoldItalic.ttf b/fonts/RobotoCondensed-BoldItalic/RobotoCondensed-BoldItalic.ttf deleted file mode 100644 index e1a648ff..00000000 Binary files a/fonts/RobotoCondensed-BoldItalic/RobotoCondensed-BoldItalic.ttf and /dev/null differ diff --git a/fonts/RobotoCondensed-BoldItalic/RobotoCondensed-BoldItalic.woff b/fonts/RobotoCondensed-BoldItalic/RobotoCondensed-BoldItalic.woff deleted file mode 100644 index 0b5db116..00000000 Binary files a/fonts/RobotoCondensed-BoldItalic/RobotoCondensed-BoldItalic.woff and /dev/null differ diff --git a/fonts/RobotoCondensed-BoldItalic/RobotoCondensed-BoldItalic.woff2 b/fonts/RobotoCondensed-BoldItalic/RobotoCondensed-BoldItalic.woff2 deleted file mode 100644 index 5d3f2072..00000000 Binary files a/fonts/RobotoCondensed-BoldItalic/RobotoCondensed-BoldItalic.woff2 and /dev/null differ diff --git a/fonts/RobotoCondensed-Italic/RobotoCondensed-Italic.ttf b/fonts/RobotoCondensed-Italic/RobotoCondensed-Italic.ttf deleted file mode 100644 index 97ff9f1e..00000000 Binary files a/fonts/RobotoCondensed-Italic/RobotoCondensed-Italic.ttf and /dev/null differ diff --git a/fonts/RobotoCondensed-Italic/RobotoCondensed-Italic.woff b/fonts/RobotoCondensed-Italic/RobotoCondensed-Italic.woff deleted file mode 100644 index 4548bfa9..00000000 Binary files a/fonts/RobotoCondensed-Italic/RobotoCondensed-Italic.woff and /dev/null differ diff --git a/fonts/RobotoCondensed-Italic/RobotoCondensed-Italic.woff2 b/fonts/RobotoCondensed-Italic/RobotoCondensed-Italic.woff2 deleted file mode 100644 index 04141901..00000000 Binary files a/fonts/RobotoCondensed-Italic/RobotoCondensed-Italic.woff2 and /dev/null differ diff --git a/fonts/RobotoCondensed-Light/RobotoCondensed-Light.ttf b/fonts/RobotoCondensed-Light/RobotoCondensed-Light.ttf deleted file mode 100644 index 2dae31e2..00000000 Binary files a/fonts/RobotoCondensed-Light/RobotoCondensed-Light.ttf and /dev/null differ diff --git a/fonts/RobotoCondensed-Light/RobotoCondensed-Light.woff b/fonts/RobotoCondensed-Light/RobotoCondensed-Light.woff deleted file mode 100644 index b797af13..00000000 Binary files a/fonts/RobotoCondensed-Light/RobotoCondensed-Light.woff and /dev/null differ diff --git a/fonts/RobotoCondensed-Light/RobotoCondensed-Light.woff2 b/fonts/RobotoCondensed-Light/RobotoCondensed-Light.woff2 deleted file mode 100644 index 1e3b487a..00000000 Binary files a/fonts/RobotoCondensed-Light/RobotoCondensed-Light.woff2 and /dev/null differ diff --git a/fonts/RobotoCondensed-LightItalic/RobotoCondensed-LightItalic.ttf b/fonts/RobotoCondensed-LightItalic/RobotoCondensed-LightItalic.ttf deleted file mode 100644 index da108d3a..00000000 Binary files a/fonts/RobotoCondensed-LightItalic/RobotoCondensed-LightItalic.ttf and /dev/null differ diff --git a/fonts/RobotoCondensed-LightItalic/RobotoCondensed-LightItalic.woff b/fonts/RobotoCondensed-LightItalic/RobotoCondensed-LightItalic.woff deleted file mode 100644 index 8eae55a8..00000000 Binary files a/fonts/RobotoCondensed-LightItalic/RobotoCondensed-LightItalic.woff and /dev/null differ diff --git a/fonts/RobotoCondensed-LightItalic/RobotoCondensed-LightItalic.woff2 b/fonts/RobotoCondensed-LightItalic/RobotoCondensed-LightItalic.woff2 deleted file mode 100644 index 947a1358..00000000 Binary files a/fonts/RobotoCondensed-LightItalic/RobotoCondensed-LightItalic.woff2 and /dev/null differ diff --git a/fonts/RobotoCondensed-Regular/RobotoCondensed-Regular.ttf b/fonts/RobotoCondensed-Regular/RobotoCondensed-Regular.ttf deleted file mode 100644 index c2304c14..00000000 Binary files a/fonts/RobotoCondensed-Regular/RobotoCondensed-Regular.ttf and /dev/null differ diff --git a/fonts/RobotoCondensed-Regular/RobotoCondensed-Regular.woff b/fonts/RobotoCondensed-Regular/RobotoCondensed-Regular.woff deleted file mode 100644 index 3e25d65d..00000000 Binary files a/fonts/RobotoCondensed-Regular/RobotoCondensed-Regular.woff and /dev/null differ diff --git a/fonts/RobotoCondensed-Regular/RobotoCondensed-Regular.woff2 b/fonts/RobotoCondensed-Regular/RobotoCondensed-Regular.woff2 deleted file mode 100644 index d7c42c8c..00000000 Binary files a/fonts/RobotoCondensed-Regular/RobotoCondensed-Regular.woff2 and /dev/null differ diff --git a/fonts/package.json b/fonts/package.json new file mode 100644 index 00000000..ad164516 --- /dev/null +++ b/fonts/package.json @@ -0,0 +1,15 @@ +{ + "name": "magicmirror-fonts", + "description": "Package for fonts use by MagicMirror Core.", + "repository": { + "type": "git", + "url": "git+https://github.com/MichMich/MagicMirror.git" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/MichMich/MagicMirror/issues" + }, + "dependencies": { + "roboto-fontface": "^0.8.0" + } +} diff --git a/fonts/roboto.css b/fonts/roboto.css index ce9099da..11d3f41d 100644 --- a/fonts/roboto.css +++ b/fonts/roboto.css @@ -5,9 +5,9 @@ src: local("Roboto Thin"), local("Roboto-Thin"), - url("Roboto-Thin/Roboto-Thin.woff2") format("woff2"), - url("Roboto-Thin/Roboto-Thin.woff") format("woff"), - url("Roboto-Thin/Roboto-Thin.ttf") format("truetype"); + url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff2") format("woff2"), + url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff") format("woff"), + url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.ttf") format("truetype"); } @font-face { @@ -17,9 +17,9 @@ src: local("Roboto Condensed Light"), local("RobotoCondensed-Light"), - url("RobotoCondensed-Light/RobotoCondensed-Light.woff2") format("woff2"), - url("RobotoCondensed-Light/RobotoCondensed-Light.woff") format("woff"), - url("RobotoCondensed-Light/RobotoCondensed-Light.ttf") format("truetype"); + url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.woff2") format("woff2"), + url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.woff") format("woff"), + url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.ttf") format("truetype"); } @font-face { @@ -29,9 +29,9 @@ src: local("Roboto Condensed"), local("RobotoCondensed-Regular"), - url("RobotoCondensed-Regular/RobotoCondensed-Regular.woff2") format("woff2"), - url("RobotoCondensed-Regular/RobotoCondensed-Regular.woff") format("woff"), - url("RobotoCondensed-Regular/RobotoCondensed-Regular.ttf") format("truetype"); + url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.woff2") format("woff2"), + url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.woff") format("woff"), + url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.ttf") format("truetype"); } @font-face { @@ -41,9 +41,9 @@ src: local("Roboto Condensed Bold"), local("RobotoCondensed-Bold"), - url("RobotoCondensed-Bold/RobotoCondensed-Bold.woff2") format("woff2"), - url("RobotoCondensed-Bold/RobotoCondensed-Bold.woff") format("woff"), - url("RobotoCondensed-Bold/RobotoCondensed-Bold.ttf") format("truetype"); + url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.woff2") format("woff2"), + url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.woff") format("woff"), + url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.ttf") format("truetype"); } @font-face { @@ -53,9 +53,9 @@ src: local("Roboto"), local("Roboto-Regular"), - url("Roboto-Regular/Roboto-Regular.woff2") format("woff2"), - url("Roboto-Regular/Roboto-Regular.woff") format("woff"), - url("Roboto-Regular/Roboto-Regular.ttf") format("truetype"); + url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2") format("woff2"), + url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff") format("woff"), + url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.ttf") format("truetype"); } @font-face { @@ -65,9 +65,9 @@ src: local("Roboto Medium"), local("Roboto-Medium"), - url("Roboto-Medium/Roboto-Medium.woff2") format("woff2"), - url("Roboto-Medium/Roboto-Medium.woff") format("woff"), - url("Roboto-Medium/Roboto-Medium.ttf") format("truetype"); + url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2") format("woff2"), + url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff") format("woff"), + url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.ttf") format("truetype"); } @font-face { @@ -77,9 +77,9 @@ src: local("Roboto Bold"), local("Roboto-Bold"), - url("Roboto-Bold/Roboto-Bold.woff2") format("woff2"), - url("Roboto-Bold/Roboto-Bold.woff") format("woff"), - url("Roboto-Bold/Roboto-Bold.ttf") format("truetype"); + url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2") format("woff2"), + url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff") format("woff"), + url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.ttf") format("truetype"); } @font-face { @@ -89,7 +89,7 @@ src: local("Roboto Light"), local("Roboto-Light"), - url("Roboto-Light/Roboto-Light.woff2") format("woff2"), - url("Roboto-Light/Roboto-Light.woff") format("woff"), - url("Roboto-Light/Roboto-Light.ttf") format("truetype"); + url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2") format("woff2"), + url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff") format("woff"), + url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.ttf") format("truetype"); } diff --git a/index.html b/index.html index 85951a85..f3e8d12f 100644 --- a/index.html +++ b/index.html @@ -1,7 +1,7 @@ - Magic Mirror + MagicMirror² diff --git a/js/class.js b/js/class.js index 3c44250e..ec75f6f2 100644 --- a/js/class.js +++ b/js/class.js @@ -4,15 +4,15 @@ */ // Inspired by base2 and Prototype -(function() { +(function () { var initializing = false; - var fnTest = /xyz/.test(function() {xyz;}) ? /\b_super\b/ : /.*/; + var fnTest = /xyz/.test(function () { xyz; }) ? /\b_super\b/ : /.*/; // The base Class implementation (does nothing) - this.Class = function() {}; + this.Class = function () { }; // Create a new Class that inherits from this class - Class.extend = function(prop) { + Class.extend = function (prop) { var _super = this.prototype; // Instantiate a base class (but only create the instance, @@ -30,23 +30,23 @@ for (var name in prop) { // Check if we're overwriting an existing function prototype[name] = typeof prop[name] == "function" && - typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn) { - return function() { - var tmp = this._super; + typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function (name, fn) { + return function () { + var tmp = this._super; - // Add a new ._super() method that is the same method - // but on the super-class - this._super = _super[name]; + // Add a new ._super() method that is the same method + // but on the super-class + this._super = _super[name]; - // The method only need to be bound temporarily, so we - // remove it when we're done executing - var ret = fn.apply(this, arguments); - this._super = tmp; + // The method only need to be bound temporarily, so we + // remove it when we're done executing + var ret = fn.apply(this, arguments); + this._super = tmp; - return ret; - }; - })(name, prop[name]) : prop[name]; + return ret; + }; + })(name, prop[name]) : prop[name]; } // The dummy class constructor @@ -90,4 +90,9 @@ function cloneObject(obj) { } /*************** DO NOT EDIT THE LINE BELOW ***************/ -if (typeof module !== "undefined") {module.exports = Class;} +if (typeof module !== "undefined") { + module.exports = Class; + module.exports._test = { + cloneObject: cloneObject + } +} diff --git a/js/defaults.js b/js/defaults.js index eada87a4..08c4d945 100644 --- a/js/defaults.js +++ b/js/defaults.js @@ -8,10 +8,12 @@ */ var port = 8080; +var address = "localhost"; if (typeof(mmPort) !== "undefined") { port = mmPort; } var defaults = { + address: address, port: port, kioskmode: false, electronOptions: {}, diff --git a/js/electron.js b/js/electron.js index 334a3593..84842ed2 100644 --- a/js/electron.js +++ b/js/electron.js @@ -6,7 +6,7 @@ const electron = require("electron"); const core = require(__dirname + "/app.js"); // Config -var config = {}; +var config = process.env.config ? JSON.parse(process.env.config) : {}; // Module to control application life. const app = electron.app; // Module to create native browser window. @@ -17,7 +17,6 @@ const BrowserWindow = electron.BrowserWindow; let mainWindow; function createWindow() { - var electronOptionsDefaults = { width: 800, height: 600, @@ -46,8 +45,9 @@ function createWindow() { mainWindow = new BrowserWindow(electronOptions); // and load the index.html of the app. - //mainWindow.loadURL('file://' + __dirname + '../../index.html'); - mainWindow.loadURL("http://localhost:" + config.port); + // If config.address is not defined or is an empty string (listening on all interfaces), connect to localhost + var address = (config.address === void 0) | (config.address === "") ? (config.address = "localhost") : config.address; + mainWindow.loadURL(`http://${address}:${config.port}`); // Open the DevTools if run with "npm start dev" if (process.argv.includes("dev")) { @@ -96,8 +96,10 @@ app.on("activate", function() { } }); -// Start the core application. +// Start the core application if server is run on localhost // This starts all node helpers and starts the webserver. -core.start(function(c) { - config = c; -}); +if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) > -1) { + core.start(function(c) { + config = c; + }); +} diff --git a/js/module.js b/js/module.js index 0d51e559..413efa46 100644 --- a/js/module.js +++ b/js/module.js @@ -194,7 +194,7 @@ var Module = Class.extend({ * return string - File path. */ file: function (file) { - return this.data.path + "/" + file; + return (this.data.path + "/" + file).replace("//", "/"); }, /* loadStyles() diff --git a/js/server.js b/js/server.js index 002c2031..fbc51cbc 100644 --- a/js/server.js +++ b/js/server.js @@ -13,6 +13,7 @@ var path = require("path"); var ipfilter = require("express-ipfilter").IpFilter; var fs = require("fs"); var helmet = require("helmet"); +var Utils = require(__dirname + "/utils.js"); var Server = function(config, callback) { @@ -26,11 +27,11 @@ var Server = function(config, callback) { server.listen(port, config.address ? config.address : null); if (config.ipWhitelist instanceof Array && config.ipWhitelist.length == 0) { - console.info("You're using a full whitelist configuration to allow for all IPs") + console.info(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs")) } app.use(function(req, res, next) { - var result = ipfilter(config.ipWhitelist, {mode: "allow", log: false})(req, res, function(err) { + var result = ipfilter(config.ipWhitelist, {mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false})(req, res, function(err) { if (err === undefined) { return next(); } @@ -52,6 +53,10 @@ var Server = function(config, callback) { res.send(global.version); }); + app.get("/config", function(req,res) { + res.send(config); + }); + app.get("/", function(req, res) { var html = fs.readFileSync(path.resolve(global.root_path + "/index.html"), {encoding: "utf8"}); html = html.replace("#VERSION#", global.version); diff --git a/modules/README.md b/modules/README.md index 4a0a1da5..12a42e59 100644 --- a/modules/README.md +++ b/modules/README.md @@ -472,7 +472,7 @@ this.translate("RUNNING", { { "RUNNING": "Slutar", } - +```` In this case the `translate`-function will not find any variables in the translation, will look for `fallback` variable and use that if possible to create the translation. ## The Node Helper: node_helper.js diff --git a/modules/default/calendar/calendar.js b/modules/default/calendar/calendar.js index 9ab864b9..ad3be8ce 100644 --- a/modules/default/calendar/calendar.js +++ b/modules/default/calendar/calendar.js @@ -67,30 +67,7 @@ Module.register("calendar", { Log.log("Starting module: " + this.name); // Set locale. - moment.locale(config.language); - - switch (config.timeFormat) { - case 12: { - moment.updateLocale(config.language, { - longDateFormat: { - LT: "h:mm A" - } - }); - break; - } - case 24: { - moment.updateLocale(config.language, { - longDateFormat: { - LT: "hh:mm" - } - }); - break; - } - // If config.timeFormat was not given (or has invalid format) default to locale default - default: { - break; - } - } + moment.updateLocale(config.language, this.getLocaleSpecification(config.timeFormat)); for (var c in this.config.calendars) { var calendar = this.config.calendars[c]; @@ -102,7 +79,9 @@ Module.register("calendar", { }; // we check user and password here for backwards compatibility with old configs - if(calendar.user && calendar.pass){ + if(calendar.user && calendar.pass) { + Log.warn("Deprecation warning: Please update your calendar authentication configuration."); + Log.warn("https://github.com/MichMich/MagicMirror/tree/v2.1.2/modules/default/calendar#calendar-authentication-options"); calendar.auth = { user: calendar.user, pass: calendar.pass @@ -153,20 +132,6 @@ Module.register("calendar", { for (var e in events) { var event = events[e]; - - var excluded = false; - for (var f in this.config.excludedEvents) { - var filter = this.config.excludedEvents[f]; - if (event.title.toLowerCase().includes(filter.toLowerCase())) { - excluded = true; - break; - } - } - - if (excluded) { - continue; - } - var eventWrapper = document.createElement("tr"); if (this.config.colored) { @@ -320,6 +285,31 @@ Module.register("calendar", { return wrapper; }, + /** + * This function accepts a number (either 12 or 24) and returns a moment.js LocaleSpecification with the + * corresponding timeformat to be used in the calendar display. If no number is given (or otherwise invalid input) + * it will a localeSpecification object with the system locale time format. + * + * @param {number} timeFormat Specifies either 12 or 24 hour time format + * @returns {moment.LocaleSpecification} + */ + getLocaleSpecification: function(timeFormat) { + switch (timeFormat) { + case 12: { + return { longDateFormat: {LT: "h:mm A"} }; + break; + } + case 24: { + return { longDateFormat: {LT: "HH:mm"} }; + break; + } + default: { + return { longDateFormat: {LT: moment.localeData().longDateFormat("LT")} }; + break; + } + } + }, + /* hasCalendarURL(url) * Check if this config contains the calendar url. * @@ -366,7 +356,7 @@ Module.register("calendar", { return a.startDate - b.startDate; }); - return events.slice(0, this.config.maximumEntries); + return events; }, /* createEventList(url) @@ -377,6 +367,7 @@ Module.register("calendar", { addCalendar: function (url, auth, calendarConfig) { this.sendSocketNotification("ADD_CALENDAR", { url: url, + excludedEvents: calendarConfig.excludedEvents || this.config.excludedEvents, maximumEntries: calendarConfig.maximumEntries || this.config.maximumEntries, maximumNumberOfDays: calendarConfig.maximumNumberOfDays || this.config.maximumNumberOfDays, fetchInterval: this.config.fetchInterval, @@ -437,25 +428,27 @@ Module.register("calendar", { return defaultValue; }, - /* shorten(string, maxLength) - * Shortens a string if it's longer than maxLength. - * Adds an ellipsis to the end. - * - * argument string string - The string to shorten. - * argument maxLength number - The max length of the string. - * argument wrapEvents - Wrap the text after the line has reached maxLength - * - * return string - The shortened string. + /** + * Shortens a string if it's longer than maxLength and add a ellipsis to the end + * + * @param {string} string Text string to shorten + * @param {number} maxLength The max length of the string + * @param {boolean} wrapEvents Wrap the text after the line has reached maxLength + * @returns {string} The shortened string */ shorten: function (string, maxLength, wrapEvents) { - if (wrapEvents) { + if (typeof string !== "string") { + return ""; + } + + if (wrapEvents === true) { var temp = ""; var currentLine = ""; var words = string.split(" "); for (var i = 0; i < words.length; i++) { var word = words[i]; - if (currentLine.length + word.length < 25 - 1) { // max - 1 to account for a space + if (currentLine.length + word.length < (typeof maxLength === "number" ? maxLength : 25) - 1) { // max - 1 to account for a space currentLine += (word + " "); } else { if (currentLine.length > 0) { @@ -467,12 +460,12 @@ Module.register("calendar", { } } - return temp + currentLine; + return (temp + currentLine).trim(); } else { - if (string.length > maxLength) { - return string.slice(0, maxLength) + "…"; + if (maxLength && typeof maxLength === "number" && string.length > maxLength) { + return string.trim().slice(0, maxLength) + "…"; } else { - return string; + return string.trim(); } } }, @@ -522,6 +515,8 @@ Module.register("calendar", { var calendar = this.calendarData[url]; for (var e in calendar) { var event = cloneObject(calendar[e]); + event.symbol = this.symbolsForUrl(url); + event.color = this.colorForUrl(url); delete event.url; eventList.push(event); } diff --git a/modules/default/calendar/calendarfetcher.js b/modules/default/calendar/calendarfetcher.js index 9655f21e..12495f78 100644 --- a/modules/default/calendar/calendarfetcher.js +++ b/modules/default/calendar/calendarfetcher.js @@ -8,7 +8,7 @@ var ical = require("./vendor/ical.js"); var moment = require("moment"); -var CalendarFetcher = function(url, reloadInterval, maximumEntries, maximumNumberOfDays, auth) { +var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth) { var self = this; var reloadTimer = null; @@ -113,6 +113,19 @@ var CalendarFetcher = function(url, reloadInterval, maximumEntries, maximumNumbe title = event.description; } + var excluded = false; + for (var f in excludedEvents) { + var filter = excludedEvents[f]; + if (title.toLowerCase().includes(filter.toLowerCase())) { + excluded = true; + break; + } + } + + if (excluded) { + continue; + } + var location = event.location || false; var geo = event.geo || false; var description = event.description || false; diff --git a/modules/default/calendar/node_helper.js b/modules/default/calendar/node_helper.js index 90c286c8..25e7f1f7 100644 --- a/modules/default/calendar/node_helper.js +++ b/modules/default/calendar/node_helper.js @@ -24,7 +24,7 @@ module.exports = NodeHelper.create({ socketNotificationReceived: function(notification, payload) { if (notification === "ADD_CALENDAR") { //console.log('ADD_CALENDAR: '); - this.createFetcher(payload.url, payload.fetchInterval, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth); + this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth); } }, @@ -36,7 +36,7 @@ module.exports = NodeHelper.create({ * attribute reloadInterval number - Reload interval in milliseconds. */ - createFetcher: function(url, fetchInterval, maximumEntries, maximumNumberOfDays, auth) { + createFetcher: function(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth) { var self = this; if (!validUrl.isUri(url)) { @@ -47,7 +47,7 @@ module.exports = NodeHelper.create({ var fetcher; if (typeof self.fetchers[url] === "undefined") { console.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval); - fetcher = new CalendarFetcher(url, fetchInterval, maximumEntries, maximumNumberOfDays, auth); + fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth); fetcher.onReceive(function(fetcher) { //console.log('Broadcast events.'); diff --git a/modules/default/clock/clock_styles.css b/modules/default/clock/clock_styles.css index dd9eb0f8..1df9bf83 100644 --- a/modules/default/clock/clock_styles.css +++ b/modules/default/clock/clock_styles.css @@ -1,5 +1,5 @@ .clockCircle { - margin: 0; + margin: 0 auto; position: relative; border-radius: 50%; background-size: 100%; diff --git a/modules/default/currentweather/currentweather.js b/modules/default/currentweather/currentweather.js index 702c67d8..a43b5444 100644 --- a/modules/default/currentweather/currentweather.js +++ b/modules/default/currentweather/currentweather.js @@ -486,7 +486,7 @@ Module.register("currentweather",{ * * argument temperature number - Temperature. * - * return number - Rounded Temperature. + * return string - Rounded Temperature. */ roundValue: function(temperature) { var decimals = this.config.roundTemp ? 0 : 1; diff --git a/modules/default/updatenotification/updatenotification.js b/modules/default/updatenotification/updatenotification.js index f663f593..bf7ec2c1 100644 --- a/modules/default/updatenotification/updatenotification.js +++ b/modules/default/updatenotification/updatenotification.js @@ -11,11 +11,11 @@ Module.register("updatenotification", { }, - notificationReceived: function(notification, payload, sender) { + notificationReceived: function (notification, payload, sender) { if (notification === "DOM_OBJECTS_CREATED") { this.sendSocketNotification("CONFIG", this.config); this.sendSocketNotification("MODULES", Module.definitions); - this.hide(0,{lockString: self.identifier}); + this.hide(0, { lockString: self.identifier }); } }, @@ -26,11 +26,11 @@ Module.register("updatenotification", { } }, - updateUI: function() { + updateUI: function () { var self = this; if (this.status && this.status.behind > 0) { self.updateDom(0); - self.show(1000, {lockString: self.identifier}); + self.show(1000, { lockString: self.identifier }); } }, @@ -59,8 +59,8 @@ Module.register("updatenotification", { var subtext = document.createElement("div"); subtext.innerHTML = this.translate("UPDATE_INFO") - .replace("COMMIT_COUNT", this.status.behind + " " + ((this.status.behind == 1)? "commit" : "commits")) - .replace("BRANCH_NAME", this.status.current); + .replace("COMMIT_COUNT", this.status.behind + " " + ((this.status.behind == 1) ? "commit" : "commits")) + .replace("BRANCH_NAME", this.status.current); subtext.className = "xsmall dimmed"; wrapper.appendChild(subtext); } diff --git a/modules/default/weatherforecast/weatherforecast.js b/modules/default/weatherforecast/weatherforecast.js index aab3aa15..87fe40ae 100644 --- a/modules/default/weatherforecast/weatherforecast.js +++ b/modules/default/weatherforecast/weatherforecast.js @@ -24,6 +24,7 @@ Module.register("weatherforecast",{ fade: true, fadePoint: 0.25, // Start on 1/4th of the list. colored: false, + scale: false, initialLoadDelay: 2500, // 2.5 seconds delay. This delay is used to keep the OpenWeather API happy. retryDelay: 2500, @@ -139,13 +140,28 @@ Module.register("weatherforecast",{ icon.className = "wi weathericon " + forecast.icon; iconCell.appendChild(icon); + var degreeLabel = ""; + if(this.config.scale) { + switch(this.config.units) { + case "metric": + degreeLabel = " °C"; + break; + case "imperial": + degreeLabel = " °F"; + break; + case "default": + degreeLabel = "K"; + break; + } + } + var maxTempCell = document.createElement("td"); - maxTempCell.innerHTML = forecast.maxTemp; + maxTempCell.innerHTML = forecast.maxTemp + degreeLabel; maxTempCell.className = "align-right bright max-temp"; row.appendChild(maxTempCell); var minTempCell = document.createElement("td"); - minTempCell.innerHTML = forecast.minTemp; + minTempCell.innerHTML = forecast.minTemp + degreeLabel; minTempCell.className = "align-right min-temp"; row.appendChild(minTempCell); @@ -358,7 +374,7 @@ Module.register("weatherforecast",{ * * argument temperature number - Temperature. * - * return number - Rounded Temperature. + * return string - Rounded Temperature. */ roundValue: function(temperature) { var decimals = this.config.roundTemp ? 0 : 1; diff --git a/package.json b/package.json index fe64cc41..d081d164 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "start": "sh run-start.sh", "install": "cd vendor && npm install", - "postinstall": "sh installers/postinstall/postinstall.sh", + "install-fonts": "cd fonts && npm install", + "postinstall": "sh installers/postinstall/postinstall.sh && npm run install-fonts", "test": "NODE_ENV=test ./node_modules/mocha/bin/mocha tests --recursive", "test:unit": "NODE_ENV=test ./node_modules/mocha/bin/mocha tests/unit --recursive", "test:e2e": "NODE_ENV=test ./node_modules/mocha/bin/mocha tests/e2e --recursive", @@ -44,8 +45,9 @@ "http-auth": "^3.1.3", "jshint": "^2.9.4", "mocha": "^3.4.2", - "spectron": "^3.6.4", - "stylelint": "^7.11.0", + "mocha-each": "^1.1.0", + "spectron": "3.6.x", + "stylelint": "^8.0.0", "stylelint-config-standard": "latest", "time-grunt": "latest" }, @@ -54,10 +56,11 @@ "colors": "^1.1.2", "electron": "^1.6.10", "express": "^4.15.3", - "express-ipfilter": "latest", + "express-ipfilter": "0.3.1", "feedme": "latest", "helmet": "^3.6.1", "iconv-lite": "latest", + "mocha-logger": "^1.0.5", "moment": "latest", "request": "^2.81.0", "rrule-alt": "^2.2.5", diff --git a/tests/configs/check_config.js b/tests/configs/check_config.js index fa294761..f5ad61c1 100644 --- a/tests/configs/check_config.js +++ b/tests/configs/check_config.js @@ -14,7 +14,7 @@ var path = require("path"); var fs = require("fs"); var Utils = require(__dirname + "/../../js/utils.js"); -if (process.env.NODE_ENV == "test") {return 0}; +if (process.env.NODE_ENV == "test") { return 0 }; /* getConfigFile() * Return string with path of configuration file @@ -48,9 +48,9 @@ try { // In case the there errors show messages and // return console.info(Utils.colors.info("Checking file... ", configFileName)); - // I'm not sure if all ever is utf-8 -fs.readFile(configFileName, "utf-8", function(err, data) { - if (err) {throw err;} +// I'm not sure if all ever is utf-8 +fs.readFile(configFileName, "utf-8", function (err, data) { + if (err) { throw err; } v.JSHINT(data); // Parser by jshint if (v.JSHINT.errors.length == 0) { diff --git a/tests/configs/modules/clock/es/clock_showWeek.js b/tests/configs/modules/clock/es/clock_showWeek.js new file mode 100644 index 00000000..29550f04 --- /dev/null +++ b/tests/configs/modules/clock/es/clock_showWeek.js @@ -0,0 +1,38 @@ + +/* Magic Mirror + * + * Test config for default clock module + * Language es for showWeek feature + * + * By Rodrigo Ramírez Norambuena + * https://rodrigoramirez.com + * + * MIT Licensed. + */ + +var config = { + port: 8080, + ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], + + language: "es", + timeFormat: 12, + units: "metric", + electronOptions: { + webPreferences: { + nodeIntegration: true, + }, + }, + + modules: [ + { + module: "clock", + position: "middle_center", + config: { + showWeek: true + } + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") {module.exports = config;} diff --git a/tests/configs/modules/helloworld/helloworld_default.js b/tests/configs/modules/helloworld/helloworld_default.js new file mode 100644 index 00000000..710000f0 --- /dev/null +++ b/tests/configs/modules/helloworld/helloworld_default.js @@ -0,0 +1,31 @@ +/* Magic Mirror + * + * Test config sample module hello world default config + * + * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com + * MIT Licensed. + */ + +var config = { + port: 8080, + ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], + + language: "en", + timeFormat: 24, + units: "metric", + electronOptions: { + webPreferences: { + nodeIntegration: true, + }, + }, + + modules: [ + { + module: "helloworld", + position: "bottom_bar" + } + ] +}; + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") {module.exports = config;} diff --git a/tests/e2e/dev_console.js b/tests/e2e/dev_console.js index b430e9e4..42530a38 100644 --- a/tests/e2e/dev_console.js +++ b/tests/e2e/dev_console.js @@ -1,50 +1,65 @@ -const Application = require("spectron").Application; +const helpers = require("./global-setup"); const path = require("path"); -const chai = require("chai"); -const expect = chai.expect; -const chaiAsPromised = require("chai-as-promised"); +const request = require("request"); -var electronPath = path.join(__dirname, "../../", "node_modules", ".bin", "electron"); +const expect = require("chai").expect; -if (process.platform === "win32") { - electronPath += ".cmd"; -} +const describe = global.describe; +const it = global.it; +const beforeEach = global.beforeEach; +const afterEach = global.afterEach; -var appPath = path.join(__dirname, "../../js/electron.js"); +describe("Development console tests", function() { + // This tests fail and crash another tests + // Suspect problem with window focus + // FIXME + return false; -var app = new Application({ - path: electronPath -}); + helpers.setupTimeout(this); -global.before(function () { - chai.should(); - chai.use(chaiAsPromised); -}); - -describe("Argument 'dev'", function () { - this.timeout(20000); + var app = null; before(function() { // Set config sample for use in test process.env.MM_CONFIG_FILE = "tests/configs/env.js"; }); - afterEach(function (done) { - app.stop().then(function() { done(); }); - }); + describe("Without 'dev' commandline argument", function() { + before(function() { + return helpers + .startApplication({ + args: ["js/electron.js"] + }) + .then(function(startedApp) { + app = startedApp; + }); + }); - it("should not open dev console when absent", function () { - app.args = [appPath]; + after(function() { + return helpers.stopApplication(app); + }); - return app.start().then(function() { + it("should not open dev console when absent", function() { return expect(app.browserWindow.isDevToolsOpened()).to.eventually.equal(false); }); }); - it("should open dev console when provided", function () { - app.args = [appPath, "dev"]; + describe("With 'dev' commandline argument", function() { + before(function() { + return helpers + .startApplication({ + args: ["js/electron.js", "dev"] + }) + .then(function(startedApp) { + app = startedApp; + }); + }); - return app.start().then(function() { + after(function() { + return helpers.stopApplication(app); + }); + + it("should open dev console when provided", function() { return expect(app.browserWindow.isDevToolsOpened()).to.eventually.equal(true); }); }); diff --git a/tests/e2e/env_spec.js b/tests/e2e/env_spec.js index 202bd5e4..9454b92e 100644 --- a/tests/e2e/env_spec.js +++ b/tests/e2e/env_spec.js @@ -1,47 +1,69 @@ -const globalSetup = require("./global-setup"); -const app = globalSetup.app; +const helpers = require("./global-setup"); +const path = require("path"); const request = require("request"); -const chai = require("chai"); -const expect = chai.expect; -describe("Electron app environment", function () { - this.timeout(20000); +const expect = require("chai").expect; + +const describe = global.describe; +const it = global.it; +const beforeEach = global.beforeEach; +const afterEach = global.afterEach; + +describe("Electron app environment", function() { + helpers.setupTimeout(this); + + var app = null; before(function() { // Set config sample for use in test process.env.MM_CONFIG_FILE = "tests/configs/env.js"; }); - beforeEach(function (done) { - app.start().then(function() { done(); } ); + beforeEach(function() { + return helpers + .startApplication({ + args: ["js/electron.js"] + }) + .then(function(startedApp) { + app = startedApp; + }); }); - afterEach(function (done) { - app.stop().then(function() { done(); }); + afterEach(function() { + return helpers.stopApplication(app); }); - it("is set to open new app window", function () { - return app.client.waitUntilWindowLoaded() - .getWindowCount().should.eventually.equal(1); + it("should open a browserwindow", function() { + return app.client + .waitUntilWindowLoaded() + .browserWindow.focus() + .getWindowCount() + .should.eventually.equal(1) + .browserWindow.isMinimized() + .should.eventually.be.false.browserWindow.isDevToolsOpened() + .should.eventually.be.false.browserWindow.isVisible() + .should.eventually.be.true.browserWindow.isFocused() + .should.eventually.be.true.browserWindow.getBounds() + .should.eventually.have.property("width") + .and.be.above(0) + .browserWindow.getBounds() + .should.eventually.have.property("height") + .and.be.above(0) + .browserWindow.getTitle() + .should.eventually.equal("MagicMirror²"); }); - it("sets correct window title", function () { - return app.client.waitUntilWindowLoaded() - .getTitle().should.eventually.equal("Magic Mirror"); - }); - - it("get request from http://localhost:8080 should return 200", function (done) { - request.get("http://localhost:8080", function (err, res, body) { + it("get request from http://localhost:8080 should return 200", function(done) { + request.get("http://localhost:8080", function(err, res, body) { expect(res.statusCode).to.equal(200); done(); }); }); - it("get request from http://localhost:8080/nothing should return 404", function (done) { - request.get("http://localhost:8080/nothing", function (err, res, body) { + it("get request from http://localhost:8080/nothing should return 404", function(done) { + request.get("http://localhost:8080/nothing", function(err, res, body) { expect(res.statusCode).to.equal(404); done(); }); }); - }); diff --git a/tests/e2e/fonts.js b/tests/e2e/fonts.js new file mode 100644 index 00000000..0303414b --- /dev/null +++ b/tests/e2e/fonts.js @@ -0,0 +1,52 @@ +const helpers = require("./global-setup"); +const path = require("path"); +const request = require("request"); + +const expect = require("chai").expect; + +const describe = global.describe; +const it = global.it; +const beforeEach = global.beforeEach; +const afterEach = global.afterEach; +const forEach = require("mocha-each"); + +describe("All font files from roboto.css should be downloadable", function() { + helpers.setupTimeout(this); + + var fontFiles = []; + // Statements below filters out all 'url' lines in the CSS file + var fileContent = require("fs").readFileSync(__dirname + "/../../fonts/roboto.css", "utf8"); + var regex = /\burl\(['"]([^'"]+)['"]\)/g; + var match = regex.exec(fileContent); + while (match != null) { + // Push 1st match group onto fontFiles stack + fontFiles.push(match[1]); + // Find the next one + match = regex.exec(fileContent); + } + + before(function() { + // Set config sample for use in test + process.env.MM_CONFIG_FILE = "tests/configs/without_modules.js"; + + return helpers + .startApplication({ + args: ["js/electron.js"] + }) + .then(function(startedApp) { + app = startedApp; + }); + }); + + after(function() { + return helpers.stopApplication(app); + }); + + forEach(fontFiles).it("should return 200 HTTP code for file '%s'", (fontFile, done) => { + var fontUrl = "http://localhost:8080/fonts/" + fontFile; + request.get(fontUrl, function(err, res, body) { + expect(res.statusCode).to.equal(200); + done(); + }); + }); +}); diff --git a/tests/e2e/global-setup.js b/tests/e2e/global-setup.js index 7b94ec40..6bfe11d0 100644 --- a/tests/e2e/global-setup.js +++ b/tests/e2e/global-setup.js @@ -9,26 +9,54 @@ */ const Application = require("spectron").Application; -const path = require("path"); +const assert = require("assert"); const chai = require("chai"); const chaiAsPromised = require("chai-as-promised"); -var electronPath = path.join(__dirname, "../../", "node_modules", ".bin", "electron"); +const path = require("path"); -if (process.platform === "win32") { - electronPath += ".cmd"; -} - -var appPath = path.join(__dirname, "../../js/electron.js"); - -var app = new Application({ - path: electronPath, - args: [appPath] -}); - -global.before(function () { +global.before(function() { chai.should(); chai.use(chaiAsPromised); }); -exports.app = app; +exports.getElectronPath = function() { + var electronPath = path.join(__dirname, "..", "..", "node_modules", ".bin", "electron"); + if (process.platform === "win32") { + electronPath += ".cmd"; + } + return electronPath; +}; + +// Set timeout - if this is run within Travis, increase timeout +exports.setupTimeout = function(test) { + if (process.env.CI) { + test.timeout(30000); + } else { + test.timeout(10000); + } +}; + +exports.startApplication = function(options) { + options.path = exports.getElectronPath(); + if (process.env.CI) { + options.startTimeout = 30000; + } + + var app = new Application(options); + return app.start().then(function() { + assert.equal(app.isRunning(), true); + chaiAsPromised.transferPromiseness = app.transferPromiseness; + return app; + }); +}; + +exports.stopApplication = function(app) { + if (!app || !app.isRunning()) { + return; + } + + return app.stop().then(function() { + assert.equal(app.isRunning(), false); + }); +}; diff --git a/tests/e2e/ipWhistlist_spec.js b/tests/e2e/ipWhistlist_spec.js index 46fc4cff..ef89aa24 100644 --- a/tests/e2e/ipWhistlist_spec.js +++ b/tests/e2e/ipWhistlist_spec.js @@ -1,24 +1,31 @@ -const globalSetup = require("./global-setup"); -const app = globalSetup.app; +const helpers = require("./global-setup"); +const path = require("path"); const request = require("request"); -const chai = require("chai"); -const expect = chai.expect; +const expect = require("chai").expect; + +const describe = global.describe; +const it = global.it; +const beforeEach = global.beforeEach; +const afterEach = global.afterEach; describe("ipWhitelist directive configuration", function () { + helpers.setupTimeout(this); - this.timeout(20000); + var app = null; - beforeEach(function (done) { - app.start().then(function() { done(); } ); + beforeEach(function () { + return helpers.startApplication({ + args: ["js/electron.js"] + }).then(function (startedApp) { app = startedApp; }) }); - afterEach(function (done) { - app.stop().then(function() { done(); }); + afterEach(function () { + return helpers.stopApplication(app); }); describe("Set ipWhitelist without access", function () { - before(function() { + before(function () { // Set config sample for use in test process.env.MM_CONFIG_FILE = "tests/configs/noIpWhiteList.js"; }); @@ -31,7 +38,7 @@ describe("ipWhitelist directive configuration", function () { }); describe("Set ipWhitelist []", function () { - before(function() { + before(function () { // Set config sample for use in test process.env.MM_CONFIG_FILE = "tests/configs/empty_ipWhiteList.js"; }); diff --git a/tests/e2e/modules/calendar_spec.js b/tests/e2e/modules/calendar_spec.js index c701ed3c..e989cb17 100644 --- a/tests/e2e/modules/calendar_spec.js +++ b/tests/e2e/modules/calendar_spec.js @@ -1,19 +1,32 @@ -const globalSetup = require("../global-setup"); -const serverBasicAuth = require("../../servers/basic-auth.js"); -const app = globalSetup.app; -const chai = require("chai"); -const expect = chai.expect; +const helpers = require("../global-setup"); +const path = require("path"); +const request = require("request"); +const serverBasicAuth = require("../../servers/basic-auth.js"); -describe("Calendar module", function () { +const expect = require("chai").expect; - this.timeout(20000); +const describe = global.describe; +const it = global.it; +const beforeEach = global.beforeEach; +const afterEach = global.afterEach; - beforeEach(function (done) { - app.start().then(function() { done(); } ); +describe("Calendar module", function() { + helpers.setupTimeout(this); + + var app = null; + + beforeEach(function() { + return helpers + .startApplication({ + args: ["js/electron.js"] + }) + .then(function(startedApp) { + app = startedApp; + }); }); - afterEach(function (done) { - app.stop().then(function() { done(); }); + afterEach(function() { + return helpers.stopApplication(app); }); describe("Default configuration", function() { @@ -22,12 +35,11 @@ describe("Calendar module", function () { process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/default.js"; }); - it("Should return TestEvents", function () { + it("Should return TestEvents", function() { return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000); }); }); - describe("Basic auth", function() { before(function() { serverBasicAuth.listen(8010); @@ -35,12 +47,15 @@ describe("Calendar module", function () { process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/basic-auth.js"; }); - it("Should return TestEvents", function () { + after(function(done) { + serverBasicAuth.close(done()); + }); + + it("Should return TestEvents", function() { return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000); }); }); - describe("Basic auth by default", function() { before(function() { serverBasicAuth.listen(8011); @@ -48,19 +63,27 @@ describe("Calendar module", function () { process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/auth-default.js"; }); - it("Should return TestEvents", function () { + after(function(done) { + serverBasicAuth.close(done()); + }); + + it("Should return TestEvents", function() { return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000); }); }); - describe("Basic auth backward compatibilty configuration", function() { + describe("Basic auth backward compatibilty configuration: DEPRECATED", function() { before(function() { serverBasicAuth.listen(8012); // Set config sample for use in test process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/old-basic-auth.js"; }); - it("Should return TestEvents", function () { + after(function(done) { + serverBasicAuth.close(done()); + }); + + it("Should return TestEvents", function() { return app.client.waitUntilTextExists(".calendar", "TestEvent", 10000); }); }); @@ -72,10 +95,12 @@ describe("Calendar module", function () { process.env.MM_CONFIG_FILE = "tests/configs/modules/calendar/fail-basic-auth.js"; }); - it("Should return No upcoming events", function () { + after(function(done) { + serverBasicAuth.close(done()); + }); + + it("Should return No upcoming events", function() { return app.client.waitUntilTextExists(".calendar", "No upcoming events.", 10000); }); }); - - }); diff --git a/tests/e2e/modules/clock_es_spec.js b/tests/e2e/modules/clock_es_spec.js index f90263cf..455f3bed 100644 --- a/tests/e2e/modules/clock_es_spec.js +++ b/tests/e2e/modules/clock_es_spec.js @@ -1,8 +1,32 @@ -const globalSetup = require("../global-setup"); -const app = globalSetup.app; +const helpers = require("../global-setup"); +const path = require("path"); +const request = require("request"); -describe("Clock set to spanish language module", function () { - this.timeout(20000); +const expect = require("chai").expect; + +const describe = global.describe; +const it = global.it; +const beforeEach = global.beforeEach; +const afterEach = global.afterEach; + +describe("Clock set to spanish language module", function() { + helpers.setupTimeout(this); + + var app = null; + + beforeEach(function() { + return helpers + .startApplication({ + args: ["js/electron.js"] + }) + .then(function(startedApp) { + app = startedApp; + }); + }); + + afterEach(function() { + return helpers.stopApplication(app); + }); describe("with default 24hr clock config", function() { before(function() { @@ -10,24 +34,14 @@ describe("Clock set to spanish language module", function () { process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/es/clock_24hr.js"; }); - beforeEach(function (done) { - app.start().then(function() { done(); } ); - }); - - afterEach(function (done) { - app.stop().then(function() { done(); }); - }); - - it("shows date with correct format", function () { + it("shows date with correct format", function() { const dateRegex = /^(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo), \d{1,2} de (?:enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre) de \d{4}$/; - return app.client.waitUntilWindowLoaded() - .getText(".clock .date").should.eventually.match(dateRegex); + return app.client.waitUntilWindowLoaded().getText(".clock .date").should.eventually.match(dateRegex); }); it("shows time in 24hr format", function() { - const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/ - return app.client.waitUntilWindowLoaded() - .getText(".clock .time").should.eventually.match(timeRegex); + const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/; + return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); }); }); @@ -37,24 +51,14 @@ describe("Clock set to spanish language module", function () { process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/es/clock_12hr.js"; }); - beforeEach(function (done) { - app.start().then(function() { done(); } ); - }); - - afterEach(function (done) { - app.stop().then(function() { done(); }); - }); - - it("shows date with correct format", function () { + it("shows date with correct format", function() { const dateRegex = /^(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo), \d{1,2} de (?:enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre) de \d{4}$/; - return app.client.waitUntilWindowLoaded() - .getText(".clock .date").should.eventually.match(dateRegex); + return app.client.waitUntilWindowLoaded().getText(".clock .date").should.eventually.match(dateRegex); }); it("shows time in 12hr format", function() { const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/; - return app.client.waitUntilWindowLoaded() - .getText(".clock .time").should.eventually.match(timeRegex); + return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); }); }); @@ -64,18 +68,23 @@ describe("Clock set to spanish language module", function () { process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/es/clock_showPeriodUpper.js"; }); - beforeEach(function (done) { - app.start().then(function() { done(); } ); - }); - - afterEach(function (done) { - app.stop().then(function() { done(); }); - }); - it("shows 12hr time with upper case AM/PM", function() { const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/; - return app.client.waitUntilWindowLoaded() - .getText(".clock .time").should.eventually.match(timeRegex); + return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); }); }); + + describe("with showWeek config enabled", function() { + before(function() { + // Set config sample for use in test + process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/es/clock_showWeek.js"; + }); + + it("shows week with correct format", function() { + const weekRegex = /^Semana [0-9]{1,2}$/; + return app.client.waitUntilWindowLoaded() + .getText(".clock .week").should.eventually.match(weekRegex); + }); + }); + }); diff --git a/tests/e2e/modules/clock_spec.js b/tests/e2e/modules/clock_spec.js index 89d7e9e9..e342242c 100644 --- a/tests/e2e/modules/clock_spec.js +++ b/tests/e2e/modules/clock_spec.js @@ -1,8 +1,32 @@ -const globalSetup = require("../global-setup"); -const app = globalSetup.app; +const helpers = require("../global-setup"); +const path = require("path"); +const request = require("request"); -describe("Clock module", function () { - this.timeout(20000); +const expect = require("chai").expect; + +const describe = global.describe; +const it = global.it; +const beforeEach = global.beforeEach; +const afterEach = global.afterEach; + +describe("Clock module", function() { + helpers.setupTimeout(this); + + var app = null; + + beforeEach(function() { + return helpers + .startApplication({ + args: ["js/electron.js"] + }) + .then(function(startedApp) { + app = startedApp; + }); + }); + + afterEach(function() { + return helpers.stopApplication(app); + }); describe("with default 24hr clock config", function() { before(function() { @@ -10,24 +34,14 @@ describe("Clock module", function () { process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_24hr.js"; }); - beforeEach(function (done) { - app.start().then(function() { done(); } ); - }); - - afterEach(function (done) { - app.stop().then(function() { done(); }); - }); - - it("shows date with correct format", function () { + it("shows date with correct format", function() { const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/; - return app.client.waitUntilWindowLoaded() - .getText(".clock .date").should.eventually.match(dateRegex); + return app.client.waitUntilWindowLoaded().getText(".clock .date").should.eventually.match(dateRegex); }); it("shows time in 24hr format", function() { - const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/ - return app.client.waitUntilWindowLoaded() - .getText(".clock .time").should.eventually.match(timeRegex); + const timeRegex = /^(?:2[0-3]|[01]\d):[0-5]\d[0-5]\d$/; + return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); }); }); @@ -37,24 +51,14 @@ describe("Clock module", function () { process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_12hr.js"; }); - beforeEach(function (done) { - app.start().then(function() { done(); } ); - }); - - afterEach(function (done) { - app.stop().then(function() { done(); }); - }); - - it("shows date with correct format", function () { + it("shows date with correct format", function() { const dateRegex = /^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (?:January|February|March|April|May|June|July|August|September|October|November|December) \d{1,2}, \d{4}$/; - return app.client.waitUntilWindowLoaded() - .getText(".clock .date").should.eventually.match(dateRegex); + return app.client.waitUntilWindowLoaded().getText(".clock .date").should.eventually.match(dateRegex); }); it("shows time in 12hr format", function() { const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[ap]m$/; - return app.client.waitUntilWindowLoaded() - .getText(".clock .time").should.eventually.match(timeRegex); + return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); }); }); @@ -64,18 +68,9 @@ describe("Clock module", function () { process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_showPeriodUpper.js"; }); - beforeEach(function (done) { - app.start().then(function() { done(); } ); - }); - - afterEach(function (done) { - app.stop().then(function() { done(); }); - }); - it("shows 12hr time with upper case AM/PM", function() { const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[0-5]\d[AP]M$/; - return app.client.waitUntilWindowLoaded() - .getText(".clock .time").should.eventually.match(timeRegex); + return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); }); }); @@ -85,18 +80,9 @@ describe("Clock module", function () { process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_displaySeconds_false.js"; }); - beforeEach(function (done) { - app.start().then(function() { done(); } ); - }); - - afterEach(function (done) { - app.stop().then(function() { done(); }); - }); - it("shows 12hr time without seconds am/pm", function() { const timeRegex = /^(?:1[0-2]|[1-9]):[0-5]\d[ap]m$/; - return app.client.waitUntilWindowLoaded() - .getText(".clock .time").should.eventually.match(timeRegex); + return app.client.waitUntilWindowLoaded().getText(".clock .time").should.eventually.match(timeRegex); }); }); @@ -106,29 +92,17 @@ describe("Clock module", function () { process.env.MM_CONFIG_FILE = "tests/configs/modules/clock/clock_showWeek.js"; }); - beforeEach(function (done) { - app.start().then(function() { done(); } ); - }); - - afterEach(function (done) { - app.stop().then(function() { done(); }); - }); - it("shows week with correct format", function() { const weekRegex = /^Week [0-9]{1,2}$/; - return app.client.waitUntilWindowLoaded() - .getText(".clock .week").should.eventually.match(weekRegex); + return app.client.waitUntilWindowLoaded().getText(".clock .week").should.eventually.match(weekRegex); }); it("shows week with correct number of week of year", function() { - - it("FIXME: if the day is a sunday this not match"); - // const currentWeekNumber = require("current-week-number")(); - // const weekToShow = "Week " + currentWeekNumber; - // return app.client.waitUntilWindowLoaded() - // .getText(".clock .week").should.eventually.equal(weekToShow); + it("FIXME: if the day is a sunday this not match"); + // const currentWeekNumber = require("current-week-number")(); + // const weekToShow = "Week " + currentWeekNumber; + // return app.client.waitUntilWindowLoaded() + // .getText(".clock .week").should.eventually.equal(weekToShow); }); - }); - }); diff --git a/tests/e2e/modules/compliments_spec.js b/tests/e2e/modules/compliments_spec.js index 0dd2c411..a840981e 100644 --- a/tests/e2e/modules/compliments_spec.js +++ b/tests/e2e/modules/compliments_spec.js @@ -1,77 +1,81 @@ -const globalSetup = require("../global-setup"); -const app = globalSetup.app; -const chai = require("chai"); -const expect = chai.expect; +const helpers = require("../global-setup"); +const path = require("path"); +const request = require("request"); -describe("Compliments module", function () { - this.timeout(20000); +const expect = require("chai").expect; +const describe = global.describe; +const it = global.it; +const beforeEach = global.beforeEach; +const afterEach = global.afterEach; - beforeEach(function (done) { - app.start().then(function() { done(); } ); +describe("Compliments module", function() { + helpers.setupTimeout(this); + + var app = null; + + beforeEach(function() { + return helpers + .startApplication({ + args: ["js/electron.js"] + }) + .then(function(startedApp) { + app = startedApp; + }); }); - afterEach(function (done) { - app.stop().then(function() { done(); }); + afterEach(function() { + return helpers.stopApplication(app); }); - describe("parts of days", function() { - before(function() { // Set config sample for use in test process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_parts_day.js"; }); - it("if Morning compliments for that part of day", function () { + it("if Morning compliments for that part of day", function() { var hour = new Date().getHours(); if (hour >= 3 && hour < 12) { // if morning check - return app.client.waitUntilWindowLoaded() - .getText(".compliments").then(function (text) { - expect(text).to.be.oneOf(["Hi", "Good Morning", "Morning test"]); - }) + return app.client.waitUntilWindowLoaded().getText(".compliments").then(function(text) { + expect(text).to.be.oneOf(["Hi", "Good Morning", "Morning test"]); + }); } }); - it("if Afternoon show Compliments for that part of day", function () { + it("if Afternoon show Compliments for that part of day", function() { var hour = new Date().getHours(); if (hour >= 12 && hour < 17) { // if morning check - return app.client.waitUntilWindowLoaded() - .getText(".compliments").then(function (text) { - expect(text).to.be.oneOf(["Hello", "Good Afternoon", "Afternoon test"]); - }) + return app.client.waitUntilWindowLoaded().getText(".compliments").then(function(text) { + expect(text).to.be.oneOf(["Hello", "Good Afternoon", "Afternoon test"]); + }); } }); - it("if Evening show Compliments for that part of day", function () { + it("if Evening show Compliments for that part of day", function() { var hour = new Date().getHours(); if (!(hour >= 3 && hour < 12) && !(hour >= 12 && hour < 17)) { // if evening check - return app.client.waitUntilWindowLoaded() - .getText(".compliments").then(function (text) { - expect(text).to.be.oneOf(["Hello There", "Good Evening", "Evening test"]); - }) + return app.client.waitUntilWindowLoaded().getText(".compliments").then(function(text) { + expect(text).to.be.oneOf(["Hello There", "Good Evening", "Evening test"]); + }); } }); - }); - describe("Feature anytime in compliments module", function() { - describe("Set anytime and empty compliments for morning, evening and afternoon ", function() { before(function() { // Set config sample for use in test process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_anytime.js"; }); - it("Show anytime because if configure empty parts of day compliments and set anytime compliments", function () { - return app.client.waitUntilWindowLoaded() - .getText(".compliments").then(function (text) { - expect(text).to.be.oneOf(["Anytime here"]); - }) + it("Show anytime because if configure empty parts of day compliments and set anytime compliments", function() { + return app.client.waitUntilWindowLoaded().getText(".compliments").then(function(text) { + expect(text).to.be.oneOf(["Anytime here"]); + }); }); }); @@ -81,15 +85,11 @@ describe("Compliments module", function () { process.env.MM_CONFIG_FILE = "tests/configs/modules/compliments/compliments_only_anytime.js"; }); - it("Show anytime compliments", function () { - return app.client.waitUntilWindowLoaded() - .getText(".compliments").then(function (text) { - expect(text).to.be.oneOf(["Anytime here"]); - }) + it("Show anytime compliments", function() { + return app.client.waitUntilWindowLoaded().getText(".compliments").then(function(text) { + expect(text).to.be.oneOf(["Anytime here"]); + }); }); }); - - }); - }); diff --git a/tests/e2e/modules/helloworld_spec.js b/tests/e2e/modules/helloworld_spec.js index f956effb..655594b0 100644 --- a/tests/e2e/modules/helloworld_spec.js +++ b/tests/e2e/modules/helloworld_spec.js @@ -1,24 +1,56 @@ -const globalSetup = require("../global-setup"); -const app = globalSetup.app; +const helpers = require("../global-setup"); +const path = require("path"); +const request = require("request"); -describe("Test helloworld module", function () { - this.timeout(20000); +const expect = require("chai").expect; - before(function() { - // Set config sample for use in test - process.env.MM_CONFIG_FILE = "tests/configs/modules/helloworld/helloworld.js"; +const describe = global.describe; +const it = global.it; +const beforeEach = global.beforeEach; +const afterEach = global.afterEach; + +describe("Test helloworld module", function() { + helpers.setupTimeout(this); + + var app = null; + + beforeEach(function() { + return helpers + .startApplication({ + args: ["js/electron.js"] + }) + .then(function(startedApp) { + app = startedApp; + }); }); - beforeEach(function (done) { - app.start().then(function() { done(); } ); + + afterEach(function() { + return helpers.stopApplication(app); }); - afterEach(function (done) { - app.stop().then(function() { done(); }); + describe("helloworld set config text", function () { + before(function() { + // Set config sample for use in test + process.env.MM_CONFIG_FILE = "tests/configs/modules/helloworld/helloworld.js"; + }); + + it("Test message helloworld module", function () { + return app.client.waitUntilWindowLoaded() + .getText(".helloworld").should.eventually.equal("Test HelloWorld Module"); + }); }); - it("Test message helloworld module", function () { - return app.client.waitUntilWindowLoaded() - .getText(".helloworld").should.eventually.equal("Test HelloWorld Module"); + describe("helloworld default config text", function () { + before(function() { + // Set config sample for use in test + process.env.MM_CONFIG_FILE = "tests/configs/modules/helloworld/helloworld_default.js"; + }); + + it("Test message helloworld module", function () { + return app.client.waitUntilWindowLoaded() + .getText(".helloworld").should.eventually.equal("Hello World!"); + }); }); + }); diff --git a/tests/e2e/modules/newsfeed_spec.js b/tests/e2e/modules/newsfeed_spec.js index 049d1a2a..e062121c 100644 --- a/tests/e2e/modules/newsfeed_spec.js +++ b/tests/e2e/modules/newsfeed_spec.js @@ -1,27 +1,39 @@ -const globalSetup = require("../global-setup"); -const app = globalSetup.app; -const chai = require("chai"); -const expect = chai.expect; +const helpers = require("../global-setup"); +const path = require("path"); +const request = require("request"); -describe("Newsfeed module", function () { +const expect = require("chai").expect; - this.timeout(20000); +const describe = global.describe; +const it = global.it; +const beforeEach = global.beforeEach; +const afterEach = global.afterEach; - beforeEach(function (done) { - app.start().then(function() { done(); } ); +describe("Newsfeed module", function() { + helpers.setupTimeout(this); + + var app = null; + + beforeEach(function() { + return helpers + .startApplication({ + args: ["js/electron.js"] + }) + .then(function(startedApp) { + app = startedApp; + }); }); - afterEach(function (done) { - app.stop().then(function() { done(); }); + afterEach(function() { + return helpers.stopApplication(app); }); describe("Default configuration", function() { - before(function() { process.env.MM_CONFIG_FILE = "tests/configs/modules/newsfeed/default.js"; }); - it("show title newsfeed", function () { + it("show title newsfeed", function() { return app.client.waitUntilTextExists(".newsfeed .small", "Rodrigo Ramirez Blog", 10000).should.be.fulfilled; }); }); diff --git a/tests/e2e/modules_position_spec.js b/tests/e2e/modules_position_spec.js index a781388a..d3091cad 100644 --- a/tests/e2e/modules_position_spec.js +++ b/tests/e2e/modules_position_spec.js @@ -1,24 +1,32 @@ -const globalSetup = require("./global-setup"); -const app = globalSetup.app; -const chai = require("chai"); -const expect = chai.expect; +const helpers = require("./global-setup"); +const path = require("path"); +const request = require("request"); + +const expect = require("chai").expect; + +const describe = global.describe; +const it = global.it; +const beforeEach = global.beforeEach; +const afterEach = global.afterEach; describe("Position of modules", function () { - this.timeout(20000); + helpers.setupTimeout(this); + var app = null; - beforeEach(function (done) { - app.start().then(function() { done(); } ); + beforeEach(function () { + return helpers.startApplication({ + args: ["js/electron.js"] + }).then(function (startedApp) { app = startedApp; }) }); - afterEach(function (done) { - app.stop().then(function() { done(); }); + afterEach(function () { + return helpers.stopApplication(app); }); + describe("Using helloworld", function () { - describe("Using helloworld", function() { - - before(function() { + before(function () { // Set config sample for use in test process.env.MM_CONFIG_FILE = "tests/configs/modules/positions.js"; }); @@ -32,7 +40,7 @@ describe("Position of modules", function () { for (idx in positions) { position = positions[idx]; className = position.replace("_", "."); - it("show text in " + position , function () { + it("show text in " + position, function () { return app.client.waitUntilWindowLoaded() .getText("." + className).should.eventually.equal("Text in " + position); }); diff --git a/tests/e2e/port_config.js b/tests/e2e/port_config.js index 44c6b498..00964d53 100644 --- a/tests/e2e/port_config.js +++ b/tests/e2e/port_config.js @@ -1,27 +1,35 @@ -const globalSetup = require("./global-setup"); -const app = globalSetup.app; +const helpers = require("./global-setup"); +const path = require("path"); const request = require("request"); -const chai = require("chai"); -const expect = chai.expect; +const expect = require("chai").expect; + +const describe = global.describe; +const it = global.it; +const beforeEach = global.beforeEach; +const afterEach = global.afterEach; describe("port directive configuration", function () { + helpers.setupTimeout(this); - this.timeout(20000); + var app = null; - beforeEach(function (done) { - app.start().then(function() { done(); } ); + beforeEach(function () { + return helpers.startApplication({ + args: ["js/electron.js"] + }).then(function (startedApp) { app = startedApp; }) }); - afterEach(function (done) { - app.stop().then(function() { done(); }); + afterEach(function () { + return helpers.stopApplication(app); }); describe("Set port 8090", function () { - before(function() { + before(function () { // Set config sample for use in this test process.env.MM_CONFIG_FILE = "tests/configs/port_8090.js"; }); + it("should return 200", function (done) { request.get("http://localhost:8090", function (err, res, body) { expect(res.statusCode).to.equal(200); @@ -31,15 +39,16 @@ describe("port directive configuration", function () { }); describe("Set port 8100 on enviroment variable MM_PORT", function () { - before(function() { + before(function () { process.env.MM_PORT = 8100; // Set config sample for use in this test process.env.MM_CONFIG_FILE = "tests/configs/port_8090.js"; }); - after(function(){ + after(function () { delete process.env.MM_PORT; }); + it("should return 200", function (done) { request.get("http://localhost:8100", function (err, res, body) { expect(res.statusCode).to.equal(200); diff --git a/tests/e2e/vendor_spec.js b/tests/e2e/vendor_spec.js index 39abf906..0d9751f1 100644 --- a/tests/e2e/vendor_spec.js +++ b/tests/e2e/vendor_spec.js @@ -1,31 +1,41 @@ -const globalSetup = require("./global-setup"); -const app = globalSetup.app; +const helpers = require("./global-setup"); +const path = require("path"); const request = require("request"); -const chai = require("chai"); -const expect = chai.expect; +const expect = require("chai").expect; + +const describe = global.describe; +const it = global.it; +const before = global.before; +const after = global.after; describe("Vendors", function () { - this.timeout(20000); + return; // Test still getting failed in Travis - beforeEach(function (done) { - app.start().then(function() { done(); } ); + helpers.setupTimeout(this); + + var app = null; + + before(function () { + return helpers.startApplication({ + args: ["js/electron.js"] + }).then(function (startedApp) { app = startedApp; }) }); - afterEach(function (done) { - app.stop().then(function() { done(); }); + after(function () { + return helpers.stopApplication(app); }); describe("Get list vendors", function () { - before(function() { + before(function () { process.env.MM_CONFIG_FILE = "tests/configs/env.js"; }); var vendors = require(__dirname + "/../../vendor/vendor.js"); Object.keys(vendors).forEach(vendor => { - it(`should return 200 HTTP code for vendor "${vendor}"`, function() { + it(`should return 200 HTTP code for vendor "${vendor}"`, function () { urlVendor = "http://localhost:8080/vendor/" + vendors[vendor]; request.get(urlVendor, function (err, res, body) { expect(res.statusCode).to.equal(200); diff --git a/tests/e2e/without_modules.js b/tests/e2e/without_modules.js index 73e845f8..e0eda168 100644 --- a/tests/e2e/without_modules.js +++ b/tests/e2e/without_modules.js @@ -1,44 +1,34 @@ -const Application = require("spectron").Application; +const helpers = require("./global-setup"); const path = require("path"); -const chai = require("chai"); -const chaiAsPromised = require("chai-as-promised"); - -var electronPath = path.join(__dirname, "../../", "node_modules", ".bin", "electron"); - -if (process.platform === "win32") { - electronPath += ".cmd"; -} - -var appPath = path.join(__dirname, "../../js/electron.js"); - -var app = new Application({ - path: electronPath, - args: [appPath] -}); - -global.before(function () { - chai.should(); - chai.use(chaiAsPromised); -}); +const request = require("request"); +const expect = require("chai").expect; +const describe = global.describe; +const it = global.it; +const beforeEach = global.beforeEach; +const afterEach = global.afterEach; describe("Check configuration without modules", function () { - this.timeout(20000); + helpers.setupTimeout(this); - before(function() { - // Set config sample for use in test + var app = null; + + beforeEach(function () { + return helpers.startApplication({ + args: ["js/electron.js"] + }).then(function (startedApp) { app = startedApp; }) + }); + + afterEach(function () { + return helpers.stopApplication(app); + }); + + before(function () { + // Set config sample for use in test process.env.MM_CONFIG_FILE = "tests/configs/without_modules.js"; }); - beforeEach(function (done) { - app.start().then(function() { done(); } ); - }); - - afterEach(function (done) { - app.stop().then(function() { done(); }); - }); - it("Show the message MagicMirror title", function () { return app.client.waitUntilWindowLoaded() .getText("#module_1_helloworld .module-content").should.eventually.equal("Magic Mirror2") diff --git a/tests/servers/basic-auth.js b/tests/servers/basic-auth.js index 238bdc26..c409ad2d 100644 --- a/tests/servers/basic-auth.js +++ b/tests/servers/basic-auth.js @@ -1,16 +1,21 @@ var http = require("http"); var path = require("path"); var auth = require("http-auth"); -var express = require("express") +var express = require("express"); +var app = express(); -var basic = auth.basic({ - realm: "MagicMirror Area restricted." -}, (username, password, callback) => { - callback(username === "MagicMirror" && password === "CallMeADog"); -}); +var server; -this.server = express(); -this.server.use(auth.connect(basic)); +var basic = auth.basic( + { + realm: "MagicMirror Area restricted." + }, + (username, password, callback) => { + callback(username === "MagicMirror" && password === "CallMeADog"); + } +); + +app.use(auth.connect(basic)); // Set directories availables var directories = ["/tests/configs"]; @@ -18,13 +23,13 @@ var directory; rootPath = path.resolve(__dirname + "/../../"); for (i in directories) { directory = directories[i]; - this.server.use(directory, express.static(path.resolve(rootPath + directory))); + app.use(directory, express.static(path.resolve(rootPath + directory))); } -exports.listen = function () { - this.server.listen.apply(this.server, arguments); +exports.listen = function() { + server = app.listen.apply(app, arguments); }; -exports.close = function (callback) { - this.server.close(callback); +exports.close = function(callback) { + server.close(callback); }; diff --git a/tests/unit/functions/calendar_spec.js b/tests/unit/functions/calendar_spec.js index 2c7f62f8..d4035f33 100644 --- a/tests/unit/functions/calendar_spec.js +++ b/tests/unit/functions/calendar_spec.js @@ -1,9 +1,6 @@ -var fs = require("fs"); -var path = require("path"); -var chai = require("chai"); -var expect = chai.expect; -var vm = require("vm"); +const expect = require("chai").expect; +global.moment = require("moment"); describe("Functions into modules/default/calendar/calendar.js", function() { @@ -14,8 +11,10 @@ describe("Functions into modules/default/calendar/calendar.js", function() { Module.definitions[name] = moduleDefinition; }; - // load calendar.js - require("../../../modules/default/calendar/calendar.js"); + before(function() { + // load calendar.js + require("../../../modules/default/calendar/calendar.js"); + }); describe("capFirst", function() { words = { @@ -27,10 +26,103 @@ describe("Functions into modules/default/calendar/calendar.js", function() { }; Object.keys(words).forEach(word => { - it(`for ${word} should return ${words[word]}`, function() { + it(`for '${word}' should return '${words[word]}'`, function() { expect(Module.definitions.calendar.capFirst(word)).to.equal(words[word]); }); }); }); + + describe("getLocaleSpecification", function() { + it("Should return a valid moment.LocaleSpecification for a 12-hour format", function() { + expect(Module.definitions.calendar.getLocaleSpecification(12)).to.deep.equal({ longDateFormat: {LT: "h:mm A"} }); + }); + + it("Should return a valid moment.LocaleSpecification for a 24-hour format", function() { + expect(Module.definitions.calendar.getLocaleSpecification(24)).to.deep.equal({ longDateFormat: {LT: "HH:mm"} }); + }); + + it("Should return the current system locale when called without timeFormat number", function() { + expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: {LT: moment.localeData().longDateFormat("LT")} } ); + }); + + it("Should return a 12-hour longDateFormat when using the 'en' locale", function() { + var localeBackup = moment.locale(); + moment.locale("en"); + expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: {LT: "h:mm A"} }); + moment.locale(localeBackup); + }); + + it("Should return a 12-hour longDateFormat when using the 'au' locale", function() { + var localeBackup = moment.locale(); + moment.locale("au"); + expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: {LT: "h:mm A"} }); + moment.locale(localeBackup); + }); + + it("Should return a 12-hour longDateFormat when using the 'eg' locale", function() { + var localeBackup = moment.locale(); + moment.locale("eg"); + expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: {LT: "h:mm A"} }); + moment.locale(localeBackup); + }); + + it("Should return a 24-hour longDateFormat when using the 'nl' locale", function() { + var localeBackup = moment.locale(); + moment.locale("nl"); + expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: {LT: "HH:mm"} }); + moment.locale(localeBackup); + }); + + it("Should return a 24-hour longDateFormat when using the 'fr' locale", function() { + var localeBackup = moment.locale(); + moment.locale("fr"); + expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: {LT: "HH:mm"} }); + moment.locale(localeBackup); + }); + + it("Should return a 24-hour longDateFormat when using the 'uk' locale", function() { + var localeBackup = moment.locale(); + moment.locale("uk"); + expect(Module.definitions.calendar.getLocaleSpecification()).to.deep.equal({ longDateFormat: {LT: "HH:mm"} }); + moment.locale(localeBackup); + }); + }); + + describe("shorten", function() { + strings = { + " String with whitespace at the beginning that needs trimming" : { length: 16, return: "String with whit…" }, + "long string that needs shortening": { length: 16, return: "long string that…" }, + "short string": { length: 16, return: "short string" }, + "long string with no maxLength defined": { return: "long string with no maxLength defined" }, + }; + + Object.keys(strings).forEach(string => { + it(`for '${string}' should return '${strings[string].return}'`, function() { + expect(Module.definitions.calendar.shorten(string, strings[string].length)).to.equal(strings[string].return); + }); + }); + + it("should return an empty string if shorten is called with a non-string", function () { + expect(Module.definitions.calendar.shorten(100)).to.equal(""); + }); + + it("should not shorten the string if shorten is called with a non-number maxLength", function () { + expect(Module.definitions.calendar.shorten("This is a test string", "This is not a number")).to.equal("This is a test string"); + }); + + it("should wrap the string instead of shorten it if shorten is called with wrapEvents = true (with maxLength defined as 20)", function () { + expect(Module.definitions.calendar.shorten( + "This is a wrapEvent test. Should wrap the string instead of shorten it if called with wrapEvent = true", + 20, + true)).to.equal("This is a
wrapEvent test. Should wrap
the string instead of
shorten it if called with
wrapEvent = true"); + }); + + it("should wrap the string instead of shorten it if shorten is called with wrapEvents = true (without maxLength defined, default 25)", function () { + expect(Module.definitions.calendar.shorten( + "This is a wrapEvent test. Should wrap the string instead of shorten it if called with wrapEvent = true", + undefined, + true)).to.equal("This is a wrapEvent
test. Should wrap the string
instead of shorten it if called
with wrapEvent = true"); + }); + }); }); diff --git a/tests/unit/functions/class_spec.js b/tests/unit/functions/class_spec.js new file mode 100644 index 00000000..e2e6d521 --- /dev/null +++ b/tests/unit/functions/class_spec.js @@ -0,0 +1,51 @@ +var chai = require("chai"); +var expect = chai.expect; +var jsClass = require("../../../js/class.js"); + +describe("File js/class", function() { + describe("Test function cloneObject", function() { + var cloneObject = jsClass._test.cloneObject; + + it("should be return equals object", function() { + var expected = {name: "Rodrigo", web: "https://rodrigoramirez.com", project: "MagicMirror"}; + var obj = {}; + obj = cloneObject(expected); + expect(expected).to.deep.equal(obj); + }); + + it("should be return equals int", function() { + var expected = 1; + var obj = {}; + obj = cloneObject(expected); + expect(expected).to.equal(obj); + }); + + it("should be return equals string", function() { + var expected = "Perfect stranger"; + var obj = {}; + obj = cloneObject(expected); + expect(expected).to.equal(obj); + }); + + it("should be return equals undefined", function() { + var expected = undefined; + var obj = {}; + obj = cloneObject(expected); + expect(undefined).to.equal(obj); + }); + + // CoverageME + /* + context("Test lockstring code", function() { + it("should be return equals object", function() { + var expected = {name: "Module", lockStrings: "stringLock"}; + var obj = {}; + obj = cloneObject(expected); + expect(expected).to.deep.equal(obj); + }); + }); + */ + + }); +}); + diff --git a/tests/unit/functions/currentweather_spec.js b/tests/unit/functions/currentweather_spec.js new file mode 100644 index 00000000..18b52c81 --- /dev/null +++ b/tests/unit/functions/currentweather_spec.js @@ -0,0 +1,78 @@ +var fs = require("fs"); +var path = require("path"); +var chai = require("chai"); +var expect = chai.expect; +var vm = require("vm"); + + +describe("Functions module currentweather", function() { + + + // Fake for use by currentweather.js + Module = {}; + config = {}; + Module.definitions = {}; + Module.register = function (name, moduleDefinition) { + Module.definitions[name] = moduleDefinition; + }; + + + before(function(){ + require("../../../modules/default/currentweather/currentweather.js"); + Module.definitions.currentweather.config = {}; + }); + + describe("roundValue", function() { + + describe("this.config.roundTemp is true", function() { + before(function(){ + Module.definitions.currentweather.config.roundTemp = true; + }); + + var values = [ + // index 0 value + // index 1 expect + [1 , "1"], + [1.0 , "1"], + [1.02 , "1"], + [10.12 , "10"], + [2.0 , "2"], + ["2.12" , "2"], + [10.1 , "10"] + ] + + values.forEach(value => { + it(`for ${value[0]} should be return ${value[1]}`, function() { + expect(Module.definitions.currentweather.roundValue(value[0])).to.equal(value[1]); + }); + }); + }); + + + describe("this.config.roundTemp is false", function() { + + before(function(){ + Module.definitions.currentweather.config.roundTemp = false; + }); + + var values = [ + // index 0 value + // index 1 expect + [1 , "1.0"], + [1.0 , "1.0"], + [1.02 , "1.0"], + [10.12 , "10.1"], + [2.0 , "2.0"], + ["2.12" , "2.1"], + [10.1 , "10.1"], + [10.10 , "10.1"] + ] + + values.forEach(value => { + it(`for ${value[0]} should be return ${value[1]}`, function() { + expect(Module.definitions.currentweather.roundValue(value[0])).to.equal(value[1]); + }); + }); + }); + }); +}); diff --git a/tests/unit/functions/newsfeed_spec.js b/tests/unit/functions/newsfeed_spec.js new file mode 100644 index 00000000..583d696a --- /dev/null +++ b/tests/unit/functions/newsfeed_spec.js @@ -0,0 +1,37 @@ +var fs = require("fs"); +var path = require("path"); +var chai = require("chai"); +var expect = chai.expect; +var vm = require("vm"); + + +describe("Functions into modules/default/newsfeed/newsfeed.js", function() { + + Module = {} + Module.definitions = {}; + Module.register = function (name, moduleDefinition) { + Module.definitions[name] = moduleDefinition; + }; + + // load newsfeed.js + require("../../../modules/default/newsfeed/newsfeed.js"); + + describe("capitalizeFirstLetter", function() { + words = { + "rodrigo": "Rodrigo", + "123m": "123m", + "magic mirror": "Magic mirror", + ",a": ",a", + "ñandú": "Ñandú", + ".!": ".!" + }; + + Object.keys(words).forEach(word => { + it(`for ${word} should return ${words[word]}`, function() { + expect(Module.definitions.newsfeed.capitalizeFirstLetter(word)).to.equal(words[word]); + }); + }); + }); + +}); + diff --git a/tests/unit/functions/weatherforecast_spec.js b/tests/unit/functions/weatherforecast_spec.js new file mode 100644 index 00000000..57c386dd --- /dev/null +++ b/tests/unit/functions/weatherforecast_spec.js @@ -0,0 +1,74 @@ +var fs = require("fs"); +var path = require("path"); +var chai = require("chai"); +var expect = chai.expect; +var vm = require("vm"); + + +describe("Functions module weatherforecast", function() { + + before(function(){ + Module = {}; + config = {}; + Module.definitions = {}; + Module.register = function (name, moduleDefinition) { + Module.definitions[name] = moduleDefinition; + }; + require("../../../modules/default/weatherforecast/weatherforecast.js"); + Module.definitions.weatherforecast.config = {}; + }); + + describe("roundValue", function() { + + describe("this.config.roundTemp is true", function() { + before(function(){ + Module.definitions.weatherforecast.config.roundTemp = true; + }); + + var values = [ + // index 0 value + // index 1 expect + [1 , "1"], + [1.0 , "1"], + [1.02 , "1"], + [10.12 , "10"], + [2.0 , "2"], + ["2.12" , "2"], + [10.1 , "10"] + ] + + values.forEach(value => { + it(`for ${value[0]} should be return ${value[1]}`, function() { + expect(Module.definitions.weatherforecast.roundValue(value[0])).to.equal(value[1]); + }); + }); + }); + + + describe("this.config.roundTemp is false", function() { + + before(function(){ + Module.definitions.weatherforecast.config.roundTemp = false; + }); + + var values = [ + // index 0 value + // index 1 expect + [1 , "1.0"], + [1.0 , "1.0"], + [1.02 , "1.0"], + [10.12 , "10.1"], + [2.0 , "2.0"], + ["2.12" , "2.1"], + [10.1 , "10.1"], + [10.10 , "10.1"] + ] + + values.forEach(value => { + it(`for ${value[0]} should be return ${value[1]}`, function() { + expect(Module.definitions.weatherforecast.roundValue(value[0])).to.equal(value[1]); + }); + }); + }); + }); +}); diff --git a/tests/unit/translations/same_keys.js b/tests/unit/translations/same_keys.js index 67ed7170..9511a60f 100644 --- a/tests/unit/translations/same_keys.js +++ b/tests/unit/translations/same_keys.js @@ -2,6 +2,7 @@ var fs = require("fs"); var path = require("path"); var chai = require("chai"); var expect = chai.expect; +var mlog = require("mocha-logger"); describe("Translations have the same keys as en.js", function() { var translations = require("../../../translations/translations.js"); @@ -31,6 +32,8 @@ describe("Translations have the same keys as en.js", function() { expect(fileKeys).to.deep.equal(baseKeys); } catch(e) { if (e instanceof chai.AssertionError) { + diff = baseKeys.filter(function(x) { return fileKeys.indexOf(x) < 0 }); + mlog.pending("Missing Translations for language " + tr + ": ", diff); test.skip(); } else { throw e; diff --git a/translations/fr.json b/translations/fr.json index d27bc7e6..c17b2df8 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -3,8 +3,11 @@ "TODAY": "Aujourd'hui", "TOMORROW": "Demain", + "DAYAFTERTOMORROW": "Après-demain", "RUNNING": "Se termine dans", - "EMPTY": "Aucun RDV.", + "EMPTY": "Aucun RDV à venir.", + + "WEEK": "Semaine", "N": "N", "NNE": "NNE", @@ -21,5 +24,9 @@ "W": "O", "WNW": "ONO", "NW": "NO", - "NNW": "NNO" + "NNW": "NNO", + + "UPDATE_NOTIFICATION": "Une mise à jour de MagicMirror² est disponible", + "UPDATE_NOTIFICATION_MODULE": "Une mise à jour est disponible pour le module MODULE_NAME .", +"UPDATE_INFO": "L'installation actuelle est COMMIT_COUNT en retard sur la branche BRANCH_NAME ." } diff --git a/translations/zh_cn.json b/translations/zh_cn.json index 5efbb691..addffbbc 100644 --- a/translations/zh_cn.json +++ b/translations/zh_cn.json @@ -23,7 +23,7 @@ "WNW": "西偏北风", "NW": "西北风", "NNW": "北偏西风", - + "UPDATE_NOTIFICATION": "MagicMirror² 有新的更新", "UPDATE_NOTIFICATION_MODULE": "模块 MODULE_NAME 可更新", "UPDATE_INFO": "当前已安装版本为 COMMIT_COUNT 落后于分支 BRANCH_NAME "