diff --git a/CHANGELOG.md b/CHANGELOG.md index f76e6710..8d4d7bcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [v4.0.6](https://github.com/OpenZWave/Zwave2Mqtt/compare/v4.0.5...v4.0.6) + +> 4 November 2020 + +- feat(ui): save items per page in local storage [`#816`](https://github.com/OpenZWave/Zwave2Mqtt/pull/816) +- chore(deps): bump chrisns/openzwave in /docker [`#815`](https://github.com/OpenZWave/Zwave2Mqtt/pull/815) +- fix(hass): misuse drying state for Full Power mode [`#775`](https://github.com/OpenZWave/Zwave2Mqtt/pull/775) +- chore(deps): bump actions/setup-node from v1 to v2.1.2 [`#768`](https://github.com/OpenZWave/Zwave2Mqtt/pull/768) +- chore(deps): bump prismjs from 1.21.0 to 1.22.0 [`#778`](https://github.com/OpenZWave/Zwave2Mqtt/pull/778) +- chore(deps): bump socket.io-client from 2.3.0 to 2.3.1 [`#759`](https://github.com/OpenZWave/Zwave2Mqtt/pull/759) +- chore(deps): bump pascalgn/automerge-action from v0.11.0 to v0.12.0 [`#772`](https://github.com/OpenZWave/Zwave2Mqtt/pull/772) +- fix(ui): vuetify deprecation `.native` [`#797`](https://github.com/OpenZWave/Zwave2Mqtt/pull/797) +- feat(hass): add deviceId '881-21-2' for Eurotronic Spirit Z-Wave Plus device [`#799`](https://github.com/OpenZWave/Zwave2Mqtt/pull/799) +- fix: update last active on value changes [`#798`](https://github.com/OpenZWave/Zwave2Mqtt/pull/798) +- fix: allow `/` char in name and location #790 [`#796`](https://github.com/OpenZWave/Zwave2Mqtt/pull/796) + #### [v4.0.5](https://github.com/OpenZWave/Zwave2Mqtt/compare/v4.0.4...v4.0.5) > 16 October 2020 @@ -26,6 +42,7 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). - docker: update ozw to version 1.6.1382 [`#748`](https://github.com/OpenZWave/Zwave2Mqtt/pull/748) - feat(hass): Eurotronic Stella Z thermostat [`#746`](https://github.com/OpenZWave/Zwave2Mqtt/pull/746) - fix: Rows per page not set correctly (#793) [`#792`](https://github.com/OpenZWave/Zwave2Mqtt/issues/792) +- Release 4.0.5 [`43601f5`](https://github.com/OpenZWave/Zwave2Mqtt/commit/43601f51ac2b8d52a7269f8caf7d20d66398af26) - fix tag list composition [`55e0dc0`](https://github.com/OpenZWave/Zwave2Mqtt/commit/55e0dc0b69bd212130305989793037c33e588383) - fix tag list composition [`50cf1ad`](https://github.com/OpenZWave/Zwave2Mqtt/commit/50cf1add8ed6d52b90898c0fcd78cc9f90d5a63f) - fix tag list composition [`0e95d46`](https://github.com/OpenZWave/Zwave2Mqtt/commit/0e95d46235559c735e142b908c6f55a14a5753b5) diff --git a/docker/Dockerfile b/docker/Dockerfile index c26de957..38454161 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,6 +1,6 @@ # ---------------- # STEP 1: -FROM chrisns/openzwave:alpine-1.6.1392 as ozw +FROM chrisns/openzwave:alpine-1.6.1545 as ozw # ---------------- # STEP 2: diff --git a/hass/devices.js b/hass/devices.js index f7b465d8..f6829917 100644 --- a/hass/devices.js +++ b/hass/devices.js @@ -87,13 +87,22 @@ const SPIRIT_ZWAVE_PLUS = { type: 'climate', object_id: 'thermostat', values: ['64-1-0', '49-1-1', '67-1-1', '67-1-11'], - mode_map: { off: 'Off', heat: 'Heat', cool: 'Heat Eco' }, - setpoint_topic: { Heat: '67-1-1', 'Heat Eco': '67-1-11' }, + mode_map: { + off: 'Off', + heat: 'Heat', + cool: 'Heat Eco', + drying: 'Full Power' + }, + setpoint_topic: { + Heat: '67-1-1', + 'Heat Eco': '67-1-11', + 'Full Power': '67-1-1' + }, default_setpoint: '67-1-1', discovery_payload: { min_temp: 8, max_temp: 28, - modes: ['off', 'heat', 'cool'], + modes: ['off', 'heat', 'cool', 'drying'], mode_state_topic: '64-1-0', mode_command_topic: true, current_temperature_topic: '49-1-1', diff --git a/package-lock.json b/package-lock.json index d86e1bde..7ae493c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "zwave2mqtt", - "version": "4.0.5", + "version": "4.0.6", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2871,9 +2871,9 @@ } }, "@babel/polyfill": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.11.5.tgz", - "integrity": "sha512-FunXnE0Sgpd61pKSj2OSOs1D44rKTD3pGOfGilZ6LGrrIH0QEtJlTjqOqdF8Bs98JmjfGhni2BBkTfv9KcKJ9g==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.12.1.tgz", + "integrity": "sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==", "requires": { "core-js": "^2.6.5", "regenerator-runtime": "^0.13.4" @@ -3378,28 +3378,28 @@ } }, "@serialport/binding-abstract": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-9.0.1.tgz", - "integrity": "sha512-ncUFSRyVdpyCRuah2dzrs99UfEWWMAhV31ae2FT6j4f8TypQ8OgAF8KkcHiD4M3wORDh3UKCCTS7n8aJWge1RA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-9.0.2.tgz", + "integrity": "sha512-kyMX6usn+VLpidt0YsDq5JwztIan9TPCX6skr0XcalOxI8I7w+/2qVZJzjgo2fSqDnPRcU2jMWTytwzEXFODvQ==", "requires": { "debug": "^4.1.1" } }, "@serialport/binding-mock": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-9.0.1.tgz", - "integrity": "sha512-C01T6iX+nNKB7S6BhQEy5nfk4lUk/CkdFEfen9DDPYhtFtIsm5GCGvRB3Fjnp+8oDrGWJOrZfxFf3kWOOx665A==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-9.0.2.tgz", + "integrity": "sha512-HfrvJ/LXULHk8w63CGxwDNiDidFgDX8BnadY+cgVS6yHMHikbhLCLjCmUKsKBWaGKRqOznl0w+iUl7TMi1lkXQ==", "requires": { - "@serialport/binding-abstract": "^9.0.1", + "@serialport/binding-abstract": "^9.0.2", "debug": "^4.1.1" } }, "@serialport/bindings": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@serialport/bindings/-/bindings-9.0.1.tgz", - "integrity": "sha512-O5QuwCdnHuZygBKw7tVq2wHysfOnCbOyKtR/k9T9zHqptd89Tzy6xJQNtnrcbV/2D22noKX6yWj+1wqvNe6NRA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@serialport/bindings/-/bindings-9.0.2.tgz", + "integrity": "sha512-kQ3co4aGwwbUqkRdJ7UfdlbLB5dUQwNfSglexC8iv65D5HXfjSBR1bE0XUH8PH/v/6Dh6CSnwf6OP0I3H5vMWQ==", "requires": { - "@serialport/binding-abstract": "^9.0.1", + "@serialport/binding-abstract": "^9.0.2", "@serialport/parser-readline": "^9.0.1", "bindings": "^1.5.0", "debug": "^4.1.1", @@ -3441,9 +3441,9 @@ "integrity": "sha512-BHTV+Lkl+J8hSecFtDRENaR4fgA6tw44J+dmA1vEKEyum0iDN4bihbu8yvztYyo4PhBGUKDfm/PnD5EkJm0dPA==" }, "@serialport/stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-9.0.1.tgz", - "integrity": "sha512-S1xaf99vygbrMDNS/9GHYZYskWJHXJy6dCksW+ME2dzNXEXpz64vF0iug1tC1EIAhME9oD/s3ky2C9CUAd/GUg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-9.0.2.tgz", + "integrity": "sha512-0RkVe+gvwZu/PPfbb7ExQ+euGoCTGKD/B8TQ5fuhe+eKk1sh73RwjKmu9gp6veSNqx9Zljnh1dF6mhdEKWZpSA==", "requires": { "debug": "^4.1.1" } @@ -3550,9 +3550,9 @@ } }, "@types/html-minifier-terser": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz", - "integrity": "sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", + "integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==", "dev": true }, "@types/http-cache-semantics": { @@ -3640,9 +3640,9 @@ "dev": true }, "@types/uglify-js": { - "version": "3.9.3", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.9.3.tgz", - "integrity": "sha512-KswB5C7Kwduwjj04Ykz+AjvPcfgv/37Za24O2EDzYNbwyzOo8+ydtvzUfZ5UMguiVu29Gx44l1A6VsPPcmYu9w==", + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.11.1.tgz", + "integrity": "sha512-7npvPKV+jINLu1SpSYVWG8KvyJBhBa8tmzMMdDoVc2pWUYHN8KIXlPJhjJ4LT97c4dXJA2SHL/q6ADbDriZN+Q==", "dev": true, "requires": { "source-map": "^0.6.1" @@ -3655,9 +3655,9 @@ "dev": true }, "@types/webpack": { - "version": "4.41.22", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.22.tgz", - "integrity": "sha512-JQDJK6pj8OMV9gWOnN1dcLCyU9Hzs6lux0wBO4lr1+gyEhIBR9U3FMrz12t2GPkg110XAxEAw2WHF6g7nZIbRQ==", + "version": "4.41.24", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.24.tgz", + "integrity": "sha512-1A0MXPwZiMOD3DPMuOKUKcpkdPo8Lq33UGggZ7xio6wJ/jV1dAu5cXDrOfGDnldUroPIRLsr/DT43/GqOA4RFQ==", "dev": true, "requires": { "@types/anymatch": "*", @@ -3669,9 +3669,9 @@ } }, "@types/webpack-sources": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-1.4.2.tgz", - "integrity": "sha512-77T++JyKow4BQB/m9O96n9d/UUHWLQHlcqXb9Vsf4F1+wKNrrlWNFPDLKNT92RJnCSL6CieTc+NDXtCVZswdTw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-2.0.0.tgz", + "integrity": "sha512-a5kPx98CNFRKQ+wqawroFunvFqv7GHm/3KOI52NY9xWADgc8smu4R6prt4EU/M4QfVjvgBkMqU4fBhw3QfMVkg==", "dev": true, "requires": { "@types/node": "*", @@ -7589,17 +7589,17 @@ "dev": true }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" }, "dependencies": { "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -8172,6 +8172,19 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "ws": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz", @@ -10943,9 +10956,9 @@ "dev": true }, "html-webpack-plugin": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.4.1.tgz", - "integrity": "sha512-nEtdEIsIGXdXGG7MjTTZlmhqhpHU9pJFc1OYxcP36c5/ZKP6b0BJMww2QTvJGQYA9aMxUnjDujpZdYcVOXiBCQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz", + "integrity": "sha512-MouoXEYSjTzCrjIxWwg8gxL5fE2X2WZJLmBYXlaJhQUH5K/b5OrqmV7T4dB7iu0xkmJ6JlUuV6fFVtnqbPopZw==", "dev": true, "requires": { "@types/html-minifier-terser": "^5.0.0", @@ -13445,6 +13458,15 @@ "readdirp": "~3.4.0" } }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -14643,9 +14665,9 @@ } }, "ora": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.0.0.tgz", - "integrity": "sha512-s26qdWqke2kjN/wC4dy+IQPBIMWBJlSU/0JZhk30ZDBLelW25rv66yutUWARMigpGPzcXHb+Nac5pNhN/WsARw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.1.0.tgz", + "integrity": "sha512-9tXIMPvjZ7hPTbk8DFq1f7Kow/HU/pQYB60JbNq+QnGwcyhWVZaQ4hM9zQDEsPxw/muLpgiHSaumUZxCAmod/w==", "dev": true, "requires": { "chalk": "^4.1.0", @@ -15865,15 +15887,15 @@ } }, "prebuild-install": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.5.tgz", - "integrity": "sha512-YmMO7dph9CYKi5IR/BzjOJlRzpxGGVo1EsLSUZ0mt/Mq0HWZIHOKHHcHdT69yG54C9m6i45GpItwRHpk0Py7Uw==", + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.6.tgz", + "integrity": "sha512-s8Aai8++QQGi4sSbs/M1Qku62PFK49Jm1CbgXklGz4nmHveDq0wzJkg7Na5QbnO1uNH8K7iqx2EQ/mV0MZEmOg==", "requires": { "detect-libc": "^1.0.3", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", - "mkdirp": "^0.5.1", + "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", "node-abi": "^2.7.0", "noop-logger": "^0.1.1", @@ -16477,13 +16499,13 @@ } }, "pretty-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", - "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", + "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", "dev": true, "requires": { - "renderkid": "^2.0.1", - "utila": "~0.4" + "lodash": "^4.17.20", + "renderkid": "^2.0.4" } }, "prismjs": { @@ -17114,6 +17136,15 @@ "which": "^2.0.1" } }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, "decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -17325,6 +17356,12 @@ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -17566,16 +17603,16 @@ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" }, "renderkid": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz", - "integrity": "sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.4.tgz", + "integrity": "sha512-K2eXrSOJdq+HuKzlcjOlGoOarUu5SDguDEhE7+Ah4zuOWL40j8A/oHvLlLob9PSTNvVnBd+/q0Er1QfpEuem5g==", "dev": true, "requires": { "css-select": "^1.1.0", "dom-converter": "^0.2", "htmlparser2": "^3.3.0", - "strip-ansi": "^3.0.0", - "utila": "^0.4.0" + "lodash": "^4.17.20", + "strip-ansi": "^3.0.0" } }, "repeat-element": { @@ -18741,19 +18778,19 @@ } }, "serialport": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/serialport/-/serialport-9.0.1.tgz", - "integrity": "sha512-35Ms8dqjtAb73lptfEZG2l/nFZOxHt3hUjCHvl+g3Mu737gzFLDpSBrRywBJw4G4eS5ozZ3YcthwYnop1WO+ng==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-9.0.2.tgz", + "integrity": "sha512-N++EVrc2F3kUJ6aCE6BLxHwTrelFAZM3LFw4lo8TV0fDtfrwTc3+aoDpSsvfQg3DxrRf3shCtA6WYEH4g8kapw==", "requires": { - "@serialport/binding-mock": "^9.0.1", - "@serialport/bindings": "^9.0.1", + "@serialport/binding-mock": "^9.0.2", + "@serialport/bindings": "^9.0.2", "@serialport/parser-byte-length": "^9.0.1", "@serialport/parser-cctalk": "^9.0.1", "@serialport/parser-delimiter": "^9.0.1", "@serialport/parser-readline": "^9.0.1", "@serialport/parser-ready": "^9.0.1", "@serialport/parser-regex": "^9.0.1", - "@serialport/stream": "^9.0.1", + "@serialport/stream": "^9.0.2", "debug": "^4.1.1" } }, @@ -19161,6 +19198,21 @@ "socket.io-parser": "~3.4.0" }, "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", @@ -19295,10 +19347,23 @@ "isarray": "2.0.1" }, "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -19912,37 +19977,28 @@ } }, "tar-fs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz", - "integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "requires": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.0.0" + "tar-stream": "^2.1.4" } }, "tar-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz", - "integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.4.tgz", + "integrity": "sha512-o3pS2zlG4gxr67GmFYBLlq+dM8gyRGUOvsrHclSkvtVtQbjV0s/+ZE8OpICbaj8clrX3tjeHngYGP7rweaBnuw==", "requires": { - "bl": "^4.0.1", + "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" }, "dependencies": { - "buffer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", diff --git a/package.json b/package.json index 1e9fa592..b2cefe10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zwave2mqtt", - "version": "4.0.5", + "version": "4.0.6", "bin": "bin/www", "description": "Zwave To MQTT Gateway", "author": "Daniel Lando ", @@ -88,7 +88,7 @@ } }, "dependencies": { - "@babel/polyfill": "^7.11.5", + "@babel/polyfill": "^7.12.1", "ansi_up": "^4.0.4", "app-root-path": "^3.0.0", "axios": "^0.20.0", @@ -98,7 +98,7 @@ "connect-history-api-fallback": "^1.6.0", "cookie-parser": "^1.4.5", "cors": "^2.8.5", - "debug": "^4.1.1", + "debug": "^4.2.0", "ejs": "^3.1.5", "express": "^4.17.1", "jsonfile": "^6.0.1", @@ -109,7 +109,7 @@ "nedb": "^1.8.0", "openzwave-shared": "^1.7.1", "prismjs": "^1.22.0", - "serialport": "^9.0.1", + "serialport": "^9.0.2", "serve-favicon": "^2.5.0", "socket.io": "^2.3.0", "socket.io-client": "^2.3.1", @@ -164,7 +164,7 @@ "fibers": "^5.0.0", "file-loader": "^6.1.0", "friendly-errors-webpack-plugin": "^1.7.0", - "html-webpack-plugin": "^4.4.1", + "html-webpack-plugin": "^4.5.0", "lodash": "^4.17.20", "markdownlint-cli": "^0.23.2", "material-design-icons-iconfont": "^6.1.0", @@ -174,7 +174,7 @@ "nodemon": "^2.0.4", "npm-run-all": "^4.1.5", "optimize-css-assets-webpack-plugin": "^5.0.4", - "ora": "^5.0.0", + "ora": "^5.1.0", "portfinder": "^1.0.28", "postcss-import": "^12.0.1", "postcss-loader": "^3.0.0", diff --git a/src/components/ControlPanel.vue b/src/components/ControlPanel.vue index 427bd71e..c7b4527f 100644 --- a/src/components/ControlPanel.vue +++ b/src/components/ControlPanel.vue @@ -75,49 +75,11 @@ - - - + Node @@ -613,6 +575,7 @@ import Confirm from '@/components/Confirm' import AnsiUp from 'ansi_up' import DialogSceneValue from '@/components/dialogs/DialogSceneValue' +import NodesTable from '@/components/nodes-table' const ansiUp = new AnsiUp() @@ -628,7 +591,8 @@ export default { components: { ValueID, DialogSceneValue, - Confirm + Confirm, + NodesTable }, computed: { scenesWithId () { @@ -640,9 +604,6 @@ export default { dialogTitle () { return this.editedIndex === -1 ? 'New Value' : 'Edit Value' }, - tableNodes () { - return this.showHidden ? this.nodes : this.nodes.filter(n => !n.failed) - }, hassDevices () { var devices = [] if (this.selectedNode && this.selectedNode.hassDevices) { @@ -864,16 +825,6 @@ export default { locError: null, newLoc: '', selectedNode: null, - headers: [ - { text: 'ID', value: 'node_id' }, - { text: 'Type', value: 'type' }, - { text: 'Product', value: 'product' }, - { text: 'Name', value: 'name' }, - { text: 'Location', value: 'loc' }, - { text: 'Secure', value: 'secure' }, - { text: 'Status', value: 'status' }, - { text: 'Last Active', value: 'lastActive' } - ], rules: { required: value => { var valid = false @@ -897,13 +848,13 @@ export default { return match[0] !== name ? 'Only a-zA-Z0-9_- chars are allowed' : null }, - selectNode (item) { - if (!item) return + selectNode ({ node }) { + if (!node) return - if (this.selectedNode === item) { + if (this.selectedNode === node) { this.selectedNode = null } else { - this.selectedNode = this.nodes.find(n => n.node_id === item.node_id) + this.selectedNode = this.nodes.find(n => n.node_id === node.node_id) } }, getValue (v) { diff --git a/src/components/nodes-table/filter-options.vue b/src/components/nodes-table/filter-options.vue new file mode 100644 index 00000000..e8b6c046 --- /dev/null +++ b/src/components/nodes-table/filter-options.vue @@ -0,0 +1,145 @@ + + + diff --git a/src/components/nodes-table/index.vue b/src/components/nodes-table/index.vue new file mode 100644 index 00000000..689caf31 --- /dev/null +++ b/src/components/nodes-table/index.vue @@ -0,0 +1,91 @@ + + + diff --git a/src/components/nodes-table/nodes-table.css b/src/components/nodes-table/nodes-table.css new file mode 100644 index 00000000..7e7c2e85 --- /dev/null +++ b/src/components/nodes-table/nodes-table.css @@ -0,0 +1,26 @@ +.td-large { + text-overflow: ellipsis; + white-space: nowrap; + overflow-x: hidden; + max-width: 20em; +} + +.td-medium { + text-overflow: ellipsis; + white-space: nowrap; + overflow-x: hidden; + max-width: 15em; +} + +.td-small { + text-overflow: ellipsis; + white-space: nowrap; + overflow-x: hidden; + max-width: 10em; +} + +.v-chip { + text-overflow: ellipsis; + white-space: nowrap; + overflow-x: hidden; +} diff --git a/src/components/nodes-table/nodes-table.js b/src/components/nodes-table/nodes-table.js new file mode 100644 index 00000000..f07a4173 --- /dev/null +++ b/src/components/nodes-table/nodes-table.js @@ -0,0 +1,181 @@ +import { NodeCollection } from '@/modules/NodeCollection' +import filterOptions from '@/components/nodes-table/filter-options.vue' + +export default { + props: { + nodes: Array, + showHidden: Boolean + }, + components: { + filterOptions + }, + data: () => ({ + nodeTableItems: 10, + selectedNode: undefined, + filters: {}, + headers: [ + { text: 'ID', value: 'node_id' }, + { text: 'Type', value: 'type' }, + { text: 'Product', value: 'product' }, + { text: 'Name', value: 'name' }, + { text: 'Location', value: 'loc' }, + { text: 'Secure', value: 'secure' }, + { text: 'Status', value: 'status' }, + { text: 'Last Active', value: 'lastActive' } + ] + }), + methods: { + initFilters () { + return { + ids: { type: 'number' }, + types: { type: 'string' }, + products: { type: 'string' }, + names: { type: 'string' }, + locations: { type: 'string' }, + secures: { type: 'boolean' }, + states: { type: 'string' }, + lastActives: { type: 'date' } + } + }, + resetFilter () { + this.filters = this.initFilters() + }, + nodeSelected (node) { + this.selectedNode = node + this.$emit('node-selected', { node }) + }, + productName (node) { + const manufacturer = node.manufacturer ? ` (${node.manufacturer})` : '' + return node.ready ? `${node.product}${manufacturer}` : '' + } + }, + mounted () { + this.filters = this.initFilters() + const itemsPerPage = parseInt(localStorage.getItem('nodes_itemsPerPage')) + this.nodeTableItems = !isNaN(itemsPerPage) ? itemsPerPage : 10 + }, + watch: { + nodeTableItems (val) { + localStorage.setItem('nodes_itemsPerPage', val) + } + }, + computed: { + nodeCollection () { + return new NodeCollection(this.nodes) + }, + relevantNodes () { + return this.nodeCollection.filter('failed', failed => { + return this.showHidden ? true : !failed + }) + }, + filteredNodes () { + return this.relevantNodes + .betweenNumber( + 'node_id', + this.filters.ids ? this.filters.ids.min : null, + this.filters.ids ? this.filters.ids.max : null + ) + .betweenDate( + 'lastActive', + this.filters.lastActives ? this.filters.lastActives.min : null, + this.filters.lastActives ? this.filters.lastActives.max : null + ) + + .contains( + ['product', 'manufacturer'], + this.filters.products ? this.filters.products.search : '' + ) + .contains(['type'], this.filters.types ? this.filters.types.search : '') + .contains(['name'], this.filters.names ? this.filters.names.search : '') + .contains( + ['loc'], + this.filters.locations ? this.filters.locations.search : '' + ) + .contains( + ['status'], + this.filters.states ? this.filters.states.search : '' + ) + + .equalsAny( + 'node_id', + this.filters.ids + ? this.filters.ids.selections + ? this.filters.ids.selections + : [] + : [] + ) + .equalsAny( + 'type', + this.filters.types + ? this.filters.types.selections + ? this.filters.types.selections + : [] + : [] + ) + .equalsAny( + 'product', + this.filters.products + ? this.filters.products.selections + ? this.filters.products.selections + : [] + : [] + ) + .equalsAny( + 'name', + this.filters.names + ? this.filters.names.selections + ? this.filters.names.selections + : [] + : [] + ) + .equalsAny( + 'loc', + this.filters.locations + ? this.filters.locations.selections + ? this.filters.locations.selections + : [] + : [] + ) + .equalsAny( + 'status', + this.filters.states + ? this.filters.states.selections + ? this.filters.states.selections + : [] + : [] + ) + + .equals( + 'secure', + this.filters.secures ? this.filters.secures.bool : null + ) + }, + tableNodes () { + return this.filteredNodes.nodes + }, + ids () { + return this.relevantNodes.values('node_id') + }, + products () { + return this.relevantNodes.values('product') + }, + names () { + return this.relevantNodes.values('name') + }, + locations () { + return this.relevantNodes.values('loc') + }, + secures () { + return [undefined, false, true] + }, + states () { + return this.relevantNodes.values('status') + }, + types () { + return this.relevantNodes.values('type') + }, + lastActives () { + return this.relevantNodes.values('lastActive') + } + } +} diff --git a/src/modules/NodeCollection.js b/src/modules/NodeCollection.js new file mode 100644 index 00000000..ddc9d417 --- /dev/null +++ b/src/modules/NodeCollection.js @@ -0,0 +1,91 @@ +export class NodeCollection { + constructor (nodes) { + this.nodes = nodes + } + + _isUndefined (value) { + return value === undefined || value === null || value === '' + } + + _strValue (str, caseSensitive) { + return caseSensitive ? `${str}` : `${str}`.toLowerCase() + } + + _createStringFilter (filterValue, caseSensitive) { + if (this._isUndefined(filterValue)) { + filterValue = '' + } + const strFilter = this._strValue(filterValue, caseSensitive) + return value => this._strValue(value, caseSensitive).indexOf(strFilter) >= 0 + } + + _filterByProps (node, properties, filter) { + const mergedProps = [properties].reduce( + (merged, prop) => merged.concat(prop), + [] + ) + return mergedProps.find(prop => filter(node[prop])) + } + + filter (properties, filter) { + const filtered = this.nodes.filter(node => + this._filterByProps(node, properties, filter) + ) + return new NodeCollection(filtered) + } + + contains (properties, value, caseSensitive = false) { + return this.filter( + properties, + this._createStringFilter(value, caseSensitive) + ) + } + + equals (properties, value) { + return this.filter( + properties, + nodeValue => this._isUndefined(value) || value === nodeValue + ) + } + + betweenNumber (properties, minValue, maxValue) { + return this.filter( + properties, + nodeValue => + (this._isUndefined(minValue) || minValue <= nodeValue) && + (this._isUndefined(maxValue) || maxValue >= nodeValue) + ) + } + + betweenDate (properties, minValue, maxValue) { + return this.filter(properties, nodeValue => { + const nodeValueTime = new Date(nodeValue).getTime() + return ( + (this._isUndefined(minValue) || + new Date(minValue).getTime() <= nodeValueTime) && + (this._isUndefined(maxValue) || + new Date(maxValue).getTime() >= nodeValueTime) + ) + }) + } + + equalsAny (properties, values) { + return this.filter( + properties, + nodeValue => values.length === 0 || values.indexOf(nodeValue) >= 0 + ) + } + + values (property) { + const uniqueMap = {} + this.nodes.forEach(node => { + const strVal = this._strValue(node[property]) + uniqueMap[strVal] = uniqueMap[strVal] || node[property] + }) + return Object.keys(uniqueMap) + .sort() + .map(key => uniqueMap[key]) + } +} + +export default NodeCollection diff --git a/src/modules/NodeCollection.test.js b/src/modules/NodeCollection.test.js new file mode 100644 index 00000000..3856836a --- /dev/null +++ b/src/modules/NodeCollection.test.js @@ -0,0 +1,295 @@ +import chai from 'chai' +import { NodeCollection } from './NodeCollection' + +describe('NodeCollection', () => { + describe('#constructor', () => { + it('uses the nodes passed in as the collection nodes', () => { + const collection = new NodeCollection([{ id: 1 }]) + chai.expect(collection.nodes).to.eql([{ id: 1 }]) + }) + }) + describe('#filter', () => { + const isOdd = num => num % 2 + it('returns nodes with the property matching the filter', () => { + const collection = new NodeCollection([ + { id: 1 }, + { id: 2 }, + { id: 3 }, + { id: 4 } + ]) + const filtered = collection.filter('id', isOdd) + chai.expect(filtered.nodes).to.eql([{ id: 1 }, { id: 3 }]) + }) + it('returns nodes with any of the properties matching the filter', () => { + const collection = new NodeCollection([ + { id: 1, value: 2 }, + { id: 2, value: 1 }, + { id: 3, value: 2 }, + { id: 4, value: 2 } + ]) + const filtered = collection.filter(['id', 'value'], isOdd) + chai.expect(filtered.nodes).to.eql([ + { id: 1, value: 2 }, + { id: 2, value: 1 }, + { id: 3, value: 2 } + ]) + }) + }) + describe('#contains', () => { + const stringCollection = new NodeCollection([ + { id: 'pippo' }, + { id: 'paRanza' }, + { id: 'PipPo' }, + { id: 'Ames' } + ]) + + it('returns nodes with the properties containing the value', () => { + const collection = new NodeCollection([ + { id: 100 }, + { id: '210' }, + { id: 20 }, + { id: '300' } + ]) + const filtered = collection.contains('id', '10') + chai.expect(filtered.nodes).to.eql([{ id: 100 }, { id: '210' }]) + }) + it('matches values over multiple properties', () => { + const collection = new NodeCollection([ + { id: 100, name: 'sample' }, + { id: '210', name: 'trinity' }, + { id: 20, name: '10 packs' }, + { id: '300', name: 'fazuoli' } + ]) + const filtered = collection.contains(['id', 'name'], '10') + chai.expect(filtered.nodes).to.eql([ + { id: 100, name: 'sample' }, + { id: '210', name: 'trinity' }, + { id: 20, name: '10 packs' } + ]) + }) + it('is case insensitive by default', () => { + const filtered = stringCollection.contains('id', 'piPPo') + chai.expect(filtered.nodes).to.eql([{ id: 'pippo' }, { id: 'PipPo' }]) + }) + it('accepts a case sensitive flag', () => { + const filtered = stringCollection.contains('id', 'PipPo', true) + chai.expect(filtered.nodes).to.eql([{ id: 'PipPo' }]) + }) + }) + describe('#equals', () => { + it('returns nodes with the properties with equal value', () => { + const collection = new NodeCollection([ + { id: 10 }, + { id: '10' }, + { id: 20 }, + { id: '20' } + ]) + const filtered = collection.equals('id', 10) + chai.expect(filtered.nodes).to.eql([{ id: 10 }]) + }) + it('works over multiple properties', () => { + const collection = new NodeCollection([ + { id: 10, sample: '20' }, + { id: '10', sample: 30 }, + { id: 20, sample: '10' }, + { id: '20', sample: 10 } + ]) + const filtered = collection.equals(['id', 'sample'], 10) + chai.expect(filtered.nodes).to.eql([ + { id: 10, sample: '20' }, + { id: '20', sample: 10 } + ]) + }) + }) + describe('#equalsAny', () => { + it('returns all nodes when values has no elements', () => { + const collection = new NodeCollection([ + { id: 10 }, + { id: '10' }, + { id: 20 }, + { id: '20' } + ]) + const filtered = collection.equalsAny('id', []) + chai + .expect(filtered.nodes) + .to.eql([{ id: 10 }, { id: '10' }, { id: 20 }, { id: '20' }]) + }) + it('returns nodes with the properties equal to any of the values', () => { + const collection = new NodeCollection([ + { id: 10 }, + { id: '10' }, + { id: 20 }, + { id: '20' } + ]) + const filtered = collection.equalsAny('id', [10, '20']) + chai.expect(filtered.nodes).to.eql([{ id: 10 }, { id: '20' }]) + }) + it('works over multiple properties', () => { + const collection = new NodeCollection([ + { id: 10, sample: 20 }, + { id: '10', sample: '20' }, + { id: 20, sample: '10' }, + { id: '20', sample: 'zdub' } + ]) + const filtered = collection.equalsAny(['id', 'sample'], [10, '20']) + chai.expect(filtered.nodes).to.eql([ + { id: 10, sample: 20 }, + { id: '10', sample: '20' }, + { id: '20', sample: 'zdub' } + ]) + }) + }) + describe('#betweenNumber', () => { + it('returns all values if min/max are undefined', () => { + const collection = new NodeCollection([ + { id: 10, sample: 10 }, + { id: 20, sample: 20 }, + { id: 30, sample: 30 } + ]) + const filtered = collection.betweenNumber('id', undefined, undefined) + chai.expect(filtered.nodes).to.eql([ + { id: 10, sample: 10 }, + { id: 20, sample: 20 }, + { id: 30, sample: 30 } + ]) + }) + it('returns all values if min/max are null', () => { + const collection = new NodeCollection([ + { id: 10, sample: 10 }, + { id: 20, sample: 20 }, + { id: 30, sample: 30 } + ]) + const filtered = collection.betweenNumber('id', null, null) + chai.expect(filtered.nodes).to.eql([ + { id: 10, sample: 10 }, + { id: 20, sample: 20 }, + { id: 30, sample: 30 } + ]) + }) + it('returns all values that are greater or equal a min value', () => { + const collection = new NodeCollection([ + { id: 10, sample: 10 }, + { id: 20, sample: 20 }, + { id: 30, sample: 30 } + ]) + const filtered = collection.betweenNumber('id', 20, null) + chai.expect(filtered.nodes).to.eql([ + { id: 20, sample: 20 }, + { id: 30, sample: 30 } + ]) + }) + it('returns all values that are less than or equal a max value', () => { + const collection = new NodeCollection([ + { id: 10, sample: 10 }, + { id: 20, sample: 20 }, + { id: 30, sample: 30 } + ]) + const filtered = collection.betweenNumber('id', null, 20) + chai.expect(filtered.nodes).to.eql([ + { id: 10, sample: 10 }, + { id: 20, sample: 20 } + ]) + }) + it('returns all values that between or equal a min and a max value', () => { + const collection = new NodeCollection([ + { id: 10, sample: 10 }, + { id: 20, sample: 20 }, + { id: 30, sample: 30 } + ]) + const filtered = collection.betweenNumber('id', 15, 25) + chai.expect(filtered.nodes).to.eql([{ id: 20, sample: 20 }]) + }) + }) + describe('#betweenDate', () => { + it('returns all date values if min/max are undefined', () => { + const collection = new NodeCollection([ + { id: 10, lastActive: new Date(2020, 11, 9, 0, 0) }, + { id: 20, lastActive: new Date(2020, 11, 10, 0, 0) }, + { id: 30, lastActive: new Date(2020, 11, 11, 0, 0) } + ]) + const filtered = collection.betweenDate( + 'lastActive', + undefined, + undefined + ) + chai.expect(filtered.nodes).to.eql([ + { id: 10, lastActive: new Date(2020, 11, 9, 0, 0) }, + { id: 20, lastActive: new Date(2020, 11, 10, 0, 0) }, + { id: 30, lastActive: new Date(2020, 11, 11, 0, 0) } + ]) + }) + it('returns all date values if min/max are null', () => { + const collection = new NodeCollection([ + { id: 10, lastActive: new Date(2020, 11, 9, 0, 0) }, + { id: 20, lastActive: new Date(2020, 11, 10, 0, 0) }, + { id: 30, lastActive: new Date(2020, 11, 11, 0, 0) } + ]) + const filtered = collection.betweenDate('lastActive', null, null) + chai.expect(filtered.nodes).to.eql([ + { id: 10, lastActive: new Date(2020, 11, 9, 0, 0) }, + { id: 20, lastActive: new Date(2020, 11, 10, 0, 0) }, + { id: 30, lastActive: new Date(2020, 11, 11, 0, 0) } + ]) + }) + it('returns all date values that are greater or equal a min date value', () => { + const collection = new NodeCollection([ + { id: 10, lastActive: new Date(2020, 11, 9, 0, 0) }, + { id: 20, lastActive: new Date(2020, 11, 10, 0, 0) }, + { id: 30, lastActive: new Date(2020, 11, 11, 0, 0) } + ]) + const filtered = collection.betweenDate( + 'lastActive', + new Date(2020, 11, 10, 0, 0), + null + ) + chai.expect(filtered.nodes).to.eql([ + { id: 20, lastActive: new Date(2020, 11, 10, 0, 0) }, + { id: 30, lastActive: new Date(2020, 11, 11, 0, 0) } + ]) + }) + it('returns all date values that are less than or equal a max date value', () => { + const collection = new NodeCollection([ + { id: 10, lastActive: new Date(2020, 11, 9, 0, 0) }, + { id: 20, lastActive: new Date(2020, 11, 10, 0, 0) }, + { id: 30, lastActive: new Date(2020, 11, 11, 0, 0) } + ]) + const filtered = collection.betweenDate( + 'lastActive', + null, + new Date(2020, 11, 10, 0, 0) + ) + chai.expect(filtered.nodes).to.eql([ + { id: 10, lastActive: new Date(2020, 11, 9, 0, 0) }, + { id: 20, lastActive: new Date(2020, 11, 10, 0, 0) } + ]) + }) + it('returns all date values that between or equal a min and a max date value', () => { + const collection = new NodeCollection([ + { id: 10, lastActive: new Date(2020, 11, 9, 0, 0) }, + { id: 20, lastActive: new Date(2020, 11, 10, 0, 0) }, + { id: 30, lastActive: new Date(2020, 11, 11, 0, 0) } + ]) + const filtered = collection.betweenDate( + 'lastActive', + new Date(2020, 11, 9, 12, 0), + new Date(2020, 11, 10, 12, 0) + ) + chai + .expect(filtered.nodes) + .to.eql([{ id: 20, lastActive: new Date(2020, 11, 10, 0, 0) }]) + }) + }) + describe('#values', () => { + it('returns a sorted list of unique values for a property - case ignored', () => { + const collection = new NodeCollection([ + { name: 'Giacomo' }, + { name: 'GiaCOMO' }, + { name: 'Birretta' }, + { name: 10 }, + { name: 'giacomo' }, + { name: 10 } + ]) + chai.expect(collection.values('name')).to.eql([10, 'Birretta', 'Giacomo']) + }) + }) +})