diff --git a/CHANGELOG.md b/CHANGELOG.md index a7625d52..1a68603f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Implement Danger.js to notify contributors when CHANGELOG.md is missing in PR. - Allow to scroll in full page article view of default newsfeed module with gesture events from [MMM-Gestures](https://github.com/thobach/MMM-Gestures) - Changed 'compliments.js' - update DOM if remote compliments are loaded instead of waiting one updateInterval to show custom compliments +- Automated unit tests utils, deprecated, translator, cloneObject(lockstrings) +- Automated integration tests translations +- Add advanced filtering to the excludedEvents configuration of the default calendar module +- New currentweather module config option: `showFeelsLike`: Shows how it actually feels like. (wind chill or heat index) +- New currentweather module config option: `useKMPHwind`: adds an option to see wind speed in Kmph instead of just m/s or Beaufort. +- Add dc:date to parsing in newsfeed module, which allows parsing of more rss feeds. + +### Changed +- Add link to GitHub repository which contains the respective Dockerfile. +- Optimized automated unit tests cloneObject, cmpVersions +- Update notifications use now translation templates instead of normal strings. +- Yarn can be used now as an installation tool ### Fixed - News article in fullscreen (iframe) is now shown in front of modules. diff --git a/README.md b/README.md index ace701ba..b795ab97 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,11 @@ MagicMirror² focuses on a modular plugin system and uses [Electron](http://elec ### Raspberry Pi +#### Automatic Installation (Raspberry Pi only!) + *Electron*, the app wrapper around MagicMirror², only supports the Raspberry Pi 2/3. The Raspberry Pi 0/1 is currently **not** supported. If you want to run this on a Raspberry Pi 1, use the [server only](#server-only) feature and setup a fullscreen browser yourself. (Yes, people have managed to run MM² also on a Pi0, so if you insist, search in the forums.) -#### Automatic Installation (Raspberry Pi only!) +Note that you will need to install the lastest full version of Raspbian, **don't use the Lite version**. Execute the following command on your Raspberry Pi to install MagicMirror²: @@ -48,24 +50,24 @@ bash -c "$(curl -sL https://raw.githubusercontent.com/MichMich/MagicMirror/maste 1. Download and install the latest *Node.js* version. 2. Clone the repository and check out the master branch: `git clone https://github.com/MichMich/MagicMirror` -3. Enter the repository: `cd ~/MagicMirror` +3. Enter the repository: `cd MagicMirror/` 4. Install and run the app with: `npm install && npm start` \ For **Server Only** use: `npm install && node serveronly` . -**:warning: Important!** +**:warning: Important!** - **The installation step for `npm install` will take a very long time**, often with little or no terminal response! \ For the RPi3 this is **~10** minutes and for the Rpi2 **~25** minutes. \ Do not interrupt or you risk getting a :broken_heart: by Raspberry Jam. -Also note that: +Also note that: - `npm start` does **not** work via SSH. But you can use `DISPLAY=:0 nohup npm start &` instead. \ This starts the mirror on the remote display. - If you want to debug on Raspberry Pi you can use `npm start dev` which will start MM with *Dev Tools* enabled. -- To access toolbar menu when in mirror mode, hit `ALT` key. +- To access toolbar menu when in mirror mode, hit `ALT` key. - To toggle the (web) `Developer Tools` from mirror mode, use `CTRL-SHIFT-I` or `ALT` and select `View`. @@ -73,12 +75,12 @@ Also note that: 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. -**Important:** Make sure that you whitelist the interface/ip (`ipWhitelist`) in the server config where you want the client to connect to, otherwise it will not be allowed to connect to the server. You also need to set the local host `address` field to `0.0.0.0` in order for the RPi to listen on all interfaces and not only `localhost` (default). +**Important:** Make sure that you whitelist the interface/ip (`ipWhitelist`) in the server config where you want the client to connect to, otherwise it will not be allowed to connect to the server. You also need to set the local host `address` field to `0.0.0.0` in order for the RPi to listen on all interfaces and not only `localhost` (default). ```javascript var config = { address: "0.0.0.0", // default is "localhost" - port: 8080, // default + port: 8080, // default ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:172.17.0.1"], // default -- need to add your IP here ... }; @@ -96,39 +98,31 @@ MagicMirror² in server only mode can be deployed using [Docker](https://docker. ```bash docker run -d \ - --publish 80:8080 \ - --restart always \ - --volume ~/magic_mirror/config:/opt/magic_mirror/config \ - --volume ~/magic_mirror/modules:/opt/magic_mirror/modules \ - --name magic_mirror \ - bastilimbach/docker-magicmirror + --publish 80:8080 \ + --restart always \ + --volume ~/magic_mirror/config:/opt/magic_mirror/config \ + --volume ~/magic_mirror/modules:/opt/magic_mirror/modules \ + --name magic_mirror \ + bastilimbach/docker-magicmirror ``` - -| **Volumes** | **Description** | -| --- | --- | -| `/opt/magic_mirror/config` | Mount this volume to insert your own config into the docker container. | -| `/opt/magic_mirror/modules` | Mount this volume to add your own custom modules into the docker container. | - -You may need to add your Docker Host IP to your `ipWhitelist` option. If you have some issues setting up this configuration, check [this forum post](https://forum.magicmirror.builders/topic/1326/ipwhitelist-howto). - -If you want to run the server on a raspberry pi, use the `raspberry` tag. (bastilimbach/docker-magicmirror:raspberry) +To get more information about the available Dockerfile versions and configurations head over to the respective [GitHub repository](https://github.com/bastilimbach/docker-MagicMirror). ## Configuration -### Raspberry Specific +### Raspberry Specific The following wiki links are helpful for the initial configuration of your MagicMirror² operating system: - [Configuring the Raspberry Pi](https://github.com/MichMich/MagicMirror/wiki/Configuring-the-Raspberry-Pi) - [Auto Starting MagicMirror](https://github.com/MichMich/MagicMirror/wiki/Auto-Starting-MagicMirror) -### General +### General 1. Copy `config/config.js.sample` to `config/config.js`. \ **Note:** If you used the installer script. This step is already done for you. -2. Modify your required settings. \ +2. Modify your required settings. \ Note: You'll can check your configuration running `npm run config:check`. @@ -185,10 +179,6 @@ git pull && npm install If you changed nothing more than the config or the modules, this should work without any problems. Type `git status` to see your changes, if there are any, you can reset them with `git reset --hard`. After that, git pull should be possible. -## Known issues - -- Electron seems to have some issues on certain Raspberry Pi 2's. See [#145](https://github.com/MichMich/MagicMirror/issues/145). -- MagicMirror² (Electron) sometimes quits without an error after an extended period of use. See [#150](https://github.com/MichMich/MagicMirror/issues/150). ## Community diff --git a/installers/raspberry.sh b/installers/raspberry.sh index ecdcc842..4e9c20d8 100644 --- a/installers/raspberry.sh +++ b/installers/raspberry.sh @@ -42,7 +42,7 @@ sudo apt-get update || echo -e "\e[91mUpdate failed, carrying on installation .. # Installing helper tools echo -e "\e[96mInstalling helper tools ...\e[90m" -sudo apt-get install curl wget git build-essential unzip || exit +sudo apt-get --assume-yes install curl wget git build-essential unzip || exit # Check if we need to install or upgrade Node.js. echo -e "\e[96mCheck current Node installation ...\e[0m" @@ -82,7 +82,7 @@ if $NODE_INSTALL; then # The NODE_STABLE_BRANCH variable will need to be manually adjusted when a new branch is released. (e.g. 7.x) # Only tested (stable) versions are recommended as newer versions could break MagicMirror. - NODE_STABLE_BRANCH="6.x" + NODE_STABLE_BRANCH="9.x" curl -sL https://deb.nodesource.com/setup_$NODE_STABLE_BRANCH | sudo -E bash - sudo apt-get install -y nodejs echo -e "\e[92mNode.js installation Done!\e[0m" @@ -101,7 +101,7 @@ if [ -d "$HOME/MagicMirror" ] ; then fi echo -e "\e[96mCloning MagicMirror ...\e[90m" -if git clone https://github.com/MichMich/MagicMirror.git; then +if git clone --depth=1 https://github.com/MichMich/MagicMirror.git; then echo -e "\e[92mCloning MagicMirror Done!\e[0m" else echo -e "\e[91mUnable to clone MagicMirror." @@ -149,7 +149,7 @@ else fi # Use pm2 control like a service MagicMirror -read -p "Do you want use pm2 for auto starting of your MagicMirror (y/n)?" choice +read -p "Do you want use pm2 for auto starting of your MagicMirror (y/N)?" choice if [[ $choice =~ ^[Yy]$ ]]; then sudo npm install -g pm2 sudo su -c "env PATH=$PATH:/usr/bin pm2 startup linux -u pi --hp /home/pi" diff --git a/js/class.js b/js/class.js index ec75f6f2..ceccc6f1 100644 --- a/js/class.js +++ b/js/class.js @@ -92,7 +92,4 @@ function cloneObject(obj) { /*************** DO NOT EDIT THE LINE BELOW ***************/ if (typeof module !== "undefined") { module.exports = Class; - module.exports._test = { - cloneObject: cloneObject - } } diff --git a/js/module.js b/js/module.js index 57bdde4f..8eb6dca9 100644 --- a/js/module.js +++ b/js/module.js @@ -476,11 +476,3 @@ Module.register = function (name, moduleDefinition) { Log.log("Module registered: " + name); Module.definitions[name] = moduleDefinition; }; - -if (typeof exports != "undefined") { // For testing purpose only - // A good a idea move the function cmpversions a helper file. - // It's used into other side. - exports._test = { - cmpVersions: cmpVersions - } -} diff --git a/js/translator.js b/js/translator.js index b82a739e..34bbb717 100644 --- a/js/translator.js +++ b/js/translator.js @@ -156,11 +156,12 @@ var Translator = (function() { return key; }, - /* load(module, file, callback) + /* load(module, file, isFallback, callback) * Load a translation file (json) and remember the data. * * argument module Module - The module to load the translation file for. * argument file string - Path of the file we want to load. + * argument isFallback boolean - Flag to indicate fallback translations. * argument callback function - Function called when done. */ load: function(module, file, isFallback, callback) { @@ -216,10 +217,12 @@ var Translator = (function() { // defined translation after the following line. for (var first in translations) {break;} - Log.log("Loading core translation fallback file: " + translations[first]); - loadJSON(translations[first], function(translations) { - self.coreTranslationsFallback = translations; - }); + if (first) { + Log.log("Loading core translation fallback file: " + translations[first]); + loadJSON(translations[first], function(translations) { + self.coreTranslationsFallback = translations; + }); + } }, }; })(); diff --git a/modules/README.md b/modules/README.md index 56a87812..8bc8f86e 100644 --- a/modules/README.md +++ b/modules/README.md @@ -2,6 +2,30 @@ This document describes the way to develop your own MagicMirror² modules. +Table of Contents: + +- Module structure + - Files + +- The Core module file: modulename.js + - Available module instance properties + - Subclassable module methods + - Module instance methods + - Visibility locking + +- The Node Helper: node_helper.js + - Available module instance properties + - Subclassable module methods + - Module instance methods + +- MagicMirror Helper Methods + - Module Selection + +- MagicMirror Logger + +--- + + ## General Advice As MagicMirror has gained huge popularity, so has the number of available modules. For new users and developers alike, it is very time consuming to navigate around the various repositories in order to find out what exactly a certain modules does, how it looks and what it depends on. Unfortunately, this information is rarely available, nor easily obtained without having to install it first. @@ -26,7 +50,7 @@ A module can be placed in one single folder. Or multiple modules can be grouped - **modulename/public** - Any files in this folder can be accesed via the browser on `/modulename/filename.ext`. - **modulename/anyfileorfolder** Any other file or folder in the module folder can be used by the core module script. For example: *modulename/css/modulename.css* would be a good path for your additional module styles. -## Core module file: modulename.js +## The Core module file: modulename.js This is the script in which the module will be defined. This script is required in order for the module to be used. In it's most simple form, the core module file must contain: ````javascript Module.register("modulename",{}); @@ -56,30 +80,16 @@ As you can see, the `Module.register()` method takes two arguments: the name of ### Available module instance properties After the module is initialized, the module instance has a few available module properties: -#### `this.name` -**String** +| Instance Property | Type | Description | +|:----------------- |:---- |:----------- | +| `this.name` | String | The name of the module. | +| `this.identifier` | String | This is a unique identifier for the module instance. | +| `this.hidden` | Boolean | This represents if the module is currently hidden (faded away). | +| `this.config` | Boolean | The configuration of the module instance as set in the user's `config.js` file. This config will also contain the module's defaults if these properties are not over-written by the user config. | +| `this.data` | Object | The data object contain additional metadata about the module instance. (See below) | -The name of the module. -#### `this.identifier` -**String** - -This is a unique identifier for the module instance. - -#### `this.hidden` -**Boolean** - -This represents if the module is currently hidden (faded away). - -#### `this.config` -**Boolean** - -The configuration of the module instance as set in the user's config.js file. This config will also contain the module's defaults if these properties are not over written by the user config. - -#### `this.data` -**Object** - -The data object contains additional metadata about the module instance: +The `this.data` data object contain the follwoing metadata: - `data.classes` - The classes which are added to the module dom wrapper. - `data.file` - The filename of the core module file. - `data.path` - The path of the module folder. diff --git a/modules/default/.DS_Store b/modules/default/.DS_Store deleted file mode 100644 index 5e5e9ca3..00000000 Binary files a/modules/default/.DS_Store and /dev/null differ diff --git a/modules/default/alert/README.md b/modules/default/alert/README.md index 3abf2fcd..c57ba793 100644 --- a/modules/default/alert/README.md +++ b/modules/default/alert/README.md @@ -60,5 +60,5 @@ self.sendNotification("SHOW_ALERT", {}); | `timer` (optional) | How long the alert should stay visible in ms.
**Important:** If you do not use the `timer`, it is your duty to hide the alert by using `self.sendNotification("HIDE_ALERT");`!

**Possible values:** `int` `float`
**Default value:** `none` ## Open Source Licenses -###[NotificationStyles](https://github.com/codrops/NotificationStyles) +### [NotificationStyles](https://github.com/codrops/NotificationStyles) See [ympanus.net](http://tympanus.net/codrops/licensing/) for license. diff --git a/modules/default/calendar/README.md b/modules/default/calendar/README.md index 8ce9608b..2481b77b 100644 --- a/modules/default/calendar/README.md +++ b/modules/default/calendar/README.md @@ -46,7 +46,7 @@ The following properties can be configured: | `urgency` | When using a timeFormat of `absolute`, the `urgency` setting allows you to display events within a specific time frame as `relative`. This allows events within a certain time frame to be displayed as relative (in xx days) while others are displayed as absolute dates

**Possible values:** a positive integer representing the number of days for which you want a relative date, for example `7` (for 7 days)

**Default value:** `7` | `broadcastEvents` | If this property is set to true, the calendar will broadcast all the events to all other modules with the notification message: `CALENDAR_EVENTS`. The event objects are stored in an array and contain the following fields: `title`, `startDate`, `endDate`, `fullDayEvent`, `location` and `geo`.

**Possible values:** `true`, `false`

**Default value:** `true` | `hidePrivate` | Hides private calendar events.

**Possible values:** `true` or `false`
**Default value:** `false` -| `excludedEvents` | An array of words / phrases from event titles that will be excluded from being shown.

**Example:** `['Birthday', 'Hide This Event']`
**Default value:** `[]` +| `excludedEvents` | An array of words / phrases from event titles that will be excluded from being shown.

Additionally advanced filter objects can be passed in. Below is the configuration for the advance filtering object.
**Required**
`filterBy` - string used to determine if filter is applied.
**Optional**
`until` - Time before an event to display it Ex: [`'3 days'`, `'2 months'`, `'1 week'`]
`caseSensitive` - By default, excludedEvents are case insensitive, set this to true to enforce case sensitivity

**Example:** `['Birthday', 'Hide This Event', {filterBy: 'Payment', until: '6 days', caseSensitive: true}]`
**Default value:** `[]` ### Calendar configuration diff --git a/modules/default/calendar/calendarfetcher.js b/modules/default/calendar/calendarfetcher.js index 12495f78..911eaba4 100644 --- a/modules/default/calendar/calendarfetcher.js +++ b/modules/default/calendar/calendarfetcher.js @@ -113,11 +113,38 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri title = event.description; } - var excluded = false; + var excluded = false, + dateFilter = null; + for (var f in excludedEvents) { - var filter = excludedEvents[f]; - if (title.toLowerCase().includes(filter.toLowerCase())) { - excluded = true; + var filter = excludedEvents[f], + testTitle = title.toLowerCase(), + until = null; + + if (filter instanceof Object) { + if (typeof filter.until !== "undefined") { + until = filter.until; + } + + // If additional advanced filtering is added in, this section + // must remain last as we overwrite the filter object with the + // filterBy string + if (filter.caseSensitive) { + filter = filter.filterBy; + testTitle = title; + } else { + filter = filter.filterBy.toLowerCase(); + } + } else { + filter = filter.toLowerCase(); + } + + if (testTitle.includes(filter)) { + if (until) { + dateFilter = until; + } else { + excluded = true; + } break; } } @@ -137,6 +164,11 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri for (var d in dates) { startDate = moment(new Date(dates[d])); endDate = moment(parseInt(startDate.format("x")) + duration, "x"); + + if (timeFilterApplies(now, endDate, dateFilter)) { + continue; + } + if (endDate.format("x") > now) { newEvents.push({ title: title, @@ -171,6 +203,10 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri continue; } + if (timeFilterApplies(now, endDate, dateFilter)) { + continue; + } + // Every thing is good. Add it to the list. newEvents.push({ @@ -236,6 +272,28 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri return false; }; + /* timeFilterApplies() + * Determines if the user defined time filter should apply + * + * argument now Date - Date object using previously created object for consistency + * argument endDate Moment - Moment object representing the event end date + * argument filter string - The time to subtract from the end date to determine if an event should be shown + * + * return bool - The event should be filtered out + */ + var timeFilterApplies = function(now, endDate, filter) { + if (filter) { + var until = filter.split(" "), + value = parseInt(until[0]), + increment = until[1].slice("-1") === "s" ? until[1] : until[1] + "s", // Massage the data for moment js + filterUntil = moment(endDate.format()).subtract(value, increment); + + return now < filterUntil.format("x"); + } + + return false; + }; + /* public methods */ /* startFetch() diff --git a/modules/default/currentweather/README.md b/modules/default/currentweather/README.md index 304eba1e..030b04bf 100644 --- a/modules/default/currentweather/README.md +++ b/modules/default/currentweather/README.md @@ -43,7 +43,9 @@ The following properties can be configured: | `showWindDirectionAsArrow` | Show the wind direction as an arrow instead of abbreviation

**Possible values:** `true` or `false`
**Default value:** `false` | `showHumidity` | Show the current humidity

**Possible values:** `true` or `false`
**Default value:** `false` | `showIndoorTemperature` | If you have another module that emits the INDOOR_TEMPERATURE notification, the indoor temperature will be displayed
**Default value:** `false` -| `onlyTemp` | Show only current Temperature and weather icon without windspeed, sunset and sunrise time.

**Possible values:** `true` or `false`
**Default value:** `false` +| `onlyTemp` | Show only current Temperature and weather icon without windspeed, sunset, sunrise time and feels like.

**Possible values:** `true` or `false`
**Default value:** `false` +| `showFeelsLike` | Shows the Feels like temperature weather.

**Possible values:**`true` or `false`
**Default value:** `true` +| `useKMPHWind` | Uses KMPH as units for windspeed.

**Possible values:**`true` or `false`
**Default value:** `false` | `useBeaufort` | Pick between using the Beaufort scale for wind speed or using the default units.

**Possible values:** `true` or `false`
**Default value:** `true` | `lang` | The language of the days.

**Possible values:** `en`, `nl`, `ru`, etc ...
**Default value:** uses value of _config.language_ | `decimalSymbol` | The decimal symbol to use.

**Possible values:** `.`, `,` or any other symbol.
**Default value:** `.` diff --git a/modules/default/currentweather/currentweather.js b/modules/default/currentweather/currentweather.js index 8cd3a292..442e8632 100644 --- a/modules/default/currentweather/currentweather.js +++ b/modules/default/currentweather/currentweather.js @@ -23,12 +23,14 @@ Module.register("currentweather",{ showWindDirection: true, showWindDirectionAsArrow: false, useBeaufort: true, + useKMPHwind: false, lang: config.language, decimalSymbol: ".", showHumidity: false, degreeLabel: false, showIndoorTemperature: false, showIndoorHumidity: false, + showFeelsLike: true, initialLoadDelay: 0, // 0 seconds delay retryDelay: 2500, @@ -105,7 +107,7 @@ Module.register("currentweather",{ this.indoorTemperature = null; this.indoorHumidity = null; this.weatherType = null; - + this.feelsLike = null; this.loaded = false; this.scheduleUpdate(this.config.initialLoadDelay); @@ -242,6 +244,19 @@ Module.register("currentweather",{ } wrapper.appendChild(large); + + if (this.config.showFeelsLike && this.config.onlyTemp === false){ + var small = document.createElement("div"); + small.className = "normal medium"; + + var feelsLike = document.createElement("span"); + feelsLike.className = "dimmed"; + feelsLike.innerHTML = "Feels " + this.feelsLike + "°" + degreeLabel; + small.appendChild(feelsLike); + + wrapper.appendChild(small); + } + return wrapper; }, @@ -365,13 +380,71 @@ Module.register("currentweather",{ this.humidity = parseFloat(data.main.humidity); this.temperature = this.roundValue(data.main.temp); + this.feelsLike = 0; if (this.config.useBeaufort){ this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed)); + } else if (this.config.useKMPHwind) { + this.windSpeed = parseFloat((data.wind.speed * 60 * 60) / 1000).toFixed(0); } else { this.windSpeed = parseFloat(data.wind.speed).toFixed(0); } + // ONLY WORKS IF TEMP IN C // + var windInMph = parseFloat(data.wind.speed * 2.23694); + + var tempInF = 0; + switch (this.config.units){ + case "metric": tempInF = 1.8 * this.temperature + 32; + break; + case "imperial": tempInF = this.temperature; + break; + case "default": + var tc = this.temperature - 273.15; + tempInF = 1.8 * tc + 32; + break; + } + + if (windInMph > 3 && tempInF < 50){ + // windchill + var windchillinF = Math.round(35.74+0.6215*tempInF-35.75*Math.pow(windInMph,0.16)+0.4275*tempInF*Math.pow(windInMph,0.16)); + var windChillInC = (windchillinF - 32) * (5/9); + // this.feelsLike = windChillInC.toFixed(0); + + switch (this.config.units){ + case "metric": this.feelsLike = windChillInC.toFixed(0); + break; + case "imperial": this.feelsLike = windChillInF.toFixed(0); + break; + case "default": + var tc = windChillInC - 273.15; + this.feelsLike = tc.toFixed(0); + break; + } + + } else if (tempInF > 80 && this.humidity > 40){ + // heat index + var Hindex = -42.379 + 2.04901523*tempInF + 10.14333127*this.humidity + - 0.22475541*tempInF*this.humidity - 6.83783*Math.pow(10,-3)*tempInF*tempInF + - 5.481717*Math.pow(10,-2)*this.humidity*this.humidity + + 1.22874*Math.pow(10,-3)*tempInF*tempInF*this.humidity + + 8.5282*Math.pow(10,-4)*tempInF*this.humidity*this.humidity + - 1.99*Math.pow(10,-6)*tempInF*tempInF*this.humidity*this.humidity; + + switch (this.config.units){ + case "metric": this.feelsLike = Hindex.toFixed(0); + break; + case "imperial": this.feelsLike = parseFloat(Hindex * 1.8 + 32).toFixed(0); + break; + case "default": + var tc = Hindex - 273.15; + this.feelsLike = tc.toFixed(0); + break; + } + } else { + this.feelsLike = parseFloat(this.temperature).toFixed(0); + } + this.windDirection = this.deg2Cardinal(data.wind.deg); this.windDeg = data.wind.deg; this.weatherType = this.config.iconTable[data.weather[0].icon]; @@ -497,4 +570,5 @@ Module.register("currentweather",{ var decimals = this.config.roundTemp ? 0 : 1; return parseFloat(temperature).toFixed(decimals); } + }); diff --git a/modules/default/newsfeed/fetcher.js b/modules/default/newsfeed/fetcher.js index 0cc73e36..a3bf1fa0 100644 --- a/modules/default/newsfeed/fetcher.js +++ b/modules/default/newsfeed/fetcher.js @@ -45,7 +45,7 @@ var Fetcher = function(url, reloadInterval, encoding) { var title = item.title; var description = item.description || item.summary || item.content || ""; - var pubdate = item.pubdate || item.published || item.updated; + var pubdate = item.pubdate || item.published || item.updated || item["dc:date"]; var url = item.url || item.link || ""; if (title && pubdate) { diff --git a/modules/default/updatenotification/updatenotification.js b/modules/default/updatenotification/updatenotification.js index 306ac6ae..b26517e5 100644 --- a/modules/default/updatenotification/updatenotification.js +++ b/modules/default/updatenotification/updatenotification.js @@ -58,16 +58,19 @@ Module.register("updatenotification", { icon.innerHTML = " "; message.appendChild(icon); - var subtextHtml = this.translate("UPDATE_INFO") - .replace("COMMIT_COUNT", this.status.behind + " " + ((this.status.behind == 1) ? "commit" : "commits")) - .replace("BRANCH_NAME", this.status.current); + var subtextHtml = this.translate("UPDATE_INFO", { + COMMIT_COUNT: this.status.behind + " " + ((this.status.behind == 1) ? "commit" : "commits"), + BRANCH_NAME: this.status.current + }); var text = document.createElement("span"); if (this.status.module == "default") { text.innerHTML = this.translate("UPDATE_NOTIFICATION"); subtextHtml = this.diffLink(subtextHtml); } else { - text.innerHTML = this.translate("UPDATE_NOTIFICATION_MODULE").replace("MODULE_NAME", this.status.module); + text.innerHTML = this.translate("UPDATE_NOTIFICATION_MODULE", { + MODULE_NAME: this.status.module + }); } message.appendChild(text); diff --git a/package-lock.json b/package-lock.json index 1f77b632..0e3b9a31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,12 @@ "integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=", "dev": true }, + "abab": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", + "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=", + "dev": true + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -63,6 +69,21 @@ "negotiator": "0.6.1" } }, + "acorn": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", + "integrity": "sha512-XLmq3H/BVvW6/GbxKryGxWORz1ebilSsUDlyC27bXhWGWAZWkGwS6FLHjOlwFXNFoWFQEO/Df4u0YYd0K3BQgQ==", + "dev": true + }, + "acorn-globals": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.1.0.tgz", + "integrity": "sha512-KjZwU26uG3u6eZcfGbTULzFcsoz6pegNKtHPksZPOUsiKo5bUmiBPa38FuHZ/Eun+XYh/JCCkS9AS3Lu4McQOQ==", + "dev": true, + "requires": { + "acorn": "5.4.1" + } + }, "acorn-jsx": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", @@ -288,6 +309,12 @@ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true + }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", @@ -359,6 +386,12 @@ "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -630,6 +663,12 @@ "repeat-element": "1.1.2" } }, + "browser-process-hrtime": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz", + "integrity": "sha1-Ql1opY00R/AqBKqJQYf86K+Le44=", + "dev": true + }, "browser-stdout": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", @@ -1024,6 +1063,12 @@ "dashify": "0.2.2" } }, + "content-type-parser": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/content-type-parser/-/content-type-parser-1.0.2.tgz", + "integrity": "sha512-lM4l4CnMEwOLHAHr/P6MEZwZFPJFtAAKgL6pogbXmVZggIqXhdB6RbBtPOTsw2FcXwYhehRGERJmRrjOiIB8pQ==", + "dev": true + }, "cookie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", @@ -1140,6 +1185,21 @@ "integrity": "sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo=", "dev": true }, + "cssom": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz", + "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=", + "dev": true + }, + "cssstyle": { + "version": "0.2.37", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", + "integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=", + "dev": true, + "requires": { + "cssom": "0.3.2" + } + }, "current-week-number": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/current-week-number/-/current-week-number-1.0.7.tgz", @@ -1443,6 +1503,15 @@ "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", "dev": true }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "4.0.2" + } + }, "domhandler": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", @@ -1657,6 +1726,34 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, + "escodegen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.0.tgz", + "integrity": "sha512-v0MYvNQ32bzwoG2OSFzWAkuahDQHK92JBN0pTAALJ4RIxEZe766QJPDR8Hqy7XNUy5K3fnVL76OqYAdc4TZEIw==", + "dev": true, + "requires": { + "esprima": "3.1.3", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "optionator": "0.8.2", + "source-map": "0.5.7" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "optional": true + } + } + }, "eslint": { "version": "4.16.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.16.0.tgz", @@ -2919,6 +3016,15 @@ "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.1.0.tgz", "integrity": "sha512-zXhh/DqgrTXJ7erTN6Fh5k/xjMhDGXCqdYN3wvxUvGUQvnxcFfUd8E+6vLg/nk3ss1TYMb+DhRl25fYABioTvA==" }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "requires": { + "whatwg-encoding": "1.0.3" + } + }, "html-tags": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", @@ -3460,6 +3566,53 @@ "integrity": "sha512-vE2hT1D0HLZCLLclfBSfkfTTedhVj0fubHpJBHKwwUWX0nSbhPAfk+SG9rTX95BYNmau8rGFfCeaT6T5OW1C2A==", "dev": true }, + "jsdom": { + "version": "11.6.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.6.2.tgz", + "integrity": "sha512-pAeZhpbSlUp5yQcS6cBQJwkbzmv4tWFaYxHbFVSxzXefqjvtRA851Z5N2P+TguVG9YeUDcgb8pdeVQRJh0XR3Q==", + "dev": true, + "requires": { + "abab": "1.0.4", + "acorn": "5.4.1", + "acorn-globals": "4.1.0", + "array-equal": "1.0.0", + "browser-process-hrtime": "0.1.2", + "content-type-parser": "1.0.2", + "cssom": "0.3.2", + "cssstyle": "0.2.37", + "domexception": "1.0.1", + "escodegen": "1.9.0", + "html-encoding-sniffer": "1.0.2", + "left-pad": "1.2.0", + "nwmatcher": "1.4.3", + "parse5": "4.0.0", + "pn": "1.1.0", + "request": "2.83.0", + "request-promise-native": "1.0.5", + "sax": "1.2.4", + "symbol-tree": "3.2.2", + "tough-cookie": "2.3.3", + "w3c-hr-time": "1.0.1", + "webidl-conversions": "4.0.2", + "whatwg-encoding": "1.0.3", + "whatwg-url": "6.4.0", + "ws": "4.0.0", + "xml-name-validator": "3.0.0" + }, + "dependencies": { + "ws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-4.0.0.tgz", + "integrity": "sha512-QYslsH44bH8O7/W2815u5DpnCpXWpEK44FmaHffNwgJI4JMaSZONgPBTOfrxJ29mXKbXak+LsJ2uAkDTYq2ptQ==", + "dev": true, + "requires": { + "async-limiter": "1.0.0", + "safe-buffer": "5.1.1", + "ultron": "1.1.0" + } + } + } + }, "jshint": { "version": "2.9.5", "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.5.tgz", @@ -3735,6 +3888,12 @@ "invert-kv": "1.0.0" } }, + "left-pad": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz", + "integrity": "sha1-0wpzxrggHY99jnlWupYWCHpo4O4=", + "dev": true + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -3877,6 +4036,12 @@ "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, "log-symbols": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.1.0.tgz", @@ -4402,6 +4567,12 @@ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, + "nwmatcher": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.3.tgz", + "integrity": "sha512-IKdSTiDWCarf2JTS5e9e2+5tPZGdkRJ79XjYV0pzK8Q9BpsFyBq1RGKxzs7Q8UBushGw7m6TzVKz6fcY99iSWw==", + "dev": true + }, "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", @@ -4603,6 +4774,12 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, "parsejson": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz", @@ -4719,6 +4896,12 @@ "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, "postcss": { "version": "6.0.12", "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.12.tgz", @@ -5452,6 +5635,34 @@ } } }, + "request-promise-core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "dev": true, + "requires": { + "lodash": "4.17.5" + }, + "dependencies": { + "lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==", + "dev": true + } + } + }, + "request-promise-native": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", + "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", + "dev": true, + "requires": { + "request-promise-core": "1.1.1", + "stealthy-require": "1.1.1", + "tough-cookie": "2.3.3" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5890,6 +6101,12 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -6376,6 +6593,12 @@ "integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=", "dev": true }, + "symbol-tree": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", + "dev": true + }, "table": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/table/-/table-4.0.1.tgz", @@ -6561,6 +6784,23 @@ "punycode": "1.4.1" } }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", + "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=", + "dev": true + } + } + }, "trim": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", @@ -6862,6 +7102,15 @@ "integrity": "sha512-8Xz4H3vhYRGbFupLtl6dHwMx0ojUcjt0HYkqZ9oBCfipd/5mD7Md58m2/dq7uPuZU/0T3Gb1m66KS9jn+I+14Q==", "dev": true }, + "w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "dev": true, + "requires": { + "browser-process-hrtime": "0.1.2" + } + }, "walk": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/walk/-/walk-2.3.9.tgz", @@ -7046,12 +7295,38 @@ } } }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, "wgxpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wgxpath/-/wgxpath-1.0.0.tgz", "integrity": "sha1-7vikudVYzEla06mit1FZfs2a9pA=", "dev": true }, + "whatwg-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz", + "integrity": "sha512-jLBwwKUhi8WtBfsMQlL4bUUcT8sMkAtQinscJAe/M4KHCkHuUJAF6vuB0tueNIw4c8ziO6AkRmgY+jL3a0iiPw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.19" + } + }, + "whatwg-url": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.4.0.tgz", + "integrity": "sha512-Z0CVh/YE217Foyb488eo+iBv+r7eAQ0wSTyApi9n06jhcA3z6Nidg/EGvl0UFkg7kMdKxfBzzr+o9JF+cevgMg==", + "dev": true, + "requires": { + "lodash.sortby": "4.7.0", + "tr46": "1.0.1", + "webidl-conversions": "4.0.2" + } + }, "which": { "version": "1.2.14", "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", @@ -7141,6 +7416,12 @@ "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.0.0.tgz", "integrity": "sha1-iYr7k4abJGYc+cUvnujbjtB2Tdk=" }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, "xmlhttprequest-ssl": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz", diff --git a/package.json b/package.json index ac397105..9da28d23 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,14 @@ "main": "js/electron.js", "scripts": { "start": "sh run-start.sh", - "install": "cd vendor && npm install", - "install-fonts": "cd fonts && npm install", - "postinstall": "sh installers/postinstall/postinstall.sh && npm run install-fonts", + "install": "cd vendor && yon install", + "install-fonts": "cd fonts && yon install", + "postinstall": "sh installers/postinstall/postinstall.sh && yon 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", - "config:check": "node tests/configs/check_config.js" + "config:check": "node tests/configs/check_config.js", + "lint": "grunt" }, "repository": { "type": "git", @@ -44,13 +45,15 @@ "grunt-stylelint": "latest", "grunt-yamllint": "latest", "http-auth": "^3.2.3", + "jsdom": "^11.6.2", "jshint": "^2.9.5", "mocha": "^4.1.0", "mocha-each": "^1.1.0", "spectron": "3.7.x", "stylelint": "^8.4.0", "stylelint-config-standard": "latest", - "time-grunt": "latest" + "time-grunt": "latest", + "yarn-or-npm": "^2.0.4" }, "dependencies": { "body-parser": "^1.18.2", diff --git a/tests/configs/data/StripComments.json b/tests/configs/data/StripComments.json new file mode 100644 index 00000000..62d5d618 --- /dev/null +++ b/tests/configs/data/StripComments.json @@ -0,0 +1,13 @@ +{ + // Escaped + "FOO\"BAR": "Today", + + /* + * The following lines + * represent cardinal directions + */ + "N": "N", + "E": "E", + "S": "S", + "W": "W" +} diff --git a/tests/configs/data/TranslationTest.json b/tests/configs/data/TranslationTest.json new file mode 100644 index 00000000..5614b23c --- /dev/null +++ b/tests/configs/data/TranslationTest.json @@ -0,0 +1,32 @@ +{ + "LOADING": "Loading …", + + "TODAY": "Today", + "TOMORROW": "Tomorrow", + "DAYAFTERTOMORROW": "In 2 days", + "RUNNING": "Ends in", + "EMPTY": "No upcoming events.", + + "WEEK": "Week {weekNumber}", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSW", + "SW": "SW", + "WSW": "WSW", + "W": "W", + "WNW": "WNW", + "NW": "NW", + "NNW": "NNW", + + "UPDATE_NOTIFICATION": "MagicMirror² update available.", + "UPDATE_NOTIFICATION_MODULE": "Update available for MODULE_NAME module.", + "UPDATE_INFO": "The current installation is COMMIT_COUNT behind on the BRANCH_NAME branch." +} diff --git a/tests/configs/data/en.json b/tests/configs/data/en.json new file mode 100644 index 00000000..5614b23c --- /dev/null +++ b/tests/configs/data/en.json @@ -0,0 +1,32 @@ +{ + "LOADING": "Loading …", + + "TODAY": "Today", + "TOMORROW": "Tomorrow", + "DAYAFTERTOMORROW": "In 2 days", + "RUNNING": "Ends in", + "EMPTY": "No upcoming events.", + + "WEEK": "Week {weekNumber}", + + "N": "N", + "NNE": "NNE", + "NE": "NE", + "ENE": "ENE", + "E": "E", + "ESE": "ESE", + "SE": "SE", + "SSE": "SSE", + "S": "S", + "SSW": "SSW", + "SW": "SW", + "WSW": "WSW", + "W": "W", + "WNW": "WNW", + "NW": "NW", + "NNW": "NNW", + + "UPDATE_NOTIFICATION": "MagicMirror² update available.", + "UPDATE_NOTIFICATION_MODULE": "Update available for MODULE_NAME module.", + "UPDATE_INFO": "The current installation is COMMIT_COUNT behind on the BRANCH_NAME branch." +} diff --git a/tests/e2e/translations_spec.js b/tests/e2e/translations_spec.js new file mode 100644 index 00000000..727f4623 --- /dev/null +++ b/tests/e2e/translations_spec.js @@ -0,0 +1,129 @@ +const fs = require("fs"); +const path = require("path"); +const chai = require("chai"); +const expect = chai.expect; +const mlog = require("mocha-logger"); +const translations = require("../../translations/translations.js"); +const helmet = require("helmet"); +const {JSDOM} = require("jsdom"); +const express = require("express"); + +describe("Translations", function() { + let server; + + before(function() { + const app = express(); + app.use(helmet()); + app.use(function (req, res, next) { + res.header("Access-Control-Allow-Origin", "*"); + next(); + }); + app.use("/translations", express.static(path.join(__dirname, "..", "..", "translations"))); + + server = app.listen(3000); + }); + + after(function() { + server.close(); + }); + + it("should have a translation file in the specified path", function() { + for(let language in translations) { + const file = fs.statSync(translations[language]); + expect(file.isFile()).to.be.equal(true); + } + }); + + const mmm = { + name: "TranslationTest", + file(file) { + return `http://localhost:3000/${file}`; + } + }; + + describe("Parsing language files through the Translator class", function() { + for(let language in translations) { + it(`should parse ${language}`, function(done) { + const dom = new JSDOM(`\ + \ + \ + \ + \ + \ + \ + \ + \ +