Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c6880377b | ||
|
|
0ba02f605c | ||
|
|
8b07d1be6d | ||
|
|
267fdbd9f0 | ||
|
|
f8b8856029 | ||
|
|
36cb344343 | ||
|
|
75d038777b | ||
|
|
9e8c15b8cf | ||
|
|
dda16a32c4 | ||
|
|
460bf24903 | ||
|
|
49bd482b7b | ||
|
|
a2c479af60 | ||
|
|
38b2673b82 |
@@ -2,8 +2,6 @@
|
|||||||
"parser": "@typescript-eslint/parser", // Specifies the ESLint parser
|
"parser": "@typescript-eslint/parser", // Specifies the ESLint parser
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin
|
"plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin
|
||||||
"prettier/@typescript-eslint", // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
|
|
||||||
"plugin:prettier/recommended" // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
|
|
||||||
],
|
],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 2018, // Allows for the parsing of modern ECMAScript features
|
"ecmaVersion": 2018, // Allows for the parsing of modern ECMAScript features
|
||||||
@@ -14,7 +12,7 @@
|
|||||||
"@typescript-eslint/camelcase": 0,
|
"@typescript-eslint/camelcase": 0,
|
||||||
"@typescript-eslint/no-non-null-assertion": 0,
|
"@typescript-eslint/no-non-null-assertion": 0,
|
||||||
"@typescript-eslint/explicit-function-return-type": [1, { "allowExpressions": true }],
|
"@typescript-eslint/explicit-function-return-type": [1, { "allowExpressions": true }],
|
||||||
"@typescript-eslint/no-namespace": 0
|
"@typescript-eslint/no-namespace": 0,
|
||||||
},
|
},
|
||||||
"ignorePatterns": ["dist/", "node_modules/", "*.js"]
|
"ignorePatterns": ["dist/", "node_modules/", "*.js"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,6 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
|
devtools/homeassistant/*
|
||||||
|
!devtools/homeassistant/configuration.yaml
|
||||||
|
!devtools/homeassistant/ui-lovelace.yaml
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
"dist/**": true
|
"dist/**": true
|
||||||
|
},
|
||||||
|
"files.associations": {
|
||||||
|
"*.yaml": "home-assistant"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ entity: input_datetime.alarm_time
|
|||||||
hour_mode: 12
|
hour_mode: 12
|
||||||
layout:
|
layout:
|
||||||
hour_mode: single
|
hour_mode: single
|
||||||
|
align_controls: right
|
||||||
hide:
|
hide:
|
||||||
name: true
|
name: true
|
||||||
```
|
```
|
||||||
@@ -89,26 +90,35 @@ type: 'custom:time-picker-card'
|
|||||||
entity: input_datetime.alarm_time
|
entity: input_datetime.alarm_time
|
||||||
layout:
|
layout:
|
||||||
name: inside
|
name: inside
|
||||||
align_controls: right
|
align_controls: center
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Dark theme, embedded layout
|
||||||
|

|
||||||
|
|
||||||
|
### Dark theme, thin layout
|
||||||
|

|
||||||
|
|
||||||
### With a custom lovelace theme
|
### With a custom lovelace theme
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
| Name | Type | Requirement | Description | Default |
|
| Name | Type | Requirement | Description | Default |
|
||||||
| ----------- | ------------ | ------------ | --------------------------------------------------------------------------------------------------------- | ------------------------ |
|
| ----------------- | ------------ | ------------ | --------------------------------------------------------------------------------------------------------- | ------------------------ |
|
||||||
| type | string | **Required** | `custom:time-picker-card` | |
|
| type | string | **Required** | `custom:time-picker-card` | |
|
||||||
| entity | string | **Required** | [Input Datetime](https://www.home-assistant.io/integrations/input_datetime/) entity with `has_time: true` | |
|
| entity | string | **Required** | [Input Datetime](https://www.home-assistant.io/integrations/input_datetime/) entity with `has_time: true` | |
|
||||||
| name | string | **Optional** | Card name | Entity's `friendly_name` |
|
| name | string | **Optional** | Card name | Entity's `friendly_name` |
|
||||||
| link_values | boolean | **Optional** | If enabled, will change hour when minutes overflow. E.g. will go from 11:55 to 12:00, instead of 11:00 | `false` |
|
| link_values | boolean | **Optional** | If enabled, will change hour when minutes overflow. E.g. will go from 11:55 to 12:00, instead of 11:00 | `false` |
|
||||||
| hour_mode | `12` or `24` | **Optional** | Hour format. If `12`, card will show AM/PM picker | `24` |
|
| hour_mode | `12` or `24` | **Optional** | Hour format. If `12`, card will show AM/PM picker | `24` |
|
||||||
| hour_step | number | **Optional** | Hour change when clicking arrows | `1` |
|
| hour_step | number | **Optional** | Hour change when clicking arrows | `1` |
|
||||||
| minute_step | number | **Optional** | Minute change when clicking arrows | `5` |
|
| minute_step | number | **Optional** | Minute change when clicking arrows | `5` |
|
||||||
| layout | object | **Optional** | Card Layout configuration | `none` |
|
| layout | object | **Optional** | Card Layout configuration | `none` |
|
||||||
| hide | object | **Optional** | Hide object | `none` |
|
| hide | object | **Optional** | Hide object | `none` |
|
||||||
|
| tap_action | action | **Optional** | Home assistant action to perform on tap | `more-info` |
|
||||||
|
| double_tap_action | action | **Optional** | Home assistant action to perform on tap | `more-info` |
|
||||||
|
| hold_action | action | **Optional** | Home assistant action to perform on tap | `more-info` |
|
||||||
|
|
||||||
### Layout Object
|
### Layout Object
|
||||||
|
|
||||||
@@ -118,14 +128,15 @@ layout:
|
|||||||
| align_controls | `left`, `center`, `right` | **Optional** | Horizontal alignment of the controls | `center` |
|
| align_controls | `left`, `center`, `right` | **Optional** | Horizontal alignment of the controls | `center` |
|
||||||
| name | `header`, `inside` | **Optional** | Whether to show the name as a header or inside the card | `header` |
|
| name | `header`, `inside` | **Optional** | Whether to show the name as a header or inside the card | `header` |
|
||||||
| embedded | boolean | **Optional** | Render with embedded style - disables padding, box shadow, and card header | `false` |
|
| embedded | boolean | **Optional** | Render with embedded style - disables padding, box shadow, and card header | `false` |
|
||||||
|
| thin | boolean | **Optional** | Render with reduced paddings | `false` |
|
||||||
|
|
||||||
### Hide Object
|
### Hide Object
|
||||||
|
|
||||||
| Name | Type | Requirement | Description | Default |
|
| Name | Type | Requirement | Description | Default |
|
||||||
| ------- | ------- | ------------ | ------------------- | ------- |
|
| ------- | ------- | ------------ | ------------------------------------------------- | ------- |
|
||||||
| name | boolean | **Optional** | Hides the card name | `false` |
|
| name | boolean | **Optional** | Hides the card name | `false` |
|
||||||
| icon | boolean | **Optional** | Hides the card icon (only with controls "inside") | `false` |
|
| icon | boolean | **Optional** | Hides the card icon (only with controls "inside") | `false` |
|
||||||
| seconds | boolean | **Optional** | Hides seconds input | `true` |
|
| seconds | boolean | **Optional** | Hides seconds input | `true` |
|
||||||
|
|
||||||
### Theme Variables
|
### Theme Variables
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,5 @@
|
|||||||
title: Home
|
title: Home
|
||||||
views:
|
views:
|
||||||
- title: Grid
|
|
||||||
path: grid
|
|
||||||
cards:
|
|
||||||
- type: grid
|
|
||||||
cards:
|
|
||||||
- type: button
|
|
||||||
icon: mdi:reload
|
|
||||||
name: Theme
|
|
||||||
tap_action:
|
|
||||||
action: call-service
|
|
||||||
service: frontend.reload_themes
|
|
||||||
- type: custom:time-picker-card
|
|
||||||
entity: input_datetime.test_date_time
|
|
||||||
link_values: true
|
|
||||||
hour_mode: 12
|
|
||||||
hour_step: 2
|
|
||||||
minute_step: 3
|
|
||||||
layout:
|
|
||||||
hour_mode: single
|
|
||||||
name: inside
|
|
||||||
hide:
|
|
||||||
name: false
|
|
||||||
- type: entities
|
|
||||||
entities:
|
|
||||||
- input_datetime.test_date_time
|
|
||||||
- input_datetime.test_date_time
|
|
||||||
- input_datetime.test_date_time
|
|
||||||
- input_datetime.test_date_time
|
|
||||||
- input_datetime.test_date_time
|
|
||||||
- input_datetime.test_date_time
|
|
||||||
|
|
||||||
|
|
||||||
- title: dev
|
- title: dev
|
||||||
path: dev
|
path: dev
|
||||||
cards:
|
cards:
|
||||||
@@ -46,21 +14,45 @@ views:
|
|||||||
cards:
|
cards:
|
||||||
- type: custom:time-picker-card
|
- type: custom:time-picker-card
|
||||||
entity: input_datetime.test_date_time
|
entity: input_datetime.test_date_time
|
||||||
name: Alarm time
|
name: With tap actions
|
||||||
hour_mode: 24
|
hour_mode: 24
|
||||||
hour_step: 2
|
hour_step: 2
|
||||||
|
tap_action:
|
||||||
|
action: call-service
|
||||||
|
service: logbook.log
|
||||||
|
service_data:
|
||||||
|
name: "Time picker card · Tap Action"
|
||||||
|
message: "Debug"
|
||||||
|
double_tap_action:
|
||||||
|
action: call-service
|
||||||
|
service: logbook.log
|
||||||
|
service_data:
|
||||||
|
name: "Time picker card · Double Tap Action"
|
||||||
|
message: "Debug"
|
||||||
|
hold_action:
|
||||||
|
action: call-service
|
||||||
|
service: logbook.log
|
||||||
|
service_data:
|
||||||
|
name: "Time picker card · Hold action"
|
||||||
|
message: "Debug"
|
||||||
- type: custom:time-picker-card
|
- type: custom:time-picker-card
|
||||||
|
name: Hour mode 12, thin, single, hidden name
|
||||||
entity: input_datetime.test_date_time
|
entity: input_datetime.test_date_time
|
||||||
hour_mode: 12
|
hour_mode: 12
|
||||||
layout:
|
layout:
|
||||||
hour_mode: single
|
hour_mode: single
|
||||||
|
thin: true
|
||||||
hide:
|
hide:
|
||||||
name: true
|
name: true
|
||||||
|
|
||||||
- type: custom:time-picker-card
|
- type: custom:time-picker-card
|
||||||
entity: input_datetime.test_date_time
|
entity: input_datetime.test_date_time
|
||||||
|
name: Thin
|
||||||
|
layout:
|
||||||
|
thin: true
|
||||||
- type: custom:time-picker-card
|
- type: custom:time-picker-card
|
||||||
entity: input_datetime.test_date_time
|
entity: input_datetime.test_date_time
|
||||||
|
name: Hidden seconds
|
||||||
hide:
|
hide:
|
||||||
seconds: false
|
seconds: false
|
||||||
|
|
||||||
@@ -68,11 +60,13 @@ views:
|
|||||||
cards:
|
cards:
|
||||||
- type: custom:time-picker-card
|
- type: custom:time-picker-card
|
||||||
entity: input_datetime.test_date_time
|
entity: input_datetime.test_date_time
|
||||||
|
name: Hidden name
|
||||||
hour_mode: 12
|
hour_mode: 12
|
||||||
hide:
|
hide:
|
||||||
name: true
|
name: true
|
||||||
- type: custom:time-picker-card
|
- type: custom:time-picker-card
|
||||||
entity: input_datetime.test_date_time
|
entity: input_datetime.test_date_time
|
||||||
|
name: Linked, single hour mode, hidden name
|
||||||
link_values: true
|
link_values: true
|
||||||
hour_mode: 12
|
hour_mode: 12
|
||||||
hour_step: 2
|
hour_step: 2
|
||||||
@@ -81,3 +75,73 @@ views:
|
|||||||
hour_mode: single
|
hour_mode: single
|
||||||
hide:
|
hide:
|
||||||
name: false
|
name: false
|
||||||
|
|
||||||
|
- type: custom:time-picker-card
|
||||||
|
entity: input_datetime.test_date
|
||||||
|
name: Error
|
||||||
|
|
||||||
|
- title: name inside
|
||||||
|
path: name-inside
|
||||||
|
cards:
|
||||||
|
- type: custom:time-picker-card
|
||||||
|
entity: input_datetime.test_date_time
|
||||||
|
name: Inside, controls right
|
||||||
|
hour_mode: 12
|
||||||
|
layout:
|
||||||
|
name: inside
|
||||||
|
align_controls: right
|
||||||
|
- type: horizontal-stack
|
||||||
|
cards:
|
||||||
|
- type: custom:time-picker-card
|
||||||
|
entity: input_datetime.test_date_time
|
||||||
|
name: Inside, hidden name
|
||||||
|
layout:
|
||||||
|
name: inside
|
||||||
|
hide:
|
||||||
|
name: true
|
||||||
|
- type: custom:time-picker-card
|
||||||
|
entity: input_datetime.test_date_time
|
||||||
|
name: Inside, hidden icon
|
||||||
|
layout:
|
||||||
|
name: inside
|
||||||
|
hide:
|
||||||
|
icon: true
|
||||||
|
|
||||||
|
- title: embedded
|
||||||
|
path: embedded
|
||||||
|
cards:
|
||||||
|
- type: vertical-stack
|
||||||
|
cards:
|
||||||
|
|
||||||
|
- type: entities
|
||||||
|
entities:
|
||||||
|
- input_datetime.test_date_time
|
||||||
|
- type: custom:time-picker-card
|
||||||
|
entity: input_datetime.test_date_time
|
||||||
|
name: Embedded thin
|
||||||
|
hour_mode: 24
|
||||||
|
hour_step: 2
|
||||||
|
layout:
|
||||||
|
embedded: true
|
||||||
|
thin: true
|
||||||
|
|
||||||
|
- type: custom:time-picker-card
|
||||||
|
entity: input_datetime.test_date_time
|
||||||
|
name: Inside
|
||||||
|
hour_mode: 24
|
||||||
|
hour_step: 2
|
||||||
|
layout:
|
||||||
|
name: inside
|
||||||
|
|
||||||
|
- type: custom:time-picker-card
|
||||||
|
entity: input_datetime.test_date_time
|
||||||
|
hour_mode: 12
|
||||||
|
layout:
|
||||||
|
hour_mode: single
|
||||||
|
hide:
|
||||||
|
name: true
|
||||||
|
- type: custom:time-picker-card
|
||||||
|
entity: input_datetime.test_date_time
|
||||||
|
hour_mode: 12
|
||||||
|
layout:
|
||||||
|
hour_mode: single
|
||||||
|
|||||||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 142 KiB |
@@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest/presets/js-with-babel',
|
||||||
|
testRegex: '/test/.*\\.test?\\.ts$',
|
||||||
|
transformIgnorePatterns: [
|
||||||
|
'node_modules/(?!(testing-library__dom|@open-wc|lit-html|lit-element|pure-lit|lit-element-state-decoupler)/)',
|
||||||
|
],
|
||||||
|
moduleFileExtensions: ['ts', 'js'],
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "time-picker-card",
|
"name": "time-picker-card",
|
||||||
"version": "1.2.1",
|
"version": "1.3.1",
|
||||||
"author": "Georgi Gardev <georgi@gardev.com>",
|
"author": "Georgi Gardev <georgi@gardev.com>",
|
||||||
"description": "Lovelace time-picker-card",
|
"description": "Lovelace time-picker-card",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -15,47 +15,36 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "git@github.com:GeorgeSG/lovelace-time-picker-card.git",
|
"repository": "git@github.com:GeorgeSG/lovelace-time-picker-card.git",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "rollup -c --watch",
|
"start": "rollup -c --watch --bundleConfigAsCjs",
|
||||||
"build": "npm run lint && npm run rollup",
|
"build": "npm run lint && npm run rollup",
|
||||||
"lint": "eslint src/*.ts",
|
"lint": "eslint src/*.ts",
|
||||||
"rollup": "rollup -c",
|
"rollup": "rollup -c --bundleConfigAsCjs",
|
||||||
"test": "./node_modules/karma/bin/karma start ./test/karma.conf.js",
|
"test": "jest",
|
||||||
"release-patch": "npm version patch && git push origin master --tags",
|
"release-patch": "npm version patch && git push origin master --tags",
|
||||||
"release-minor": "npm version minor && git push origin master --tags",
|
"release-minor": "npm version minor && git push origin master --tags",
|
||||||
"release-major": "npm version major && git push origin master --tags",
|
"release-major": "npm version major && git push origin master --tags",
|
||||||
"start:ha": "docker run --rm -p8124:8123 -v $(pwd)/devtools/homeassistant:/config homeassistant/home-assistant:latest",
|
"start:ha": "docker run --rm -p8124:8123 -v $(pwd)/devtools/homeassistant:/config homeassistant/home-assistant:latest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"custom-card-helpers": "^1.6.6",
|
"custom-card-helpers": "^1.9.0",
|
||||||
"home-assistant-js-websocket": "^5.7.0",
|
"home-assistant-js-websocket": "^8.0.0",
|
||||||
"lit-element": "^2.4.0",
|
"lit": "^2.4.0"
|
||||||
"lit-html": "^1.3.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.7",
|
"@el3um4s/rollup-plugin-terser": "^1.0.2",
|
||||||
"@open-wc/testing": "^2.5.32",
|
"@rollup/plugin-babel": "^6.0.2",
|
||||||
"@rollup/plugin-commonjs": "^11.1.0",
|
"@rollup/plugin-commonjs": "^23.0.2",
|
||||||
"@rollup/plugin-json": "^4.1.0",
|
"@rollup/plugin-json": "^5.0.1",
|
||||||
"@rollup/plugin-node-resolve": "^7.1.3",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
"@rollup/plugin-typescript": "^4.1.2",
|
"@rollup/plugin-typescript": "^9.0.2",
|
||||||
"@types/chai": "^4.2.14",
|
"@types/jest": "^29.2.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.34.0",
|
"@typescript-eslint/eslint-plugin": "^5.42.0",
|
||||||
"@typescript-eslint/parser": "^2.34.0",
|
"eslint": "^8.26.0",
|
||||||
"eslint": "^6.8.0",
|
"jest": "^29.2.2",
|
||||||
"eslint-config-prettier": "^6.15.0",
|
"prettier": "^2.7.1",
|
||||||
"eslint-plugin-prettier": "^3.1.4",
|
"rollup": "^3.2.5",
|
||||||
"karma": "^6.3.16",
|
"rollup-plugin-serve": "^2.0.1",
|
||||||
"karma-chai": "^0.1.0",
|
"ts-jest": "^29.0.3",
|
||||||
"karma-chrome-launcher": "^3.1.0",
|
"typescript": "^4.8.4"
|
||||||
"karma-mocha": "^2.0.1",
|
|
||||||
"karma-rollup-preprocessor": "^7.0.5",
|
|
||||||
"mocha": "^7.2.0",
|
|
||||||
"prettier": "^2.2.0",
|
|
||||||
"rollup": "^1.32.1",
|
|
||||||
"rollup-plugin-babel": "^4.4.0",
|
|
||||||
"rollup-plugin-serve": "^1.1.0",
|
|
||||||
"rollup-plugin-terser": "^5.3.1",
|
|
||||||
"sinon": "^9.2.1",
|
|
||||||
"typescript": "^3.9.7"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import nodeResolve from '@rollup/plugin-node-resolve';
|
import nodeResolve from '@rollup/plugin-node-resolve';
|
||||||
import json from '@rollup/plugin-json';
|
import json from '@rollup/plugin-json';
|
||||||
import typescript from '@rollup/plugin-typescript';
|
import typescript from '@rollup/plugin-typescript';
|
||||||
import { terser } from 'rollup-plugin-terser';
|
import { terser } from '@el3um4s/rollup-plugin-terser';
|
||||||
import babel from 'rollup-plugin-babel';
|
import babel from '@rollup/plugin-babel';
|
||||||
import serve from 'rollup-plugin-serve';
|
import serve from 'rollup-plugin-serve';
|
||||||
|
|
||||||
const dev = process.env.ROLLUP_WATCH;
|
const dev = process.env.ROLLUP_WATCH;
|
||||||
|
|
||||||
const serveOptions = {
|
const serveOptions = {
|
||||||
contentBase: ['./dist'],
|
contentBase: ['./dist'],
|
||||||
host: '0.0.0.0',
|
host: 'localhost',
|
||||||
port: 5000,
|
port: 5000,
|
||||||
allowCrossOrigin: true,
|
allowCrossOrigin: true,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -24,9 +24,9 @@ export default {
|
|||||||
format: 'es',
|
format: 'es',
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
typescript({ exclude: ['test/**/*'] }),
|
||||||
nodeResolve(),
|
nodeResolve(),
|
||||||
json(),
|
json(),
|
||||||
typescript(),
|
|
||||||
babel({
|
babel({
|
||||||
exclude: 'node_modules/**',
|
exclude: 'node_modules/**',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
import { noChange } from 'lit';
|
||||||
|
import { AttributePart, directive, Directive, DirectiveParameters } from 'lit/directive.js';
|
||||||
|
|
||||||
|
import { fireEvent } from 'custom-card-helpers';
|
||||||
|
import { ActionHandlerDetail, ActionHandlerOptions } from 'custom-card-helpers/dist/types';
|
||||||
|
|
||||||
|
const isTouch =
|
||||||
|
'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0;
|
||||||
|
|
||||||
|
interface ActionHandler extends HTMLElement {
|
||||||
|
holdTime: number;
|
||||||
|
bind(element: Element, options): void;
|
||||||
|
}
|
||||||
|
interface ActionHandlerElement extends HTMLElement {
|
||||||
|
actionHandler?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HASSDomEvents {
|
||||||
|
action: ActionHandlerDetail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ActionHandler extends HTMLElement implements ActionHandler {
|
||||||
|
public holdTime = 500;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
public ripple: any;
|
||||||
|
|
||||||
|
protected timer?: number;
|
||||||
|
|
||||||
|
protected held = false;
|
||||||
|
|
||||||
|
private dblClickTimeout?: number;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.ripple = document.createElement('mwc-ripple');
|
||||||
|
}
|
||||||
|
|
||||||
|
public connectedCallback(): void {
|
||||||
|
Object.assign(this.style, {
|
||||||
|
position: 'absolute',
|
||||||
|
width: isTouch ? '100px' : '50px',
|
||||||
|
height: isTouch ? '100px' : '50px',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
zIndex: '999',
|
||||||
|
});
|
||||||
|
|
||||||
|
this.appendChild(this.ripple);
|
||||||
|
this.ripple.primary = true;
|
||||||
|
|
||||||
|
['touchcancel', 'mouseout', 'mouseup', 'touchmove', 'mousewheel', 'wheel', 'scroll'].forEach(
|
||||||
|
(ev) => {
|
||||||
|
document.addEventListener(
|
||||||
|
ev,
|
||||||
|
() => {
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
this.stopAnimation();
|
||||||
|
this.timer = undefined;
|
||||||
|
},
|
||||||
|
{ passive: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bind(element: ActionHandlerElement, options): void {
|
||||||
|
if (element.actionHandler) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
element.actionHandler = true;
|
||||||
|
|
||||||
|
element.addEventListener('contextmenu', (ev: Event) => {
|
||||||
|
const e = ev || window.event;
|
||||||
|
if (e.preventDefault) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
if (e.stopPropagation) {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
e.cancelBubble = true;
|
||||||
|
e.returnValue = false;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const start = (ev: Event): void => {
|
||||||
|
this.held = false;
|
||||||
|
let x;
|
||||||
|
let y;
|
||||||
|
if ((ev as TouchEvent).touches) {
|
||||||
|
x = (ev as TouchEvent).touches[0].pageX;
|
||||||
|
y = (ev as TouchEvent).touches[0].pageY;
|
||||||
|
} else {
|
||||||
|
x = (ev as MouseEvent).pageX;
|
||||||
|
y = (ev as MouseEvent).pageY;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timer = window.setTimeout(() => {
|
||||||
|
this.startAnimation(x, y);
|
||||||
|
this.held = true;
|
||||||
|
}, this.holdTime);
|
||||||
|
};
|
||||||
|
|
||||||
|
const end = (ev: Event): void => {
|
||||||
|
// Prevent mouse event if touch event
|
||||||
|
ev.preventDefault();
|
||||||
|
if (['touchend', 'touchcancel'].includes(ev.type) && this.timer === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
this.stopAnimation();
|
||||||
|
this.timer = undefined;
|
||||||
|
if (this.held) {
|
||||||
|
fireEvent(element, 'action', { action: 'hold' });
|
||||||
|
} else if (options.hasDoubleClick) {
|
||||||
|
if ((ev.type === 'click' && (ev as MouseEvent).detail < 2) || !this.dblClickTimeout) {
|
||||||
|
this.dblClickTimeout = window.setTimeout(() => {
|
||||||
|
this.dblClickTimeout = undefined;
|
||||||
|
fireEvent(element, 'action', { action: 'tap' });
|
||||||
|
}, 250);
|
||||||
|
} else {
|
||||||
|
clearTimeout(this.dblClickTimeout);
|
||||||
|
this.dblClickTimeout = undefined;
|
||||||
|
fireEvent(element, 'action', { action: 'double_tap' });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fireEvent(element, 'action', { action: 'tap' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEnter = (ev: KeyboardEvent): void => {
|
||||||
|
if (ev.keyCode !== 13) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
end(ev);
|
||||||
|
};
|
||||||
|
|
||||||
|
element.addEventListener('touchstart', start, { passive: true });
|
||||||
|
element.addEventListener('touchend', end);
|
||||||
|
element.addEventListener('touchcancel', end);
|
||||||
|
|
||||||
|
element.addEventListener('mousedown', start, { passive: true });
|
||||||
|
element.addEventListener('click', end);
|
||||||
|
|
||||||
|
element.addEventListener('keyup', handleEnter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private startAnimation(x: number, y: number): void {
|
||||||
|
Object.assign(this.style, {
|
||||||
|
left: `${x}px`,
|
||||||
|
top: `${y}px`,
|
||||||
|
display: null,
|
||||||
|
});
|
||||||
|
this.ripple.disabled = false;
|
||||||
|
this.ripple.active = true;
|
||||||
|
this.ripple.unbounded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private stopAnimation(): void {
|
||||||
|
this.ripple.active = false;
|
||||||
|
this.ripple.disabled = true;
|
||||||
|
this.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO You need to replace all instances of "action-handler-boilerplate" with "action-handler-<your card name>"
|
||||||
|
customElements.define('action-handler-boilerplate', ActionHandler);
|
||||||
|
|
||||||
|
const getActionHandler = (): ActionHandler => {
|
||||||
|
const body = document.body;
|
||||||
|
if (body.querySelector('action-handler-boilerplate')) {
|
||||||
|
return body.querySelector('action-handler-boilerplate') as ActionHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionhandler = document.createElement('action-handler-boilerplate');
|
||||||
|
body.appendChild(actionhandler);
|
||||||
|
|
||||||
|
return actionhandler as ActionHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actionHandlerBind = (
|
||||||
|
element: ActionHandlerElement,
|
||||||
|
options?: ActionHandlerOptions
|
||||||
|
): void => {
|
||||||
|
const actionhandler: ActionHandler = getActionHandler();
|
||||||
|
if (!actionhandler) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
actionhandler.bind(element, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actionHandler = directive(
|
||||||
|
class extends Directive {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
|
update(part: AttributePart, [options]: DirectiveParameters<this>) {
|
||||||
|
actionHandlerBind(part.element as ActionHandlerElement, options);
|
||||||
|
return noChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars, @typescript-eslint/explicit-function-return-type
|
||||||
|
render(_options?: ActionHandlerOptions) {}
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -1,13 +1,6 @@
|
|||||||
import {
|
import { css, CSSResult, html, LitElement, TemplateResult } from 'lit';
|
||||||
css,
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
CSSResult,
|
import { ClassInfo, classMap } from 'lit/directives/class-map.js';
|
||||||
customElement,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
property,
|
|
||||||
TemplateResult,
|
|
||||||
} from 'lit-element';
|
|
||||||
import { ClassInfo, classMap } from 'lit-html/directives/class-map';
|
|
||||||
import { Layout, Period } from '../types';
|
import { Layout, Period } from '../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
import {
|
import { LitElement, TemplateResult, html, CSSResult, css } from 'lit';
|
||||||
css,
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
CSSResult,
|
|
||||||
customElement,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
property,
|
|
||||||
TemplateResult,
|
|
||||||
} from 'lit-element';
|
|
||||||
import { TimeUnit } from '../models/time-unit';
|
import { TimeUnit } from '../models/time-unit';
|
||||||
import { Direction } from '../types';
|
import { Direction } from '../types';
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export const ENTITY_DOMAIN = 'input_datetime';
|
|||||||
export const DEFAULT_HOUR_STEP = 1;
|
export const DEFAULT_HOUR_STEP = 1;
|
||||||
export const DEFAULT_MINUTE_STEP = 5;
|
export const DEFAULT_MINUTE_STEP = 5;
|
||||||
export const DEFAULT_SECOND_STEP = 5;
|
export const DEFAULT_SECOND_STEP = 5;
|
||||||
export const DEFAULT_LAYOUT_HOUR_MODE = 'double';
|
export const DEFAULT_HOUR_MODE = 24;
|
||||||
|
export const DEFAULT_LAYOUT_HOUR_MODE: Layout.HourMode = 'double';
|
||||||
export const DEFAULT_LAYOUT_ALIGN_CONTROLS = Layout.AlignControls.CENTER;
|
export const DEFAULT_LAYOUT_ALIGN_CONTROLS = Layout.AlignControls.CENTER;
|
||||||
export const DEFAULT_LAYOUT_NAME = Layout.Name.HEADER;
|
export const DEFAULT_LAYOUT_NAME = Layout.Name.HEADER;
|
||||||
|
|||||||
@@ -1,272 +1,151 @@
|
|||||||
import { computeDomain, HomeAssistant, LovelaceCardEditor } from 'custom-card-helpers';
|
import { HomeAssistant, LovelaceCardEditor } from 'custom-card-helpers';
|
||||||
import { HassEntity } from 'home-assistant-js-websocket';
|
import { html, LitElement, TemplateResult } from 'lit';
|
||||||
import {
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
css,
|
import { TimePickerCardConfig } from './types';
|
||||||
CSSResult,
|
|
||||||
customElement,
|
const NAME_TO_LABEL_MAP = {
|
||||||
html,
|
entity: 'input_datetime entity id',
|
||||||
LitElement,
|
name: 'Name',
|
||||||
property,
|
hour_step: 'Hour step',
|
||||||
TemplateResult,
|
minute_step: 'Minute step',
|
||||||
} from 'lit-element';
|
hour_mode: 'Hour mode',
|
||||||
import {
|
link_values: 'Link values',
|
||||||
DEFAULT_HOUR_STEP,
|
align_controls: 'Align controls',
|
||||||
DEFAULT_MINUTE_STEP,
|
embedded: 'Embedded?',
|
||||||
ENTITY_DOMAIN,
|
thin: 'Thin layout?',
|
||||||
DEFAULT_LAYOUT_ALIGN_CONTROLS,
|
icon: 'Icon',
|
||||||
DEFAULT_LAYOUT_NAME,
|
seconds: 'Seconds',
|
||||||
DEFAULT_SECOND_STEP,
|
};
|
||||||
} from './const';
|
|
||||||
import { TimePickerCardConfig, Layout, HourMode } from './types';
|
const SCHEMA = [
|
||||||
|
{ name: 'entity', selector: { entity: { domain: 'input_datetime' } } },
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
selector: { text: {} },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'grid',
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
name: 'hour_step',
|
||||||
|
type: 'integer',
|
||||||
|
required: true,
|
||||||
|
default: 1,
|
||||||
|
valueMin: 1,
|
||||||
|
valueMax: 24,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'minute_step',
|
||||||
|
type: 'integer',
|
||||||
|
required: true,
|
||||||
|
default: 5,
|
||||||
|
valueMin: 1,
|
||||||
|
valueMax: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'hour_mode',
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
[12, '12'],
|
||||||
|
[24, '24'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: 'link_values', type: 'boolean' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'expandable',
|
||||||
|
name: 'layout',
|
||||||
|
title: 'Layout controls',
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
name: 'hour_mode',
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
['single', 'single'],
|
||||||
|
['double', 'double'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'align_controls',
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
['left', 'left'],
|
||||||
|
['center', 'center'],
|
||||||
|
['right', 'right'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'select',
|
||||||
|
options: [
|
||||||
|
['header', 'header'],
|
||||||
|
['inside', 'inside'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: 'embedded', type: 'boolean' },
|
||||||
|
{ name: 'thin', type: 'boolean' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'expandable',
|
||||||
|
name: 'hide',
|
||||||
|
title: 'Hide controls',
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
type: 'grid',
|
||||||
|
name: '',
|
||||||
|
schema: [
|
||||||
|
{ name: 'name', type: 'boolean' },
|
||||||
|
{ name: 'icon', type: 'boolean' },
|
||||||
|
{ name: 'seconds', type: 'boolean' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'expandable',
|
||||||
|
title: 'Actions',
|
||||||
|
schema: [
|
||||||
|
{ name: 'tap_action', selector: { action: {} } },
|
||||||
|
{ name: 'double_tap_action', selector: { action: {} } },
|
||||||
|
{ name: 'hold_action', selector: { action: {} } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
@customElement('time-picker-card-editor')
|
@customElement('time-picker-card-editor')
|
||||||
export class TimePickerCardEditor extends LitElement implements LovelaceCardEditor {
|
export class TimePickerCardEditor extends LitElement implements LovelaceCardEditor {
|
||||||
private static readonly NUMBER_PROPERTIES = [
|
|
||||||
'hour_step',
|
|
||||||
'minute_step',
|
|
||||||
'second_step',
|
|
||||||
'hour_mode',
|
|
||||||
];
|
|
||||||
private static readonly CONFIG_CHANGED_EVENT = 'config-changed';
|
private static readonly CONFIG_CHANGED_EVENT = 'config-changed';
|
||||||
|
|
||||||
@property({ type: Object }) hass!: HomeAssistant;
|
@property({ type: Object }) hass!: HomeAssistant;
|
||||||
@property() private config!: TimePickerCardConfig;
|
@property() private config!: TimePickerCardConfig;
|
||||||
|
|
||||||
private get entity(): HassEntity | undefined {
|
private computeLabel({ name }): string {
|
||||||
return this.hass.states[this.config.entity];
|
return NAME_TO_LABEL_MAP[name] || name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get datetimeEntities(): Array<string> {
|
private valueChanged(ev: CustomEvent): void {
|
||||||
return Object.keys(this.hass.states).filter(
|
const newConfig = { ...this.config, ...ev.detail.value };
|
||||||
(entityId) =>
|
this.dispatch(newConfig);
|
||||||
computeDomain(entityId) === ENTITY_DOMAIN &&
|
|
||||||
this.hass.states[entityId].attributes.has_time === true
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
return html`<div class="card-config">
|
return html`
|
||||||
<paper-dropdown-menu
|
<ha-form
|
||||||
style="width: 100%"
|
.hass=${this.hass}
|
||||||
label="Entity (Required)"
|
.data=${this.config}
|
||||||
.configValue=${'entity'}
|
.schema=${SCHEMA}
|
||||||
@value-changed=${this.onValueChange}
|
.computeLabel=${this.computeLabel}
|
||||||
>
|
@value-changed=${this.valueChanged}
|
||||||
<paper-listbox
|
></ha-form>
|
||||||
slot="dropdown-content"
|
`;
|
||||||
.selected=${this.datetimeEntities.indexOf(this.config.entity)}
|
|
||||||
>
|
|
||||||
${this.datetimeEntities.map((entity) => html`<paper-item>${entity}</paper-item>`)}
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-dropdown-menu>
|
|
||||||
<paper-input
|
|
||||||
label="Name (Optional)"
|
|
||||||
.configValue=${'name'}
|
|
||||||
.value=${this.config.name}
|
|
||||||
.placeholder=${this.entity?.attributes.friendly_name}
|
|
||||||
@value-changed=${this.onValueChange}
|
|
||||||
></paper-input>
|
|
||||||
<div class="side-by-side">
|
|
||||||
<div>
|
|
||||||
<ha-switch
|
|
||||||
style="margin-left: 10px"
|
|
||||||
.checked="${!Boolean(this.config.hide?.name)}"
|
|
||||||
@change="${this.onHideNameChange}"
|
|
||||||
></ha-switch>
|
|
||||||
Show name?
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<ha-switch
|
|
||||||
style="margin-left: 10px"
|
|
||||||
.checked="${!Boolean(this.config.hide?.icon)}"
|
|
||||||
@change="${this.onHideIconChange}"
|
|
||||||
></ha-switch>
|
|
||||||
Show icon?
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<paper-dropdown-menu
|
|
||||||
style="width: 100%"
|
|
||||||
label="Name Position (Optional)"
|
|
||||||
@value-changed=${this.onLayoutNameChange}
|
|
||||||
>
|
|
||||||
<paper-listbox
|
|
||||||
slot="dropdown-content"
|
|
||||||
.selected=${Object.values(Layout.Name).indexOf(
|
|
||||||
this.config.layout?.name ?? DEFAULT_LAYOUT_NAME
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
${Object.values(Layout.Name).map((name) => html`<paper-item>${name}</paper-item>`)}
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-dropdown-menu>
|
|
||||||
<div class="side-by-side">
|
|
||||||
<paper-input
|
|
||||||
type="number"
|
|
||||||
label="Hour Step (Optional)"
|
|
||||||
.configValue=${'hour_step'}
|
|
||||||
.value=${this.config.hour_step || DEFAULT_HOUR_STEP}
|
|
||||||
@value-changed=${this.onValueChange}
|
|
||||||
></paper-input>
|
|
||||||
<paper-input
|
|
||||||
type="number"
|
|
||||||
label="Minute Step (Optional)"
|
|
||||||
.configValue=${'minute_step'}
|
|
||||||
.value=${this.config.minute_step || DEFAULT_MINUTE_STEP}
|
|
||||||
@value-changed=${this.onValueChange}
|
|
||||||
></paper-input>
|
|
||||||
</div>
|
|
||||||
<div class="side-by-side">
|
|
||||||
<div>
|
|
||||||
<ha-switch
|
|
||||||
style="margin-left: 10px"
|
|
||||||
.checked="${this.config.hide?.seconds === false}"
|
|
||||||
@change="${this.onHideSecondsChange}"
|
|
||||||
></ha-switch>
|
|
||||||
Show seconds?
|
|
||||||
</div>
|
|
||||||
<paper-input
|
|
||||||
type="number"
|
|
||||||
label="Second Step (Optional)"
|
|
||||||
.configValue=${'second_step'}
|
|
||||||
.value=${this.config.second_step || DEFAULT_SECOND_STEP}
|
|
||||||
@value-changed=${this.onValueChange}
|
|
||||||
></paper-input>
|
|
||||||
</div>
|
|
||||||
<div class="side-by-side">
|
|
||||||
<div>
|
|
||||||
<ha-switch
|
|
||||||
.checked="${this.config.hour_mode === 12}"
|
|
||||||
@change="${this.onHourModeChange}"
|
|
||||||
></ha-switch>
|
|
||||||
12-Hour mode
|
|
||||||
</div>
|
|
||||||
${this.config.hour_mode === 12
|
|
||||||
? html`<div>
|
|
||||||
<ha-switch
|
|
||||||
.checked="${this.config.layout?.hour_mode === 'single'}"
|
|
||||||
@change="${this.onHourModeLayoutChange}"
|
|
||||||
></ha-switch>
|
|
||||||
"Single" hour mode layout
|
|
||||||
</div>`
|
|
||||||
: ''}
|
|
||||||
</div>
|
|
||||||
<div class="side-by-side">
|
|
||||||
<paper-dropdown-menu
|
|
||||||
style="width: 100%"
|
|
||||||
label="Align Controls (Optional)"
|
|
||||||
@value-changed=${this.onLayoutAlignChange}
|
|
||||||
>
|
|
||||||
<paper-listbox
|
|
||||||
slot="dropdown-content"
|
|
||||||
.selected=${Object.values(Layout.AlignControls).indexOf(
|
|
||||||
this.config.layout?.align_controls ?? DEFAULT_LAYOUT_ALIGN_CONTROLS
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
${Object.values(Layout.AlignControls).map((a) => html`<paper-item>${a}</paper-item>`)}
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-dropdown-menu>
|
|
||||||
<div>
|
|
||||||
<ha-switch
|
|
||||||
.checked="${this.config.link_values}"
|
|
||||||
@change="${this.onLinkValuesChange}"
|
|
||||||
></ha-switch>
|
|
||||||
Link Values
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="side-by-side">
|
|
||||||
<div>
|
|
||||||
<ha-switch
|
|
||||||
.checked="${this.config.layout?.embedded}"
|
|
||||||
@change="${this.onEmbeddedChange}"
|
|
||||||
></ha-switch>
|
|
||||||
Embedded
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setConfig(config): void {
|
setConfig(config): void {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onHourModeChange({ target: { checked } }): void {
|
|
||||||
const hourMode: HourMode = checked ? 12 : 24;
|
|
||||||
|
|
||||||
const newConfig = { ...this.config, hour_mode: hourMode };
|
|
||||||
this.dispatch(newConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onHideNameChange({ target: { checked } }): void {
|
|
||||||
const hide = { ...this.config.hide, name: !checked };
|
|
||||||
const newConfig = { ...this.config, hide };
|
|
||||||
this.dispatch(newConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onHideIconChange({ target: { checked } }): void {
|
|
||||||
const hide = { ...this.config.hide, icon: !checked };
|
|
||||||
const newConfig = { ...this.config, hide };
|
|
||||||
this.dispatch(newConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onHideSecondsChange({ target: { checked } }): void {
|
|
||||||
const hide = { ...this.config.hide, seconds: !checked };
|
|
||||||
const newConfig = { ...this.config, hide };
|
|
||||||
this.dispatch(newConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onLayoutAlignChange({ detail: { value } }: CustomEvent): void {
|
|
||||||
const layout = { ...this.config.layout, align_controls: value };
|
|
||||||
const newConfig = { ...this.config, layout };
|
|
||||||
this.dispatch(newConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onLayoutNameChange({ detail: { value } }: CustomEvent): void {
|
|
||||||
const layout = { ...this.config.layout, name: value };
|
|
||||||
const newConfig = { ...this.config, layout };
|
|
||||||
this.dispatch(newConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onHourModeLayoutChange({ target: { checked } }): void {
|
|
||||||
const hourModeLayout: Layout.HourMode = checked ? 'single' : 'double';
|
|
||||||
|
|
||||||
const newConfig = {
|
|
||||||
...this.config,
|
|
||||||
layout: { ...this.config.layout, hour_mode: hourModeLayout },
|
|
||||||
};
|
|
||||||
this.dispatch(newConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onLinkValuesChange({ target: { checked } }): void {
|
|
||||||
const newConfig = { ...this.config, link_values: checked };
|
|
||||||
this.dispatch(newConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onEmbeddedChange({ target: { checked } }): void {
|
|
||||||
const newConfig = { ...this.config, layout: { embedded: checked } };
|
|
||||||
this.dispatch(newConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onValueChange(e: CustomEvent): void {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const property = (e.target as any).configValue;
|
|
||||||
let value = e.detail.value;
|
|
||||||
|
|
||||||
if (TimePickerCardEditor.NUMBER_PROPERTIES.indexOf(property) > -1) {
|
|
||||||
value = parseInt(value);
|
|
||||||
|
|
||||||
if (isNaN(value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.config[property] === value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newConfig = { ...this.config, [property]: value };
|
|
||||||
|
|
||||||
this.dispatch(newConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
private dispatch(config: TimePickerCardConfig): void {
|
private dispatch(config: TimePickerCardConfig): void {
|
||||||
const event = new CustomEvent(TimePickerCardEditor.CONFIG_CHANGED_EVENT, {
|
const event = new CustomEvent(TimePickerCardEditor.CONFIG_CHANGED_EVENT, {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
@@ -276,23 +155,4 @@ export class TimePickerCardEditor extends LitElement implements LovelaceCardEdit
|
|||||||
|
|
||||||
this.dispatchEvent(event);
|
this.dispatchEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
|
||||||
return css`
|
|
||||||
ha-switch {
|
|
||||||
padding: 16px 0;
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
.side-by-side {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.side-by-side > * {
|
|
||||||
flex: 1;
|
|
||||||
padding-right: 4px;
|
|
||||||
}
|
|
||||||
.suffix {
|
|
||||||
margin: 0 8px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { html, TemplateResult } from 'lit-element';
|
|
||||||
import { TimePickerCardConfig, TimePickerHideConfig } from './types';
|
|
||||||
import { LovelaceCard } from 'custom-card-helpers';
|
import { LovelaceCard } from 'custom-card-helpers';
|
||||||
import { HassEntity } from 'home-assistant-js-websocket';
|
import { html, TemplateResult } from 'lit';
|
||||||
|
import { TimePickerCardConfig } from './types';
|
||||||
|
|
||||||
export class Partial {
|
export class Partial {
|
||||||
static error(error: string, origConfig: TimePickerCardConfig): TemplateResult {
|
static error(error: string, origConfig: TimePickerCardConfig): TemplateResult {
|
||||||
@@ -14,24 +13,4 @@ export class Partial {
|
|||||||
|
|
||||||
return html`${errorCard}`;
|
return html`${errorCard}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static headerName(title: string): TemplateResult {
|
|
||||||
return html`<div class="time-picker-header">${title}</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static nestedName(name: string, entity: HassEntity, hide?: TimePickerHideConfig): TemplateResult {
|
|
||||||
const icon = html`<state-badge .stateObj=${entity}></state-badge>`;
|
|
||||||
const label = html`<div class="entity-name-inside">${name}</div>`;
|
|
||||||
|
|
||||||
const visibleElements = [
|
|
||||||
{ show: !hide?.icon, value: icon },
|
|
||||||
{ show: !hide?.name, value: label },
|
|
||||||
]
|
|
||||||
.filter(({ show }) => show)
|
|
||||||
.map(({ value }) => value);
|
|
||||||
|
|
||||||
return html`${visibleElements}`;
|
|
||||||
|
|
||||||
// return hideIcon ? label : html`${icon}${label}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,27 @@
|
|||||||
import { computeDomain, HomeAssistant, LovelaceCard } from 'custom-card-helpers';
|
|
||||||
import { HassEntity } from 'home-assistant-js-websocket';
|
|
||||||
import {
|
import {
|
||||||
css,
|
ActionHandlerEvent,
|
||||||
CSSResult,
|
computeDomain,
|
||||||
customElement,
|
handleAction,
|
||||||
html,
|
hasAction,
|
||||||
LitElement,
|
HomeAssistant,
|
||||||
property,
|
LovelaceCard,
|
||||||
TemplateResult,
|
} from 'custom-card-helpers';
|
||||||
} from 'lit-element';
|
import { HassEntity } from 'home-assistant-js-websocket';
|
||||||
import { ClassInfo, classMap } from 'lit-html/directives/class-map';
|
import { css, CSSResult, html, LitElement, TemplateResult } from 'lit';
|
||||||
|
import { customElement, property } from 'lit/decorators.js';
|
||||||
|
import { ClassInfo, classMap } from 'lit/directives/class-map.js';
|
||||||
|
import { actionHandler } from './action-handler-directive';
|
||||||
import './components/time-period.component';
|
import './components/time-period.component';
|
||||||
import './components/time-unit.component';
|
import './components/time-unit.component';
|
||||||
import {
|
import {
|
||||||
CARD_SIZE,
|
CARD_SIZE,
|
||||||
CARD_VERSION,
|
CARD_VERSION,
|
||||||
|
DEFAULT_HOUR_MODE,
|
||||||
|
DEFAULT_HOUR_STEP,
|
||||||
DEFAULT_LAYOUT_ALIGN_CONTROLS,
|
DEFAULT_LAYOUT_ALIGN_CONTROLS,
|
||||||
DEFAULT_LAYOUT_HOUR_MODE,
|
DEFAULT_LAYOUT_HOUR_MODE,
|
||||||
|
DEFAULT_LAYOUT_NAME,
|
||||||
|
DEFAULT_MINUTE_STEP,
|
||||||
ENTITY_DOMAIN,
|
ENTITY_DOMAIN,
|
||||||
} from './const';
|
} from './const';
|
||||||
import './editor';
|
import './editor';
|
||||||
@@ -25,7 +30,7 @@ import { Minute } from './models/minute';
|
|||||||
import { Second } from './models/second';
|
import { Second } from './models/second';
|
||||||
import { Time } from './models/time';
|
import { Time } from './models/time';
|
||||||
import { Partial } from './partials';
|
import { Partial } from './partials';
|
||||||
import { Layout, Period, TimePickerCardConfig } from './types';
|
import { Layout, Period, TimePickerCardConfig, TimePickerHideConfig } from './types';
|
||||||
|
|
||||||
console.info(
|
console.info(
|
||||||
`%c TIME-PICKER-CARD \n%c Version ${CARD_VERSION} `,
|
`%c TIME-PICKER-CARD \n%c Version ${CARD_VERSION} `,
|
||||||
@@ -86,6 +91,7 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
|||||||
private get haCardClass(): ClassInfo {
|
private get haCardClass(): ClassInfo {
|
||||||
return {
|
return {
|
||||||
embedded: this.isEmbedded,
|
embedded: this.isEmbedded,
|
||||||
|
thin: this.config.layout?.thin === true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +110,58 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleAction(ev: ActionHandlerEvent): void {
|
||||||
|
handleAction(this, this.hass, this.config, ev.detail.action!);
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderHeaderName(title: string): TemplateResult {
|
||||||
|
return html`<div
|
||||||
|
class="time-picker-header"
|
||||||
|
@action=${this.handleAction}
|
||||||
|
.actionHandler="${actionHandler({
|
||||||
|
hasHold: hasAction(this.config.hold_action),
|
||||||
|
hasDoubleClick: hasAction(this.config.double_tap_action),
|
||||||
|
})}"
|
||||||
|
>
|
||||||
|
${title}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderNestedName(
|
||||||
|
name: string,
|
||||||
|
entity: HassEntity,
|
||||||
|
hide?: TimePickerHideConfig
|
||||||
|
): TemplateResult {
|
||||||
|
const icon = html`<state-badge
|
||||||
|
class="entity-icon"
|
||||||
|
.stateObj=${entity}
|
||||||
|
@action=${this.handleAction}
|
||||||
|
.actionHandler="${actionHandler({
|
||||||
|
hasHold: hasAction(this.config.hold_action),
|
||||||
|
hasDoubleClick: hasAction(this.config.double_tap_action),
|
||||||
|
})}"
|
||||||
|
></state-badge>`;
|
||||||
|
const label = html`<div
|
||||||
|
class="entity-name-inside"
|
||||||
|
@action=${this.handleAction}
|
||||||
|
.actionHandler="${actionHandler({
|
||||||
|
hasHold: hasAction(this.config!.hold_action),
|
||||||
|
hasDoubleClick: hasAction(this.config.double_tap_action),
|
||||||
|
})}"
|
||||||
|
>
|
||||||
|
${name}
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
const visibleElements = [
|
||||||
|
{ element: icon, visible: !hide?.icon },
|
||||||
|
{ element: label, visible: !hide?.name },
|
||||||
|
]
|
||||||
|
.filter(({ visible }) => visible)
|
||||||
|
.map(({ element }) => element);
|
||||||
|
|
||||||
|
return html`${visibleElements}`;
|
||||||
|
}
|
||||||
|
|
||||||
render(): TemplateResult | null {
|
render(): TemplateResult | null {
|
||||||
if (!this.entity) {
|
if (!this.entity) {
|
||||||
return Partial.error('Entity not found', this.config);
|
return Partial.error('Entity not found', this.config);
|
||||||
@@ -129,9 +187,11 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card class=${classMap(this.haCardClass)}>
|
<ha-card class=${classMap(this.haCardClass)}>
|
||||||
${this.hasNameInHeader ? Partial.headerName(this.name!) : ''}
|
${this.hasNameInHeader ? this.renderHeaderName(this.name!) : ''}
|
||||||
<div class=${classMap(this.rowClass)}>
|
<div class=${classMap(this.rowClass)}>
|
||||||
${this.hasNameInside ? Partial.nestedName(this.name!, this.entity, this.config.hide) : ''}
|
${this.hasNameInside
|
||||||
|
? this.renderNestedName(this.name!, this.entity, this.config.hide)
|
||||||
|
: ''}
|
||||||
|
|
||||||
<div class=${classMap(this.contentClass)}>
|
<div class=${classMap(this.contentClass)}>
|
||||||
<time-unit
|
<time-unit
|
||||||
@@ -237,11 +297,12 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
ha-card {
|
ha-card {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-card.embedded {
|
ha-card.embedded {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-picker-header {
|
.time-picker-header {
|
||||||
@@ -254,6 +315,10 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.thin > .time-picker-header {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.time-picker-row {
|
.time-picker-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -261,6 +326,10 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.thin .time-picker-row {
|
||||||
|
padding: 0 !important ;
|
||||||
|
}
|
||||||
|
|
||||||
.time-picker-row.embedded {
|
.time-picker-row.embedded {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
@@ -288,8 +357,13 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.entity-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.entity-name-inside {
|
.entity-name-inside {
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -301,10 +375,18 @@ export class TimePickerCard extends LitElement implements LovelaceCard {
|
|||||||
const datetimeEntity = entities.find((entityId) => computeDomain(entityId) === ENTITY_DOMAIN);
|
const datetimeEntity = entities.find((entityId) => computeDomain(entityId) === ENTITY_DOMAIN);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
entity: datetimeEntity || 'input_datetime.example_entity',
|
entity: datetimeEntity || '',
|
||||||
hour_mode: 24,
|
hour_mode: DEFAULT_HOUR_MODE,
|
||||||
hour_step: 1,
|
hour_step: DEFAULT_HOUR_STEP,
|
||||||
minute_step: 5,
|
minute_step: DEFAULT_MINUTE_STEP,
|
||||||
|
layout: {
|
||||||
|
hour_mode: DEFAULT_LAYOUT_HOUR_MODE,
|
||||||
|
align_controls: DEFAULT_LAYOUT_ALIGN_CONTROLS,
|
||||||
|
name: DEFAULT_LAYOUT_NAME,
|
||||||
|
},
|
||||||
|
hide: {
|
||||||
|
seconds: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { LovelaceCardConfig } from 'custom-card-helpers';
|
import { ActionConfig, LovelaceCardConfig } from 'custom-card-helpers';
|
||||||
|
|
||||||
export interface TimePickerCardConfig extends LovelaceCardConfig {
|
export interface TimePickerCardConfig extends LovelaceCardConfig {
|
||||||
entity: string;
|
entity: string;
|
||||||
@@ -10,6 +10,9 @@ export interface TimePickerCardConfig extends LovelaceCardConfig {
|
|||||||
second_step?: number;
|
second_step?: number;
|
||||||
layout?: TimePickerLayoutConfig;
|
layout?: TimePickerLayoutConfig;
|
||||||
hide?: TimePickerHideConfig;
|
hide?: TimePickerHideConfig;
|
||||||
|
tap_action?: ActionConfig;
|
||||||
|
double_tap_action?: ActionConfig;
|
||||||
|
hold_action?: ActionConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HourMode = 12 | 24 | undefined;
|
export type HourMode = 12 | 24 | undefined;
|
||||||
@@ -19,6 +22,7 @@ export interface TimePickerLayoutConfig {
|
|||||||
name?: Layout.Name;
|
name?: Layout.Name;
|
||||||
hour_mode?: Layout.HourMode;
|
hour_mode?: Layout.HourMode;
|
||||||
embedded?: boolean;
|
embedded?: boolean;
|
||||||
|
thin?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace Layout {
|
export namespace Layout {
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import { LovelaceCard } from 'custom-card-helpers';
|
|
||||||
import { customElement, html, LitElement, TemplateResult } from 'lit-element';
|
|
||||||
|
|
||||||
@customElement('hui-error-card')
|
|
||||||
export class HuiErrorCard extends LitElement implements LovelaceCard {
|
|
||||||
setConfig(): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): TemplateResult {
|
|
||||||
return html``;
|
|
||||||
}
|
|
||||||
|
|
||||||
getCardSize(): number {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
import { expect, fixture, html } from '@open-wc/testing';
|
|
||||||
import { HomeAssistant } from 'custom-card-helpers';
|
|
||||||
import { HassEntity } from 'home-assistant-js-websocket';
|
|
||||||
import '../../src/time-picker-card';
|
|
||||||
import { TimePickerCard } from '../../src/time-picker-card';
|
|
||||||
import { TimePickerCardConfig, HourMode } from '../../src/types';
|
|
||||||
import './test-error-card';
|
|
||||||
|
|
||||||
async function render(config?: {
|
|
||||||
entityId?: string;
|
|
||||||
friendlyName?: string;
|
|
||||||
hasTime?: boolean;
|
|
||||||
hourMode?: HourMode;
|
|
||||||
hideName?: boolean;
|
|
||||||
}): Promise<TimePickerCard> {
|
|
||||||
const { entityId, friendlyName, hasTime, hourMode, hideName } = Object.assign(
|
|
||||||
{
|
|
||||||
entityId: 'input_datetime.wake_time',
|
|
||||||
friendlyName: 'Wake Time',
|
|
||||||
hasTime: true,
|
|
||||||
hideName: false,
|
|
||||||
},
|
|
||||||
config
|
|
||||||
);
|
|
||||||
|
|
||||||
const testEntity: HassEntity = {
|
|
||||||
entity_id: entityId,
|
|
||||||
attributes: {
|
|
||||||
friendly_name: friendlyName,
|
|
||||||
has_time: hasTime,
|
|
||||||
},
|
|
||||||
state: '00:10:00',
|
|
||||||
last_changed: '',
|
|
||||||
last_updated: '',
|
|
||||||
context: {
|
|
||||||
id: entityId,
|
|
||||||
user_id: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const hass: Partial<HomeAssistant> = {
|
|
||||||
states: {
|
|
||||||
[entityId]: testEntity,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const testConfig: TimePickerCardConfig = {
|
|
||||||
type: 'custom:time-picker-card',
|
|
||||||
entity: entityId,
|
|
||||||
hour_mode: hourMode,
|
|
||||||
hide: {
|
|
||||||
name: hideName,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const card = await fixture<TimePickerCard>(
|
|
||||||
html`<time-picker-card .hass=${hass} .config=${testConfig}></time-picker-card>`
|
|
||||||
);
|
|
||||||
|
|
||||||
card.setConfig(testConfig);
|
|
||||||
|
|
||||||
return card;
|
|
||||||
}
|
|
||||||
|
|
||||||
function errorCard(): string {
|
|
||||||
return html`<hui-error-card></hui-error-card>`.getHTML();
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('test', () => {
|
|
||||||
it('renders', async () => {
|
|
||||||
const card = await render();
|
|
||||||
expect(card).shadowDom.to.match('time-picker-card');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('errors if entity is not an input_datetime', async () => {
|
|
||||||
const card = await render({ entityId: 'sensor.test_sensor' });
|
|
||||||
expect(card).shadowDom.to.equal(errorCard());
|
|
||||||
});
|
|
||||||
|
|
||||||
it("errors if entity doesn't have time", async () => {
|
|
||||||
const card = await render({ hasTime: false });
|
|
||||||
expect(card).shadowDom.to.equal(errorCard());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders card name', async () => {
|
|
||||||
const card = await render();
|
|
||||||
expect(card.shadowRoot?.innerHTML).to.match(/Wake Time/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('hides card name', async () => {
|
|
||||||
const card = await render({ hideName: true });
|
|
||||||
expect(card.shadowRoot?.innerHTML).not.to.match(/Wake Time/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders time units', async () => {
|
|
||||||
const card = await render();
|
|
||||||
expect(card.shadowRoot?.innerHTML.match(/<time-unit>/g)!.length).to.equal(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('time-period', () => {
|
|
||||||
it('is not rendered by default', async () => {
|
|
||||||
const card = await render();
|
|
||||||
expect(card.shadowRoot?.innerHTML.match(/<time-period>/g)).to.be.null;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders in 12-hour mode', async () => {
|
|
||||||
const card = await render({ hourMode: 12 });
|
|
||||||
expect(card.shadowRoot?.innerHTML.match(/<time-period>/g)!.length).to.equal(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
module.exports = function (config) {
|
|
||||||
return config.set({
|
|
||||||
basePath: './',
|
|
||||||
frameworks: ['mocha', 'chai'],
|
|
||||||
files: ['**/*.test.ts'],
|
|
||||||
preprocessors: {
|
|
||||||
'**/*.test.ts': ['rollup'],
|
|
||||||
},
|
|
||||||
|
|
||||||
rollupPreprocessor: {
|
|
||||||
plugins: [
|
|
||||||
require('@rollup/plugin-node-resolve')(),
|
|
||||||
require('@rollup/plugin-json')(),
|
|
||||||
require('@rollup/plugin-typescript')(),
|
|
||||||
require('@rollup/plugin-commonjs')({
|
|
||||||
namedExports: {
|
|
||||||
chai: ['expect'],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
output: {
|
|
||||||
format: 'umd',
|
|
||||||
sourcemap: 'inline',
|
|
||||||
},
|
|
||||||
onwarn: function (message) {
|
|
||||||
if (/Circular dependency/.test(message)) return;
|
|
||||||
console.warn(message);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
reporters: ['dots'],
|
|
||||||
|
|
||||||
port: 9876,
|
|
||||||
colors: true,
|
|
||||||
autoWatch: false,
|
|
||||||
singleRun: true,
|
|
||||||
|
|
||||||
concurrency: Infinity,
|
|
||||||
browsers: ['ChromeHeadless'],
|
|
||||||
|
|
||||||
client: {
|
|
||||||
captureConsole: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -1,165 +1,164 @@
|
|||||||
import { Hour } from '../../src/models/hour';
|
import { Hour } from '../../src/models/hour';
|
||||||
import { Direction } from '../../src/types';
|
import { Direction } from '../../src/types';
|
||||||
import { DEFAULT_HOUR_STEP } from '../../src/const';
|
import { DEFAULT_HOUR_STEP } from '../../src/const';
|
||||||
import { expect } from 'chai';
|
|
||||||
|
|
||||||
describe('Hour', () => {
|
describe('Hour', () => {
|
||||||
let hour: Hour;
|
let hour: Hour;
|
||||||
|
|
||||||
context('with default config', () => {
|
describe('with default config', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
hour = new Hour(2);
|
hour = new Hour(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns value', () => {
|
it('returns value', () => {
|
||||||
expect(hour.value).to.equal(2);
|
expect(hour.value).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has correct max value', () => {
|
it('has correct max value', () => {
|
||||||
expect(hour.maxValue).to.equal(23);
|
expect(hour.maxValue).toEqual(23);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('toString', () => {
|
describe('toString', () => {
|
||||||
it('returns formatted string', () => {
|
it('returns formatted string', () => {
|
||||||
expect(hour.toString()).to.equal('02');
|
expect(hour.toString()).toEqual('02');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders values correctly', () => {
|
it('renders values correctly', () => {
|
||||||
hour = new Hour(0);
|
hour = new Hour(0);
|
||||||
expect(hour.toString()).to.equal('00');
|
expect(hour.toString()).toEqual('00');
|
||||||
|
|
||||||
hour = new Hour(6);
|
hour = new Hour(6);
|
||||||
expect(hour.toString()).to.equal('06');
|
expect(hour.toString()).toEqual('06');
|
||||||
|
|
||||||
hour = new Hour(12);
|
hour = new Hour(12);
|
||||||
expect(hour.toString()).to.equal('12');
|
expect(hour.toString()).toEqual('12');
|
||||||
|
|
||||||
hour = new Hour(18);
|
hour = new Hour(18);
|
||||||
expect(hour.toString()).to.equal('18');
|
expect(hour.toString()).toEqual('18');
|
||||||
|
|
||||||
hour = new Hour(23);
|
hour = new Hour(23);
|
||||||
expect(hour.toString()).to.equal('23');
|
expect(hour.toString()).toEqual('23');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('stepUpdate', () => {
|
describe('stepUpdate', () => {
|
||||||
it('updates up', () => {
|
it('updates up', () => {
|
||||||
hour.stepUpdate(Direction.UP);
|
hour.stepUpdate(Direction.UP);
|
||||||
expect(hour.value).to.equal(3);
|
expect(hour.value).toEqual(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates down', () => {
|
it('updates down', () => {
|
||||||
hour.stepUpdate(Direction.DOWN);
|
hour.stepUpdate(Direction.DOWN);
|
||||||
expect(hour.value).to.equal(1);
|
expect(hour.value).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('goes up from 23 to 0', () => {
|
it('goes up from 23 to 0', () => {
|
||||||
hour = new Hour(23);
|
hour = new Hour(23);
|
||||||
hour.stepUpdate(Direction.UP);
|
hour.stepUpdate(Direction.UP);
|
||||||
expect(hour.value).to.equal(0);
|
expect(hour.value).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('goes down from 0 to 23', () => {
|
it('goes down from 0 to 23', () => {
|
||||||
hour = new Hour(0);
|
hour = new Hour(0);
|
||||||
hour.stepUpdate(Direction.DOWN);
|
hour.stepUpdate(Direction.DOWN);
|
||||||
expect(hour.value).to.equal(23);
|
expect(hour.value).toEqual(23);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setStringValue', () => {
|
describe('setStringValue', () => {
|
||||||
it('sets value from valid string', () => {
|
it('sets value from valid string', () => {
|
||||||
hour.setStringValue('3');
|
hour.setStringValue('3');
|
||||||
expect(hour.value).to.equal(3);
|
expect(hour.value).toEqual(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't set value from invalid string", () => {
|
it("doesn't set value from invalid string", () => {
|
||||||
hour.setStringValue('test');
|
hour.setStringValue('test');
|
||||||
expect(hour.value).to.equal(2);
|
expect(hour.value).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't set value from outside of scope", () => {
|
it("doesn't set value from outside of scope", () => {
|
||||||
hour.setStringValue('24');
|
hour.setStringValue('24');
|
||||||
expect(hour.value).to.equal(2);
|
expect(hour.value).toEqual(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('with a different step size', () => {
|
describe('with a different step size', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
hour = new Hour(2, 5);
|
hour = new Hour(2, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('goes up correctly', () => {
|
it('goes up correctly', () => {
|
||||||
hour.stepUpdate(Direction.UP);
|
hour.stepUpdate(Direction.UP);
|
||||||
expect(hour.value).to.equal(7);
|
expect(hour.value).toEqual(7);
|
||||||
|
|
||||||
hour.stepUpdate(Direction.UP);
|
hour.stepUpdate(Direction.UP);
|
||||||
hour.stepUpdate(Direction.UP);
|
hour.stepUpdate(Direction.UP);
|
||||||
hour.stepUpdate(Direction.UP);
|
hour.stepUpdate(Direction.UP);
|
||||||
hour.stepUpdate(Direction.UP);
|
hour.stepUpdate(Direction.UP);
|
||||||
|
|
||||||
expect(hour.value).to.equal(3);
|
expect(hour.value).toEqual(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('goes down correclty', () => {
|
it('goes down correclty', () => {
|
||||||
hour.stepUpdate(Direction.DOWN);
|
hour.stepUpdate(Direction.DOWN);
|
||||||
expect(hour.value).to.equal(21);
|
expect(hour.value).toEqual(21);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('in 12 hour mode', () => {
|
describe('in 12 hour mode', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
hour = new Hour(2, DEFAULT_HOUR_STEP, 12);
|
hour = new Hour(2, DEFAULT_HOUR_STEP, 12);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has correct max value', () => {
|
it('has correct max value', () => {
|
||||||
expect(hour.maxValue).to.equal(12);
|
expect(hour.maxValue).toEqual(12);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('togglePeriod', () => {
|
describe('togglePeriod', () => {
|
||||||
it('changes values correclty', () => {
|
it('changes values correclty', () => {
|
||||||
hour = new Hour(2, DEFAULT_HOUR_STEP, 12);
|
hour = new Hour(2, DEFAULT_HOUR_STEP, 12);
|
||||||
hour.togglePeriod();
|
hour.togglePeriod();
|
||||||
expect(hour.value).to.equal(14);
|
expect(hour.value).toEqual(14);
|
||||||
|
|
||||||
hour = new Hour(0, DEFAULT_HOUR_STEP, 12);
|
hour = new Hour(0, DEFAULT_HOUR_STEP, 12);
|
||||||
hour.togglePeriod();
|
hour.togglePeriod();
|
||||||
expect(hour.value).to.equal(12);
|
expect(hour.value).toEqual(12);
|
||||||
|
|
||||||
hour = new Hour(23, DEFAULT_HOUR_STEP, 12);
|
hour = new Hour(23, DEFAULT_HOUR_STEP, 12);
|
||||||
hour.togglePeriod();
|
hour.togglePeriod();
|
||||||
expect(hour.value).to.equal(11);
|
expect(hour.value).toEqual(11);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has no side effects', () => {
|
it('has no side effects', () => {
|
||||||
const value = hour.value;
|
const value = hour.value;
|
||||||
hour.togglePeriod();
|
hour.togglePeriod();
|
||||||
hour.togglePeriod();
|
hour.togglePeriod();
|
||||||
expect(hour.value).to.equal(value);
|
expect(hour.value).toEqual(value);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('toString', () => {
|
describe('toString', () => {
|
||||||
it('renders values above 12 orrectly', () => {
|
it('renders values above 12 orrectly', () => {
|
||||||
hour = new Hour(13, DEFAULT_HOUR_STEP, 12);
|
hour = new Hour(13, DEFAULT_HOUR_STEP, 12);
|
||||||
expect(hour.toString()).to.equal('01');
|
expect(hour.toString()).toEqual('01');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders values correctly', () => {
|
it('renders values correctly', () => {
|
||||||
hour = new Hour(0, DEFAULT_HOUR_STEP, 12);
|
hour = new Hour(0, DEFAULT_HOUR_STEP, 12);
|
||||||
expect(hour.toString()).to.equal('12');
|
expect(hour.toString()).toEqual('12');
|
||||||
|
|
||||||
hour = new Hour(6, DEFAULT_HOUR_STEP, 12);
|
hour = new Hour(6, DEFAULT_HOUR_STEP, 12);
|
||||||
expect(hour.toString()).to.equal('06');
|
expect(hour.toString()).toEqual('06');
|
||||||
|
|
||||||
hour = new Hour(12, DEFAULT_HOUR_STEP, 12);
|
hour = new Hour(12, DEFAULT_HOUR_STEP, 12);
|
||||||
expect(hour.toString()).to.equal('12');
|
expect(hour.toString()).toEqual('12');
|
||||||
|
|
||||||
hour = new Hour(18, DEFAULT_HOUR_STEP, 12);
|
hour = new Hour(18, DEFAULT_HOUR_STEP, 12);
|
||||||
expect(hour.toString()).to.equal('06');
|
expect(hour.toString()).toEqual('06');
|
||||||
|
|
||||||
hour = new Hour(23, DEFAULT_HOUR_STEP, 12);
|
hour = new Hour(23, DEFAULT_HOUR_STEP, 12);
|
||||||
expect(hour.toString()).to.equal('11');
|
expect(hour.toString()).toEqual('11');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,119 +1,118 @@
|
|||||||
import { Minute } from '../../src/models/minute';
|
import { Minute } from '../../src/models/minute';
|
||||||
import { Direction } from '../../src/types';
|
import { Direction } from '../../src/types';
|
||||||
import { expect } from 'chai';
|
|
||||||
|
|
||||||
describe('Minute', () => {
|
describe('Minute', () => {
|
||||||
let minute: Minute;
|
let minute: Minute;
|
||||||
|
|
||||||
context('with default config', () => {
|
describe('with default config', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
minute = new Minute(2);
|
minute = new Minute(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns value', () => {
|
it('returns value', () => {
|
||||||
expect(minute.value).to.equal(2);
|
expect(minute.value).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has correct max value', () => {
|
it('has correct max value', () => {
|
||||||
expect(minute.maxValue).to.equal(59);
|
expect(minute.maxValue).toEqual(59);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('toString', () => {
|
describe('toString', () => {
|
||||||
it('renders values correctly', () => {
|
it('renders values correctly', () => {
|
||||||
minute = new Minute(0);
|
minute = new Minute(0);
|
||||||
expect(minute.toString()).to.equal('00');
|
expect(minute.toString()).toEqual('00');
|
||||||
|
|
||||||
minute = new Minute(5);
|
minute = new Minute(5);
|
||||||
expect(minute.toString()).to.equal('05');
|
expect(minute.toString()).toEqual('05');
|
||||||
|
|
||||||
minute = new Minute(59);
|
minute = new Minute(59);
|
||||||
expect(minute.toString()).to.equal('59');
|
expect(minute.toString()).toEqual('59');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('stepUpdate', () => {
|
describe('stepUpdate', () => {
|
||||||
it('updates up', () => {
|
it('updates up', () => {
|
||||||
minute.stepUpdate(Direction.UP);
|
minute.stepUpdate(Direction.UP);
|
||||||
expect(minute.value).to.equal(7);
|
expect(minute.value).toEqual(7);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates down', () => {
|
it('updates down', () => {
|
||||||
minute.stepUpdate(Direction.DOWN);
|
minute.stepUpdate(Direction.DOWN);
|
||||||
expect(minute.value).to.equal(57);
|
expect(minute.value).toEqual(57);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('goes up from 59 to 4', () => {
|
it('goes up from 59 to 4', () => {
|
||||||
minute = new Minute(59);
|
minute = new Minute(59);
|
||||||
minute.stepUpdate(Direction.UP);
|
minute.stepUpdate(Direction.UP);
|
||||||
expect(minute.value).to.equal(4);
|
expect(minute.value).toEqual(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('goes down from 0 to 55', () => {
|
it('goes down from 0 to 55', () => {
|
||||||
minute = new Minute(0);
|
minute = new Minute(0);
|
||||||
minute.stepUpdate(Direction.DOWN);
|
minute.stepUpdate(Direction.DOWN);
|
||||||
expect(minute.value).to.equal(55);
|
expect(minute.value).toEqual(55);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setStringValue', () => {
|
describe('setStringValue', () => {
|
||||||
it('sets value from valid string', () => {
|
it('sets value from valid string', () => {
|
||||||
minute.setStringValue('3');
|
minute.setStringValue('3');
|
||||||
expect(minute.value).to.equal(3);
|
expect(minute.value).toEqual(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't set value from invalid string", () => {
|
it("doesn't set value from invalid string", () => {
|
||||||
minute.setStringValue('test');
|
minute.setStringValue('test');
|
||||||
expect(minute.value).to.equal(2);
|
expect(minute.value).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't set value from outside of scope", () => {
|
it("doesn't set value from outside of scope", () => {
|
||||||
minute.setStringValue('60');
|
minute.setStringValue('60');
|
||||||
expect(minute.value).to.equal(2);
|
expect(minute.value).toEqual(2);
|
||||||
|
|
||||||
minute.setStringValue('65');
|
minute.setStringValue('65');
|
||||||
expect(minute.value).to.equal(2);
|
expect(minute.value).toEqual(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('with a different step size', () => {
|
describe('with a different step size', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
minute = new Minute(2, 15);
|
minute = new Minute(2, 15);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('goes up correctly', () => {
|
it('goes up correctly', () => {
|
||||||
minute.stepUpdate(Direction.UP);
|
minute.stepUpdate(Direction.UP);
|
||||||
expect(minute.value).to.equal(17);
|
expect(minute.value).toEqual(17);
|
||||||
|
|
||||||
minute.stepUpdate(Direction.UP);
|
minute.stepUpdate(Direction.UP);
|
||||||
minute.stepUpdate(Direction.UP);
|
minute.stepUpdate(Direction.UP);
|
||||||
minute.stepUpdate(Direction.UP);
|
minute.stepUpdate(Direction.UP);
|
||||||
expect(minute.value).to.equal(2);
|
expect(minute.value).toEqual(2);
|
||||||
|
|
||||||
minute.stepUpdate(Direction.UP);
|
minute.stepUpdate(Direction.UP);
|
||||||
expect(minute.value).to.equal(17);
|
expect(minute.value).toEqual(17);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('goes down correclty', () => {
|
it('goes down correclty', () => {
|
||||||
minute.stepUpdate(Direction.DOWN);
|
minute.stepUpdate(Direction.DOWN);
|
||||||
expect(minute.value).to.equal(47);
|
expect(minute.value).toEqual(47);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('will overflow', () => {
|
describe('will overflow', () => {
|
||||||
it('returns true if the minutes will overflow an hour up', () => {
|
it('returns true if the minutes will overflow an hour up', () => {
|
||||||
minute.setStringValue('59');
|
minute.setStringValue('59');
|
||||||
expect(minute.willOverflow(Direction.UP)).to.be.true;
|
expect(minute.willOverflow(Direction.UP)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true if the minutes will overflow an hour down', () => {
|
it('returns true if the minutes will overflow an hour down', () => {
|
||||||
minute.setStringValue('0');
|
minute.setStringValue('0');
|
||||||
expect(minute.willOverflow(Direction.DOWN)).to.be.true;
|
expect(minute.willOverflow(Direction.DOWN)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true if the minutes will not overflow', () => {
|
it('returns true if the minutes will not overflow', () => {
|
||||||
minute.setStringValue('55');
|
minute.setStringValue('55');
|
||||||
expect(minute.willOverflow(Direction.DOWN)).to.be.false;
|
expect(minute.willOverflow(Direction.DOWN)).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,119 +1,118 @@
|
|||||||
import { Second } from '../../src/models/second';
|
import { Second } from '../../src/models/second';
|
||||||
import { Direction } from '../../src/types';
|
import { Direction } from '../../src/types';
|
||||||
import { expect } from 'chai';
|
|
||||||
|
|
||||||
describe('Second', () => {
|
describe('Second', () => {
|
||||||
let second: Second;
|
let second: Second;
|
||||||
|
|
||||||
context('with default config', () => {
|
describe('with default config', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
second = new Second(2);
|
second = new Second(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns value', () => {
|
it('returns value', () => {
|
||||||
expect(second.value).to.equal(2);
|
expect(second.value).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has correct max value', () => {
|
it('has correct max value', () => {
|
||||||
expect(second.maxValue).to.equal(59);
|
expect(second.maxValue).toEqual(59);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('toString', () => {
|
describe('toString', () => {
|
||||||
it('renders values correctly', () => {
|
it('renders values correctly', () => {
|
||||||
second = new Second(0);
|
second = new Second(0);
|
||||||
expect(second.toString()).to.equal('00');
|
expect(second.toString()).toEqual('00');
|
||||||
|
|
||||||
second = new Second(5);
|
second = new Second(5);
|
||||||
expect(second.toString()).to.equal('05');
|
expect(second.toString()).toEqual('05');
|
||||||
|
|
||||||
second = new Second(59);
|
second = new Second(59);
|
||||||
expect(second.toString()).to.equal('59');
|
expect(second.toString()).toEqual('59');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('stepUpdate', () => {
|
describe('stepUpdate', () => {
|
||||||
it('updates up', () => {
|
it('updates up', () => {
|
||||||
second.stepUpdate(Direction.UP);
|
second.stepUpdate(Direction.UP);
|
||||||
expect(second.value).to.equal(7);
|
expect(second.value).toEqual(7);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates down', () => {
|
it('updates down', () => {
|
||||||
second.stepUpdate(Direction.DOWN);
|
second.stepUpdate(Direction.DOWN);
|
||||||
expect(second.value).to.equal(57);
|
expect(second.value).toEqual(57);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('goes up from 59 to 4', () => {
|
it('goes up from 59 to 4', () => {
|
||||||
second = new Second(59);
|
second = new Second(59);
|
||||||
second.stepUpdate(Direction.UP);
|
second.stepUpdate(Direction.UP);
|
||||||
expect(second.value).to.equal(4);
|
expect(second.value).toEqual(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('goes down from 0 to 55', () => {
|
it('goes down from 0 to 55', () => {
|
||||||
second = new Second(0);
|
second = new Second(0);
|
||||||
second.stepUpdate(Direction.DOWN);
|
second.stepUpdate(Direction.DOWN);
|
||||||
expect(second.value).to.equal(55);
|
expect(second.value).toEqual(55);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setStringValue', () => {
|
describe('setStringValue', () => {
|
||||||
it('sets value from valid string', () => {
|
it('sets value from valid string', () => {
|
||||||
second.setStringValue('3');
|
second.setStringValue('3');
|
||||||
expect(second.value).to.equal(3);
|
expect(second.value).toEqual(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't set value from invalid string", () => {
|
it("doesn't set value from invalid string", () => {
|
||||||
second.setStringValue('test');
|
second.setStringValue('test');
|
||||||
expect(second.value).to.equal(2);
|
expect(second.value).toEqual(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't set value from outside of scope", () => {
|
it("doesn't set value from outside of scope", () => {
|
||||||
second.setStringValue('60');
|
second.setStringValue('60');
|
||||||
expect(second.value).to.equal(2);
|
expect(second.value).toEqual(2);
|
||||||
|
|
||||||
second.setStringValue('65');
|
second.setStringValue('65');
|
||||||
expect(second.value).to.equal(2);
|
expect(second.value).toEqual(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('with a different step size', () => {
|
describe('with a different step size', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
second = new Second(2, 15);
|
second = new Second(2, 15);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('goes up correctly', () => {
|
it('goes up correctly', () => {
|
||||||
second.stepUpdate(Direction.UP);
|
second.stepUpdate(Direction.UP);
|
||||||
expect(second.value).to.equal(17);
|
expect(second.value).toEqual(17);
|
||||||
|
|
||||||
second.stepUpdate(Direction.UP);
|
second.stepUpdate(Direction.UP);
|
||||||
second.stepUpdate(Direction.UP);
|
second.stepUpdate(Direction.UP);
|
||||||
second.stepUpdate(Direction.UP);
|
second.stepUpdate(Direction.UP);
|
||||||
expect(second.value).to.equal(2);
|
expect(second.value).toEqual(2);
|
||||||
|
|
||||||
second.stepUpdate(Direction.UP);
|
second.stepUpdate(Direction.UP);
|
||||||
expect(second.value).to.equal(17);
|
expect(second.value).toEqual(17);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('goes down correclty', () => {
|
it('goes down correclty', () => {
|
||||||
second.stepUpdate(Direction.DOWN);
|
second.stepUpdate(Direction.DOWN);
|
||||||
expect(second.value).to.equal(47);
|
expect(second.value).toEqual(47);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('will overflow', () => {
|
describe('will overflow', () => {
|
||||||
it('returns true if the Seconds will overflow an hour up', () => {
|
it('returns true if the Seconds will overflow an hour up', () => {
|
||||||
second.setStringValue('59');
|
second.setStringValue('59');
|
||||||
expect(second.willOverflow(Direction.UP)).to.be.true;
|
expect(second.willOverflow(Direction.UP)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true if the Seconds will overflow an hour down', () => {
|
it('returns true if the Seconds will overflow an hour down', () => {
|
||||||
second.setStringValue('0');
|
second.setStringValue('0');
|
||||||
expect(second.willOverflow(Direction.DOWN)).to.be.true;
|
expect(second.willOverflow(Direction.DOWN)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true if the Seconds will not overflow', () => {
|
it('returns true if the Seconds will not overflow', () => {
|
||||||
second.setStringValue('55');
|
second.setStringValue('55');
|
||||||
expect(second.willOverflow(Direction.DOWN)).to.be.false;
|
expect(second.willOverflow(Direction.DOWN)).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||