diff --git a/package-lock.json b/package-lock.json index 2c4ea9f0..eaf1ed53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "custom-card-helpers": "^1.8.0", - "lit": "^2.0.2" + "lit": "^2.1.1" }, "devDependencies": { "@rollup/plugin-node-resolve": "^13.1.3", @@ -18,7 +18,7 @@ "@types/jest": "^27.4.0", "jest": "^24.9.0", "jest-electron": "^0.1.12", - "rollup": "^2.63.0", + "rollup": "^2.65.0", "rollup-plugin-import-css": "^3.0.2", "rollup-plugin-minify-html-literals": "^1.2.6", "rollup-plugin-serve": "^1.1.0", @@ -26,7 +26,7 @@ "rollup-plugin-version-injector": "^1.3.3", "ts-jest": "^24.3.0", "tslib": "^2.3.1", - "typescript": "^4.5.4" + "typescript": "^4.5.5" } }, "node_modules/@babel/code-frame": { @@ -616,9 +616,9 @@ } }, "node_modules/@lit/reactive-element": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.0.2.tgz", - "integrity": "sha512-oz3d3MKjQ2tXynQgyaQaMpGTDNyNDeBdo6dXf1AbjTwhA1IRINHmA7kSaVYv9ttKweNkEoNqp9DqteDdgWzPEg==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.1.1.tgz", + "integrity": "sha512-B2JdRMwCGv+VpIRj3CYVQBx3muPDeE8y+HPgWqzrAHsO5/40BpwDFZeplIV790BaTqDVUDvZOKMSbuFM9zWC0w==" }, "node_modules/@rollup/plugin-node-resolve": { "version": "13.1.3", @@ -4737,28 +4737,28 @@ } }, "node_modules/lit": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.0.2.tgz", - "integrity": "sha512-hKA/1YaSB+P+DvKWuR2q1Xzy/iayhNrJ3aveD0OQ9CKn6wUjsdnF/7LavDOJsKP/K5jzW/kXsuduPgRvTFrFJw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.1.1.tgz", + "integrity": "sha512-yqDqf36IhXwOxIQSFqCMgpfvDCRdxLCLZl7m/+tO5C9W/OBHUj17qZpiMBT35v97QMVKcKEi1KZ3hZRyTwBNsQ==", "dependencies": { - "@lit/reactive-element": "^1.0.0", - "lit-element": "^3.0.0", - "lit-html": "^2.0.0" + "@lit/reactive-element": "^1.1.0", + "lit-element": "^3.1.0", + "lit-html": "^2.1.0" } }, "node_modules/lit-element": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.0.2.tgz", - "integrity": "sha512-9vTJ47D2DSE4Jwhle7aMzEwO2ZcOPRikqfT3CVG7Qol2c9/I4KZwinZNW5Xv8hNm+G/enSSfIwqQhIXi6ioAUg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.1.1.tgz", + "integrity": "sha512-14ClnMAU8EXnzC+M2/KDd3SFmNUn1QUw1+GxWkEMwGV3iaH8ObunMlO5svzvaWlkSV0WlxJCi40NGnDVJ2XZKQ==", "dependencies": { - "@lit/reactive-element": "^1.0.0", - "lit-html": "^2.0.0" + "@lit/reactive-element": "^1.1.0", + "lit-html": "^2.1.0" } }, "node_modules/lit-html": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.0.2.tgz", - "integrity": "sha512-dON7Zg8btb14/fWohQLQBdSgkoiQA4mIUy87evmyJHtxRq7zS6LlC32bT5EPWiof5PUQaDpF45v2OlrxHA5Clg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.1.1.tgz", + "integrity": "sha512-E4BImK6lopAYanJpvcGaAG8kQFF1ccIulPu2BRNZI7acFB6i4ujjjsnaPVFT1j/4lD9r8GKih0Y8d7/LH8SeyQ==", "dependencies": { "@types/trusted-types": "^2.0.2" } @@ -6212,9 +6212,9 @@ } }, "node_modules/rollup": { - "version": "2.63.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.63.0.tgz", - "integrity": "sha512-nps0idjmD+NXl6OREfyYXMn/dar3WGcyKn+KBzPdaLecub3x/LrId0wUcthcr8oZUAcZAR8NKcfGGFlNgGL1kQ==", + "version": "2.65.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.65.0.tgz", + "integrity": "sha512-ohZVYrhtVMTqqeqH26sngfMiyGDg6gCUReOsoflXvYpzUkDHp8sVG8F9FQxjs72OfnLWpXP2nNNqQ9I0vkRovA==", "bin": { "rollup": "dist/bin/rollup" }, @@ -7507,9 +7507,9 @@ "dev": true }, "node_modules/typescript": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", - "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -8407,9 +8407,9 @@ } }, "@lit/reactive-element": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.0.2.tgz", - "integrity": "sha512-oz3d3MKjQ2tXynQgyaQaMpGTDNyNDeBdo6dXf1AbjTwhA1IRINHmA7kSaVYv9ttKweNkEoNqp9DqteDdgWzPEg==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.1.1.tgz", + "integrity": "sha512-B2JdRMwCGv+VpIRj3CYVQBx3muPDeE8y+HPgWqzrAHsO5/40BpwDFZeplIV790BaTqDVUDvZOKMSbuFM9zWC0w==" }, "@rollup/plugin-node-resolve": { "version": "13.1.3", @@ -11718,28 +11718,28 @@ } }, "lit": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.0.2.tgz", - "integrity": "sha512-hKA/1YaSB+P+DvKWuR2q1Xzy/iayhNrJ3aveD0OQ9CKn6wUjsdnF/7LavDOJsKP/K5jzW/kXsuduPgRvTFrFJw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.1.1.tgz", + "integrity": "sha512-yqDqf36IhXwOxIQSFqCMgpfvDCRdxLCLZl7m/+tO5C9W/OBHUj17qZpiMBT35v97QMVKcKEi1KZ3hZRyTwBNsQ==", "requires": { - "@lit/reactive-element": "^1.0.0", - "lit-element": "^3.0.0", - "lit-html": "^2.0.0" + "@lit/reactive-element": "^1.1.0", + "lit-element": "^3.1.0", + "lit-html": "^2.1.0" } }, "lit-element": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.0.2.tgz", - "integrity": "sha512-9vTJ47D2DSE4Jwhle7aMzEwO2ZcOPRikqfT3CVG7Qol2c9/I4KZwinZNW5Xv8hNm+G/enSSfIwqQhIXi6ioAUg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.1.1.tgz", + "integrity": "sha512-14ClnMAU8EXnzC+M2/KDd3SFmNUn1QUw1+GxWkEMwGV3iaH8ObunMlO5svzvaWlkSV0WlxJCi40NGnDVJ2XZKQ==", "requires": { - "@lit/reactive-element": "^1.0.0", - "lit-html": "^2.0.0" + "@lit/reactive-element": "^1.1.0", + "lit-html": "^2.1.0" } }, "lit-html": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.0.2.tgz", - "integrity": "sha512-dON7Zg8btb14/fWohQLQBdSgkoiQA4mIUy87evmyJHtxRq7zS6LlC32bT5EPWiof5PUQaDpF45v2OlrxHA5Clg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.1.1.tgz", + "integrity": "sha512-E4BImK6lopAYanJpvcGaAG8kQFF1ccIulPu2BRNZI7acFB6i4ujjjsnaPVFT1j/4lD9r8GKih0Y8d7/LH8SeyQ==", "requires": { "@types/trusted-types": "^2.0.2" } @@ -12897,9 +12897,9 @@ } }, "rollup": { - "version": "2.63.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.63.0.tgz", - "integrity": "sha512-nps0idjmD+NXl6OREfyYXMn/dar3WGcyKn+KBzPdaLecub3x/LrId0wUcthcr8oZUAcZAR8NKcfGGFlNgGL1kQ==", + "version": "2.65.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.65.0.tgz", + "integrity": "sha512-ohZVYrhtVMTqqeqH26sngfMiyGDg6gCUReOsoflXvYpzUkDHp8sVG8F9FQxjs72OfnLWpXP2nNNqQ9I0vkRovA==", "requires": { "fsevents": "~2.3.2" } @@ -13938,9 +13938,9 @@ "dev": true }, "typescript": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", - "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", "dev": true }, "uglify-js": { diff --git a/package.json b/package.json index 001c1a81..185a7c2f 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@types/jest": "^27.4.0", "jest": "^24.9.0", "jest-electron": "^0.1.12", - "rollup": "^2.63.0", + "rollup": "^2.65.0", "rollup-plugin-import-css": "^3.0.2", "rollup-plugin-minify-html-literals": "^1.2.6", "rollup-plugin-serve": "^1.1.0", @@ -78,10 +78,10 @@ "rollup-plugin-version-injector": "^1.3.3", "ts-jest": "^24.3.0", "tslib": "^2.3.1", - "typescript": "^4.5.4" + "typescript": "^4.5.5" }, "dependencies": { "custom-card-helpers": "^1.8.0", - "lit": "^2.0.2" + "lit": "^2.1.1" } } diff --git a/src/custom-elements/battery-state-entity.ts b/src/custom-elements/battery-state-entity.ts index ddfe9a23..32ee7384 100644 --- a/src/custom-elements/battery-state-entity.ts +++ b/src/custom-elements/battery-state-entity.ts @@ -9,6 +9,8 @@ import entityStyles from "./battery-state-entity.css"; import { handleAction } from "../action"; import { RichStringProcessor } from "../rich-string-processor"; import { getColorForBatteryLevel } from "../colors"; +import { getSecondaryInfo } from "../entity-fields/get-secondary-info"; +import { getChargingState } from "../entity-fields/charging-state"; /** * Some sensor may produce string value like "45%". This regex is meant to parse such values. @@ -68,7 +70,7 @@ export class BatteryStateEntity extends LovelaceCard { public static get styles() { return css([sharedStyles + entityStyles]); } - + async internalUpdate() { this.name = getName(this.config, this.hass); this.state = getBatteryLevel(this.config, this.hass); @@ -116,7 +118,7 @@ export class BatteryStateEntity extends LovelaceCard { entityId: this.config.entity, }, this.hass!); } - + this.addEventListener("click", this.action); this.classList.add("clickable"); } @@ -162,28 +164,6 @@ const getName = (config: IBatteryEntityConfig, hass: HomeAssistant | undefined): return name; } -/** - * Gets secondary info text - * @param config Entity config - * @param hass HomeAssistant state object - * @param isCharging Whther battery is in charging mode - * @returns Secondary info text - */ -const getSecondaryInfo = (config: IBatteryEntityConfig, hass: HomeAssistant | undefined, isCharging: boolean): string | Date => { - if (config.secondary_info) { - const processor = new RichStringProcessor(hass, config.entity, { - "charging": isCharging ? (config.charging_state?.secondary_info_text || "Charging") : "" // todo: think about i18n - }); - - let result = processor.process(config.secondary_info); - - const dateVal = Date.parse(result); - return isNaN(dateVal) ? result : new Date(dateVal); - } - - return null; -} - /** * Getts battery level/state * @param config Entity config @@ -218,7 +198,9 @@ const getBatteryLevel = (config: IBatteryEntityConfig, hass?: HomeAssistant): st entityData.state ]; - level = candidates.find(n => n !== null && n !== undefined)?.toString() || UnknownLevel; + level = candidates.find(val => isNumber(val)) || + candidates.find(val => val !== null && val !== undefined)?.toString() || + UnknownLevel } // check if we should convert value eg. for binary sensors @@ -302,68 +284,3 @@ const getIcon = (config: IBatteryEntityConfig, level: number, isCharging: boolea return (isCharging ? "mdi:battery-charging-" : "mdi:battery-") + roundedLevel; } } - -/** - * Gets flag indicating charging mode - * @param config Entity config - * @param state Battery level/state - * @param hass HomeAssistant state object - * @returns Whether battery is in chargin mode - */ -const getChargingState = (config: IBatteryEntityConfig, state: string, hass?: HomeAssistant): boolean => { - const chargingConfig = config.charging_state; - if (!chargingConfig || !hass) { - return false; - } - - let entityWithChargingState = hass.states[config.entity]; - - // check whether we should use different entity to get charging state - if (chargingConfig.entity_id) { - entityWithChargingState = hass.states[chargingConfig.entity_id] - if (!entityWithChargingState) { - log(`'charging_state' entity id (${chargingConfig.entity_id}) not found`); - return false; - } - - state = entityWithChargingState.state; - } - - const attributesLookup = safeGetArray(chargingConfig.attribute); - // check if we should take the state from attribute - if (attributesLookup.length != 0) { - // take first attribute name which exists on entity - const exisitngAttrib = attributesLookup.find(attr => getValueFromJsonPath(entityWithChargingState.attributes, attr.name) !== undefined); - if (exisitngAttrib) { - return exisitngAttrib.value !== undefined ? - getValueFromJsonPath(entityWithChargingState.attributes, exisitngAttrib.name) == exisitngAttrib.value : - true; - } - else { - // if there is no attribute indicating charging it means the charging state is false - return false; - } - } - - const statesIndicatingCharging = safeGetArray(chargingConfig.state); - - return statesIndicatingCharging.length == 0 ? !!state : statesIndicatingCharging.some(s => s == state); -} - -/** - * Returns value from given object and the path - * @param data Data - * @param path JSON path - * @returns Value from the path - */ -const getValueFromJsonPath = (data: any, path: string) => { - if (data === undefined) { - return data; - } - - path.split(".").forEach(chunk => { - data = data ? data[chunk] : undefined; - }); - - return data; -} \ No newline at end of file diff --git a/src/custom-elements/battery-state-entity.views.ts b/src/custom-elements/battery-state-entity.views.ts index c5365bd1..cea2ff17 100644 --- a/src/custom-elements/battery-state-entity.views.ts +++ b/src/custom-elements/battery-state-entity.views.ts @@ -25,7 +25,7 @@ export const batteryHtml = (model: BatteryStateEntity) => html` ${icon(model.icon, model.iconColor)}
${model.name} - ${typeof(model.secondaryInfo) == "string" ? secondaryInfo(model.secondaryInfo) : secondaryInfoTime(model.hass, model.secondaryInfo)} + ${model.secondaryInfo instanceof Date ? secondaryInfoTime(model.hass, model.secondaryInfo) : secondaryInfo(model.secondaryInfo)}
${model.state}${model.unit} diff --git a/src/entity-fields/charging-state.ts b/src/entity-fields/charging-state.ts new file mode 100644 index 00000000..a3b3909e --- /dev/null +++ b/src/entity-fields/charging-state.ts @@ -0,0 +1,67 @@ +import { HomeAssistant } from "custom-card-helpers/dist/types"; +import { log, safeGetArray } from "../utils"; + +/** + * Gets flag indicating charging mode + * @param config Entity config + * @param state Battery level/state + * @param hass HomeAssistant state object + * @returns Whether battery is in chargin mode + */ + export const getChargingState = (config: IBatteryEntityConfig, state: string, hass?: HomeAssistant): boolean => { + const chargingConfig = config.charging_state; + if (!chargingConfig || !hass) { + return false; + } + + let entityWithChargingState = hass.states[config.entity]; + + // check whether we should use different entity to get charging state + if (chargingConfig.entity_id) { + entityWithChargingState = hass.states[chargingConfig.entity_id] + if (!entityWithChargingState) { + log(`'charging_state' entity id (${chargingConfig.entity_id}) not found`); + return false; + } + + state = entityWithChargingState.state; + } + + const attributesLookup = safeGetArray(chargingConfig.attribute); + // check if we should take the state from attribute + if (attributesLookup.length != 0) { + // take first attribute name which exists on entity + const exisitngAttrib = attributesLookup.find(attr => getValueFromJsonPath(entityWithChargingState.attributes, attr.name) !== undefined); + if (exisitngAttrib) { + return exisitngAttrib.value !== undefined ? + getValueFromJsonPath(entityWithChargingState.attributes, exisitngAttrib.name) == exisitngAttrib.value : + true; + } + else { + // if there is no attribute indicating charging it means the charging state is false + return false; + } + } + + const statesIndicatingCharging = safeGetArray(chargingConfig.state); + + return statesIndicatingCharging.length == 0 ? !!state : statesIndicatingCharging.some(s => s == state); +} + +/** + * Returns value from given object and the path + * @param data Data + * @param path JSON path + * @returns Value from the path + */ + const getValueFromJsonPath = (data: any, path: string) => { + if (data === undefined) { + return data; + } + + path.split(".").forEach(chunk => { + data = data ? data[chunk] : undefined; + }); + + return data; +} \ No newline at end of file diff --git a/src/entity-fields/get-secondary-info.ts b/src/entity-fields/get-secondary-info.ts new file mode 100644 index 00000000..0db5ef3f --- /dev/null +++ b/src/entity-fields/get-secondary-info.ts @@ -0,0 +1,31 @@ +import { HomeAssistant } from "custom-card-helpers/dist/types"; +import { RichStringProcessor } from "../rich-string-processor"; +import { isNumber } from "../utils"; + +/** + * Gets secondary info text + * @param config Entity config + * @param hass HomeAssistant state object + * @param isCharging Whther battery is in charging mode + * @returns Secondary info text + */ +export const getSecondaryInfo = (config: IBatteryEntityConfig, hass: HomeAssistant | undefined, isCharging: boolean): string | Date => { + if (config.secondary_info) { + const processor = new RichStringProcessor(hass, config.entity, { + "charging": isCharging ? (config.charging_state?.secondary_info_text || "Charging") : "" // todo: think about i18n + }); + + let result = processor.process(config.secondary_info); + + // we convert to Date in the next step where number conversion to date is valid too + // although in such cases we want to return the number - not a date + if (isNumber(result)) { + return result; + } + + const dateVal = Date.parse(result); + return isNaN(dateVal) ? result : new Date(dateVal); + } + + return null; +} \ No newline at end of file diff --git a/src/rich-string-processor.ts b/src/rich-string-processor.ts index 0d4e9011..4cdee94d 100644 --- a/src/rich-string-processor.ts +++ b/src/rich-string-processor.ts @@ -85,7 +85,7 @@ const validEntityDomains = ["sensor", "binary_sensor"]; data = JSON.stringify(data); } - return data.toString(); + return data === undefined ? undefined : data.toString(); } } diff --git a/src/utils.ts b/src/utils.ts index cb060686..a72af72c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -17,8 +17,10 @@ export const log = (message: string, level: "warn" | "error" = "warn") => { * Checks whether given value is a number * @param val String value to check */ -export const isNumber = (val: string) => !isNaN(Number(val)); - +export const isNumber = (value: string | number): boolean => + { + return (value !== null && value !== '' && !isNaN(Number(value))) + } /** * Returns array of values regardles if given value is string array or null * @param val Value to process @@ -99,4 +101,4 @@ export const throttledCall = function (func: T, throttleMs: // schedule new call timeoutHook = setTimeout(() => func.apply(null, args), 100); })) as T -} \ No newline at end of file +} diff --git a/test/entity/state.test.ts b/test/entity/state.test.ts index eae4e303..164f909e 100644 --- a/test/entity/state.test.ts +++ b/test/entity/state.test.ts @@ -27,7 +27,7 @@ test.each([ [80.456, 2, "80.46 %"], [80.456, 0, "80 %"], [80.456, undefined, "80.456 %"] -])("State updates", async (state: number, round: number | undefined, expectedState: string) => { +])("State rounding", async (state: number, round: number | undefined, expectedState: string) => { const hass = new HomeAssistantMock(); const sensor = hass.addEntity("Motion sensor battery level", state.toString()); const cardElem = hass.addCard("battery-state-entity", { @@ -89,5 +89,24 @@ test.each([ const entity = new EntityElements(cardElem); + expect(entity.stateText).toBe(expectedState); +}); + +test.each([ + ["Charging", "80", undefined, "80 %"], // value taken from battery_level attribute + ["Charging", undefined, "55", "55 %"], // value taken from battery attribute + ["44", "OneThird", undefined, "44 %"], // value taken from the entity state +])("State value priority", async (entityState: string, batteryLevelAttrib?: string, batteryAttrib?: string, expectedState?: string) => { + const hass = new HomeAssistantMock(); + const sensor = hass.addEntity("Motion sensor battery level", entityState); + sensor.setAttributes({ battery_level: batteryLevelAttrib, battery: batteryAttrib }) + const cardElem = hass.addCard("battery-state-entity", { + entity: sensor.entity_id, + }); + + await cardElem.cardUpdated; + + const entity = new EntityElements(cardElem); + expect(entity.stateText).toBe(expectedState); }); \ No newline at end of file diff --git a/test/helpers.ts b/test/helpers.ts index fec210e7..2b1e33ab 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -159,11 +159,13 @@ interface IEntityAttributes { [key: string]: any; friendly_name?: string; battery_level?: string; + battery?: string; device_class?: string; } interface IEntityMock { readonly entity_id: string; + readonly state: string; setState(state: string): IEntityMock; setAttributes(attribs: IEntityAttributes): IEntityMock; setLastUpdated(val: string): void; diff --git a/test/other/entity-fields/charging-state.test.ts b/test/other/entity-fields/charging-state.test.ts new file mode 100644 index 00000000..d0d443d6 --- /dev/null +++ b/test/other/entity-fields/charging-state.test.ts @@ -0,0 +1,58 @@ +import { getChargingState } from "../../../src/entity-fields/charging-state"; +import { HomeAssistantMock } from "../../helpers"; + + +describe("Charging state", () => { + + test("is false when there is no charging configuration", () => { + const hassMock = new HomeAssistantMock(true); + const isCharging = getChargingState({ entity: "any" }, "90", hassMock.hass); + + expect(isCharging).toBe(false); + }) + + test("is true when charging state is in attribute", () => { + const hassMock = new HomeAssistantMock(true); + const entity = hassMock.addEntity("Sensor", "80", { is_charging: "true" }) + const isCharging = getChargingState( + { entity: entity.entity_id, charging_state: { attribute: [ { name: "is_charging", value: "true" } ] } }, + entity.state, + hassMock.hass); + + expect(isCharging).toBe(true); + }) + + test("is false when charging state is in attribute but attrib value is false", () => { + const hassMock = new HomeAssistantMock(true); + const entity = hassMock.addEntity("Sensor", "80", { is_charging: "true" }) + const isCharging = getChargingState( + { entity: entity.entity_id, charging_state: { attribute: [ { name: "is_charging", value: "false" } ] } }, + entity.state, + hassMock.hass); + + expect(isCharging).toBe(false); + }) + + test("is true when charging state is in attribute (more than one attribute in configuration)", () => { + const hassMock = new HomeAssistantMock(true); + const entity = hassMock.addEntity("Sensor", "80", { is_charging: "true" }) + const isCharging = getChargingState( + { entity: entity.entity_id, charging_state: { attribute: [ { name: "status", value: "charging" }, { name: "is_charging", value: "true" } ] } }, + entity.state, + hassMock.hass); + + expect(isCharging).toBe(true); + }) + + test("is true when charging state is in the external entity state", () => { + const hassMock = new HomeAssistantMock(true); + const entity = hassMock.addEntity("Sensor", "80", { is_charging: "true" }) + const isCharging = getChargingState( + { entity: entity.entity_id, charging_state: { attribute: [ { name: "status", value: "charging" }, { name: "is_charging", value: "true" } ] } }, + entity.state, + hassMock.hass); + + expect(isCharging).toBe(true); + }) + +}); \ No newline at end of file diff --git a/test/other/entity-fields/get-secondary-info.test.ts b/test/other/entity-fields/get-secondary-info.test.ts new file mode 100644 index 00000000..4eaaf615 --- /dev/null +++ b/test/other/entity-fields/get-secondary-info.test.ts @@ -0,0 +1,34 @@ +import { getSecondaryInfo } from "../../../src/entity-fields/get-secondary-info" +import { HomeAssistantMock } from "../../helpers" + +describe("Secondary info", () => { + + test("Unsupported entity domain", () => { + const hassMock = new HomeAssistantMock(true); + const entity = hassMock.addEntity("Motion sensor kitchen", "50", {}, "device_tracker"); + const secondaryInfoConfig = "{" + entity.entity_id + "}"; + const result = getSecondaryInfo({ entity: "any", secondary_info: secondaryInfoConfig }, hassMock.hass, false); + + expect(result).toBe("{device_tracker.motion_sensor_kitchen}"); + }) + + test("Other entity state (number)", () => { + const hassMock = new HomeAssistantMock(true); + const entity = hassMock.addEntity("Motion sensor kitchen", "50", {}, "sensor"); + const secondaryInfoConfig = "{" + entity.entity_id + ".state}"; + const result = getSecondaryInfo({ entity: "any", secondary_info: secondaryInfoConfig }, hassMock.hass, false); + + expect(result).toBe("50"); + }) + + test("Attribute 'last_changed'", () => { + const hassMock = new HomeAssistantMock(true); + const entity = hassMock.addEntity("Motion sensor kitchen", "50", {}, "sensor"); + entity.setLastChanged("2022-02-07"); + const secondaryInfoConfig = "{last_changed}"; + const result = getSecondaryInfo({ entity: entity.entity_id, secondary_info: secondaryInfoConfig }, hassMock.hass, false); + + expect(result).toBeInstanceOf(Date); + expect(JSON.stringify(result).slice(1, -1)).toBe("2022-02-07T00:00:00.000Z"); + }) +}) \ No newline at end of file