Compare commits

...
6 Commits
Author SHA1 Message Date
GeorgeSG 74949a3df2 0.1.1 2020-05-10 00:22:45 +03:00
GeorgeSG 224ee3b043 Remove commit activity from README 2020-05-10 00:18:38 +03:00
GeorgeSG 2e604a66d6 Update README 2020-05-10 00:17:43 +03:00
GeorgeSG b7ad71d979 Rename CI task. Don't lint twice 2020-05-10 00:15:20 +03:00
GeorgeSG 79418592c9 Fix 12AM representation. Add model tests 2020-05-10 00:13:29 +03:00
GeorgeSG 0297c9aba4 Update README 2020-05-09 20:42:35 +03:00
14 changed files with 381 additions and 59 deletions
+1 -2
View File
@@ -9,14 +9,13 @@ on:
- master
jobs:
build:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: |
npm install
npm run lint
npm run build
- name: Test
run: npm run test
+5
View File
@@ -0,0 +1,5 @@
{
"files.exclude": {
"dist/**": true
}
}
+3 -19
View File
@@ -5,7 +5,6 @@
[![GitHub Release][releases-shield]][releases-link]
[![CI][ci-shield]][ci-link]
[![Project Maintenance][maintenance-shield]][maintenance-link]
[![GitHub Activity][commits-shield]][commits-link]
[![License][license-shield]][license-link]
## Overview
@@ -32,19 +31,6 @@ Time Picker Card supports Lovelace's Visual Editor. Click the + button to add a
![Visual Editor](https://raw.githubusercontent.com/GeorgeSG/lovelace-time-picker-card/master/examples/visual_editor.png)
### YAML
```yaml
type: 'custom:time-picker-card'
entity: input_datetime.alarm_time
name: 'Choose a time'
hour_mode: 24
hour_step: 1
minute_step: 5
hide:
name: false
```
## Examples
### Default config - card name shown, 24 hour mode
@@ -111,7 +97,7 @@ layout:
| layout | object | **Optional** | Card Layout configuration | `none` |
| hide | object | **Optional** | Hide object | `none` |
## Layout Object
### Layout Object
| Name | Value | Requirement | Description | Default |
| -------------- | ------------------------- | ------------ | -------------------------------------------------------------------------------------------------- | -------- |
@@ -119,13 +105,13 @@ layout:
| 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` |
## Hide Object
### Hide Object
| Name | Type | Requirement | Description | Default |
| ---- | ------- | ------------ | ------------------- | ------- |
| name | boolean | **Optional** | Hides the card name | `false` |
## Theme Variables
### Theme Variables
Time Picker Card will automatically pick up colors from your lovelace theme, but if you want to customize some of them,
you can use the following variables in your theme's config file:
@@ -156,8 +142,6 @@ you can use the following variables in your theme's config file:
[ci-link]: https://github.com/GeorgeSG/lovelace-time-picker-card/actions?query=workflow%3ACI
[maintenance-shield]: https://img.shields.io/maintenance/yes/2020.svg
[maintenance-link]: https://github.com/GeorgeSG/lovelace-time-picker-card
[commits-shield]: https://img.shields.io/github/commit-activity/y/GeorgeSG/lovelace-time-picker-card
[commits-link]: https://github.com/GeorgeSG/lovelace-time-picker-card/commits/master
[license-shield]: https://img.shields.io/github/license/GeorgeSG/lovelace-time-picker-card?color=brightgreen
[license-link]: https://github.com/GeorgeSG/lovelace-time-picker-card/blob/master/LICENSE
[github-icon]: http://i.imgur.com/9I6NRUm.png
+54 -1
View File
@@ -1,6 +1,6 @@
{
"name": "time-picker-card",
"version": "0.1.0",
"version": "0.1.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -423,6 +423,21 @@
"lit-html": "^1.0.0"
}
},
"@rollup/plugin-commonjs": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz",
"integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==",
"dev": true,
"requires": {
"@rollup/pluginutils": "^3.0.8",
"commondir": "^1.0.1",
"estree-walker": "^1.0.1",
"glob": "^7.1.2",
"is-reference": "^1.1.2",
"magic-string": "^0.25.2",
"resolve": "^1.11.0"
}
},
"@rollup/plugin-json": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.0.3.tgz",
@@ -1086,6 +1101,12 @@
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
"commondir": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
"dev": true
},
"component-bind": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
@@ -2167,6 +2188,23 @@
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"is-reference": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz",
"integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==",
"dev": true,
"requires": {
"@types/estree": "0.0.39"
},
"dependencies": {
"@types/estree": {
"version": "0.0.39",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
"dev": true
}
}
},
"is-regex": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz",
@@ -2541,6 +2579,15 @@
"streamroller": "^1.0.6"
}
},
"magic-string": {
"version": "0.25.7",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
"integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
"dev": true,
"requires": {
"sourcemap-codec": "^1.4.4"
}
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -3421,6 +3468,12 @@
"source-map": "^0.6.0"
}
},
"sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"dev": true
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "time-picker-card",
"version": "0.1.0",
"version": "0.1.1",
"author": "Georgi Gardev <georgi@gardev.com>",
"description": "Lovelace time-picker-card",
"keywords": [
@@ -19,7 +19,7 @@
"build": "npm run lint && npm run rollup",
"lint": "eslint src/*.ts",
"rollup": "rollup -c",
"test": "./node_modules/karma/bin/karma start",
"test": "./node_modules/karma/bin/karma start ./test/karma.conf.js",
"release-patch": "npm version patch && 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"
@@ -33,6 +33,7 @@
"devDependencies": {
"@babel/core": "^7.9.0",
"@open-wc/testing": "^2.5.16",
"@rollup/plugin-commonjs": "^11.1.0",
"@rollup/plugin-json": "^4.0.3",
"@rollup/plugin-node-resolve": "^7.1.1",
"@rollup/plugin-typescript": "^4.0.0",
+1 -1
View File
@@ -27,7 +27,7 @@ export class TimeUnitComponent extends LitElement {
class="time-input"
type="number"
placeholder="MM"
min="0"
min=${this.unit.minValue}
max=${this.unit.maxValue}
.value="${this.unit.toString()}"
@change=${this.onInputChange}
+14 -10
View File
@@ -8,12 +8,16 @@ import { TimeUnit } from './time-unit';
export class Hour extends TimeUnit {
private static readonly VALUE_LIMIT = 24;
constructor(value: number, step = DEFAULT_HOUR_STEP, private hourMode: HourMode) {
constructor(value: number, step = DEFAULT_HOUR_STEP, private hourMode?: HourMode) {
super(value, step, Hour.VALUE_LIMIT);
}
get minValue(): number {
return this.hourMode === 12 ? 1 : 0; // there is no 00:00 AM -> it's 12:00 AM.
}
get maxValue(): number {
return this.hourMode || Hour.VALUE_LIMIT;
return this.hourMode === 12 ? 12 : Hour.VALUE_LIMIT - 1;
}
togglePeriod(): void {
@@ -21,15 +25,15 @@ export class Hour extends TimeUnit {
}
toString(): string {
const value = this.hourMode === 12 ? (this.value + 12) % 12 : this.value;
let value;
if (this.hourMode === 12) {
value = (this.value + 12) % 12;
value = value === 0 ? 12 : value;
} else {
value = this.value;
}
return value < 10 ? `0${value}` : value.toString();
}
protected isValidString(valueStr: string): boolean {
const value = parseInt(valueStr);
const limit = this.hourMode || this._limit;
return !isNaN(value) && value >= 0 && value <= limit;
}
}
+2 -6
View File
@@ -7,14 +7,10 @@ import { TimeUnit } from './time-unit';
export class Minute extends TimeUnit {
private static readonly VALUE_LIMIT = 60;
maxValue = Minute.VALUE_LIMIT;
minValue = 0;
maxValue = Minute.VALUE_LIMIT - 1;
constructor(value: number, step = DEFAULT_MINUTE_STEP) {
super(value, step, Minute.VALUE_LIMIT);
}
protected isValidString(valueStr: string): boolean {
const value = parseInt(valueStr);
return !isNaN(value) && value >= 0 && value <= this._limit;
}
}
+8 -4
View File
@@ -5,12 +5,11 @@ import { Direction } from '../types';
*/
export abstract class TimeUnit {
/**
* Whether {@param valueStr} can be set as a value of this instance.
* The min allowed UI value for this instance.
*/
protected abstract isValidString(valueStr: string): boolean;
abstract minValue: number;
/**
* The max allowed value for this instance. Used for UI validation.
* The max allowed value for this instance.
*/
abstract maxValue: number;
@@ -63,4 +62,9 @@ export abstract class TimeUnit {
this._value = newValue;
}
protected isValidString(valueStr: string): boolean {
const value = parseInt(valueStr);
return !isNaN(value) && value >= 0 && value < this._limit;
}
}
@@ -1,9 +1,9 @@
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 '../../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?: {
+19 -11
View File
@@ -1,10 +1,10 @@
module.exports = function (config) {
config.set({
basePath: 'test/',
return config.set({
basePath: './',
frameworks: ['mocha', 'chai'],
files: ['*.test.ts'],
files: ['**/*.test.ts'],
preprocessors: {
'*.test.ts': ['rollup'],
'**/*.test.ts': ['rollup'],
},
rollupPreprocessor: {
@@ -12,26 +12,34 @@ module.exports = function (config) {
require('@rollup/plugin-node-resolve')(),
require('@rollup/plugin-json')(),
require('@rollup/plugin-typescript')(),
require('rollup-plugin-babel')(),
require('@rollup/plugin-commonjs')({
namedExports: {
chai: ['expect'],
},
}),
],
output: {
format: 'umd',
sourcemap: 'inline',
},
onwarn: function (message) {
if (/Circular dependency/.test(message)) return;
console.warn(message);
},
},
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],
reporters: ['dots'],
port: 9876,
colors: true,
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
autoWatch: false,
singleRun: true,
browsers: ['ChromeHeadless'],
concurrency: Infinity,
browsers: ['ChromeHeadless'],
client: {
captureConsole: false,
},
});
};
+166
View File
@@ -0,0 +1,166 @@
import { Hour } from '../../src/models/hour';
import { Direction } from '../../src/types';
import { DEFAULT_HOUR_STEP } from '../../src/const';
import { expect } from 'chai';
describe('Hour', () => {
let hour: Hour;
context('with default config', () => {
beforeEach(() => {
hour = new Hour(2);
});
it('returns value', () => {
expect(hour.value).to.equal(2);
});
it('has correct max value', () => {
expect(hour.maxValue).to.equal(23);
});
describe('toString', () => {
it('returns formatted string', () => {
expect(hour.toString()).to.equal('02');
});
it('renders values correctly', () => {
hour = new Hour(0);
expect(hour.toString()).to.equal('00');
hour = new Hour(6);
expect(hour.toString()).to.equal('06');
hour = new Hour(12);
expect(hour.toString()).to.equal('12');
hour = new Hour(18);
expect(hour.toString()).to.equal('18');
hour = new Hour(23);
expect(hour.toString()).to.equal('23');
});
});
describe('stepUpdate', () => {
it('updates up', () => {
hour.stepUpdate(Direction.UP);
expect(hour.value).to.equal(3);
});
it('updates down', () => {
hour.stepUpdate(Direction.DOWN);
expect(hour.value).to.equal(1);
});
it('goes up from 23 to 0', () => {
hour = new Hour(23);
hour.stepUpdate(Direction.UP);
expect(hour.value).to.equal(0);
});
it('goes down from 0 to 23', () => {
hour = new Hour(0);
hour.stepUpdate(Direction.DOWN);
expect(hour.value).to.equal(23);
});
});
describe('setStringValue', () => {
it('sets value from valid string', () => {
hour.setStringValue('3');
expect(hour.value).to.equal(3);
});
it("doesn't set value from invalid string", () => {
hour.setStringValue('test');
expect(hour.value).to.equal(2);
});
it("doesn't set value from outside of scope", () => {
hour.setStringValue('24');
expect(hour.value).to.equal(2);
});
});
});
context('with a different step size', () => {
beforeEach(() => {
hour = new Hour(2, 5);
});
it('goes up correctly', () => {
hour.stepUpdate(Direction.UP);
expect(hour.value).to.equal(7);
hour.stepUpdate(Direction.UP);
hour.stepUpdate(Direction.UP);
hour.stepUpdate(Direction.UP);
hour.stepUpdate(Direction.UP);
expect(hour.value).to.equal(3);
});
it('goes down correclty', () => {
hour.stepUpdate(Direction.DOWN);
expect(hour.value).to.equal(21);
});
});
context('in 12 hour mode', () => {
beforeEach(() => {
hour = new Hour(2, DEFAULT_HOUR_STEP, 12);
});
it('has correct max value', () => {
expect(hour.maxValue).to.equal(12);
});
describe('togglePeriod', () => {
it('changes values correclty', () => {
hour = new Hour(2, DEFAULT_HOUR_STEP, 12);
hour.togglePeriod();
expect(hour.value).to.equal(14);
hour = new Hour(0, DEFAULT_HOUR_STEP, 12);
hour.togglePeriod();
expect(hour.value).to.equal(12);
hour = new Hour(23, DEFAULT_HOUR_STEP, 12);
hour.togglePeriod();
expect(hour.value).to.equal(11);
});
it('has no side effects', () => {
const value = hour.value;
hour.togglePeriod();
hour.togglePeriod();
expect(hour.value).to.equal(value);
});
});
describe('toString', () => {
it('renders values above 12 orrectly', () => {
hour = new Hour(13, DEFAULT_HOUR_STEP, 12);
expect(hour.toString()).to.equal('01');
});
it('renders values correctly', () => {
hour = new Hour(0, DEFAULT_HOUR_STEP, 12);
expect(hour.toString()).to.equal('12');
hour = new Hour(6, DEFAULT_HOUR_STEP, 12);
expect(hour.toString()).to.equal('06');
hour = new Hour(12, DEFAULT_HOUR_STEP, 12);
expect(hour.toString()).to.equal('12');
hour = new Hour(18, DEFAULT_HOUR_STEP, 12);
expect(hour.toString()).to.equal('06');
hour = new Hour(23, DEFAULT_HOUR_STEP, 12);
expect(hour.toString()).to.equal('11');
});
});
});
});
+102
View File
@@ -0,0 +1,102 @@
import { Minute } from '../../src/models/minute';
import { Direction } from '../../src/types';
import { expect } from 'chai';
describe('Minute', () => {
let minute: Minute;
context('with default config', () => {
beforeEach(() => {
minute = new Minute(2);
});
it('returns value', () => {
expect(minute.value).to.equal(2);
});
it('has correct max value', () => {
expect(minute.maxValue).to.equal(59);
});
describe('toString', () => {
it('renders values correctly', () => {
minute = new Minute(0);
expect(minute.toString()).to.equal('00');
minute = new Minute(5);
expect(minute.toString()).to.equal('05');
minute = new Minute(59);
expect(minute.toString()).to.equal('59');
});
});
describe('stepUpdate', () => {
it('updates up', () => {
minute.stepUpdate(Direction.UP);
expect(minute.value).to.equal(7);
});
it('updates down', () => {
minute.stepUpdate(Direction.DOWN);
expect(minute.value).to.equal(57);
});
it('goes up from 59 to 4', () => {
minute = new Minute(59);
minute.stepUpdate(Direction.UP);
expect(minute.value).to.equal(4);
});
it('goes down from 0 to 55', () => {
minute = new Minute(0);
minute.stepUpdate(Direction.DOWN);
expect(minute.value).to.equal(55);
});
});
describe('setStringValue', () => {
it('sets value from valid string', () => {
minute.setStringValue('3');
expect(minute.value).to.equal(3);
});
it("doesn't set value from invalid string", () => {
minute.setStringValue('test');
expect(minute.value).to.equal(2);
});
it("doesn't set value from outside of scope", () => {
minute.setStringValue('60');
expect(minute.value).to.equal(2);
minute.setStringValue('65');
expect(minute.value).to.equal(2);
});
});
});
context('with a different step size', () => {
beforeEach(() => {
minute = new Minute(2, 15);
});
it('goes up correctly', () => {
minute.stepUpdate(Direction.UP);
expect(minute.value).to.equal(17);
minute.stepUpdate(Direction.UP);
minute.stepUpdate(Direction.UP);
minute.stepUpdate(Direction.UP);
expect(minute.value).to.equal(2);
minute.stepUpdate(Direction.UP);
expect(minute.value).to.equal(17);
});
it('goes down correclty', () => {
minute.stepUpdate(Direction.DOWN);
expect(minute.value).to.equal(47);
});
});
});