diff --git a/package.json b/package.json
index 3c9fdbd..c6251b4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "battery-state-card",
- "version": "3.1.3",
+ "version": "3.1.4",
"description": "Battery State card for Home Assistant",
"main": "dist/battery-state-card.js",
"author": "Max Chodorowski",
diff --git a/src/custom-elements/battery-state-card.views.ts b/src/custom-elements/battery-state-card.views.ts
index cfaa33d..b9cb838 100644
--- a/src/custom-elements/battery-state-card.views.ts
+++ b/src/custom-elements/battery-state-card.views.ts
@@ -26,7 +26,7 @@ export const collapsableWrapper = (model: IBatteryGroup, batteries: IBatteryColl
‹
-
+
${model.batteryIds.map(id => batteryWrapper(batteries[id]))}
diff --git a/src/grouping.ts b/src/grouping.ts
index 174c303..91b53c4 100644
--- a/src/grouping.ts
+++ b/src/grouping.ts
@@ -1,6 +1,5 @@
-import { log } from "./utils";
+import { log, toNumber } from "./utils";
import { IBatteryCollection, IBatteryCollectionItem } from "./battery-provider";
-import { BatteryStateEntity } from "./custom-elements/battery-state-entity";
export interface IBatteryGroup {
title?: string;
@@ -87,7 +86,7 @@ const getGroupIndex = (config: IGroupConfig[], battery: IBatteryCollectionItem,
return false
}
- const level = isNaN(Number(battery.state)) ? 0 : Number(battery.state);
+ const level = isNaN(toNumber(battery.state)) ? 0 : toNumber(battery.state);
return level >= group.min! && level <= group.max!;
});
@@ -152,14 +151,14 @@ const getEnrichedText = (text: string, group: IBatteryGroup, batteries: IBattery
text = text.replace(/\{[a-z]+\}/g, keyword => {
switch (keyword) {
case "{min}":
- return group.batteryIds.reduce((agg, id) => agg > Number(batteries[id].state) ? Number(batteries[id].state) : agg, 100).toString();
+ return group.batteryIds.reduce((agg, id) => agg > toNumber(batteries[id].state) ? toNumber(batteries[id].state) : agg, 100).toString();
case "{max}":
- return group.batteryIds.reduce((agg, id) => agg < Number(batteries[id].state) ? Number(batteries[id].state) : agg, 0).toString();
+ return group.batteryIds.reduce((agg, id) => agg < toNumber(batteries[id].state) ? toNumber(batteries[id].state) : agg, 0).toString();
case "{count}":
return group.batteryIds.length.toString();
case "{range}":
- const min = group.batteryIds.reduce((agg, id) => agg > Number(batteries[id].state) ? Number(batteries[id].state) : agg, 100).toString();
- const max = group.batteryIds.reduce((agg, id) => agg < Number(batteries[id].state) ? Number(batteries[id].state) : agg, 0).toString();
+ const min = group.batteryIds.reduce((agg, id) => agg > toNumber(batteries[id].state) ? toNumber(batteries[id].state) : agg, 100).toString();
+ const max = group.batteryIds.reduce((agg, id) => agg < toNumber(batteries[id].state) ? toNumber(batteries[id].state) : agg, 0).toString();
return min == max ? min : min + "-" + max;
default:
return keyword;
diff --git a/test/card/grouping.test.ts b/test/card/grouping.test.ts
new file mode 100644
index 0000000..d19bd8a
--- /dev/null
+++ b/test/card/grouping.test.ts
@@ -0,0 +1,78 @@
+import { BatteryStateCard } from "../../src/custom-elements/battery-state-card";
+import { CardElements, HomeAssistantMock } from "../helpers";
+
+describe("Grouping", () => {
+ test.each([
+ [["10", "24", "25", "26", "50"], 25, "10 %|24 %", "25 %|26 %|50 %"],
+ [["10.1", "24.2", "25.3", "26.4", "50.5"], 25, "10,1 %|24,2 %", "25,3 %|26,4 %|50,5 %", ","],
+ [["10.1", "24.2", "25.3", "26.4", "50.5"], 25, "10.1 %|24.2 %", "25.3 %|26.4 %|50.5 %", "."],
+ ])("works with 'min' setting", async (entityStates: string[], min: number, ungrouped: string, inGroup: string, decimalPoint = ".") => {
+
+ const hass = new HomeAssistantMock();
+ const entities = entityStates.map((state, i) => {
+ const batt = hass.addEntity(`Batt ${i + 1}`, state);
+ return batt.entity_id;
+ });
+ const groupEntity = hass.addEntity("My group", "30", { entity_id: entities }, "group");
+
+ hass.mockFunc("formatEntityState", (entityData: any) => `${entityData.state.replace(".", decimalPoint)} %`);
+
+ const cardElem = hass.addCard("battery-state-card", {
+ title: "Header",
+ entities: [],
+ //sort: "state",
+ collapse: [
+ {
+ group_id: groupEntity.entity_id,
+ min
+ }
+ ]
+ });
+
+ // waiting for card to be updated/rendered
+ await cardElem.cardUpdated;
+
+ const card = new CardElements(cardElem);
+
+ const ungroupedStates = card.items.map(e => e.stateText).join("|");
+ expect(ungroupedStates).toBe(ungrouped);
+
+ expect(card.groupsCount).toBe(1);
+
+ const groupStates = card.group(0).items.map(e => e.stateText).join("|");
+ expect(groupStates).toBe(inGroup);
+ });
+
+ test.each([
+ [["10", "24", "25", "26", "50"], "Min {min}, Max {max}, Range {range}, Count {count}", "Min 25, Max 50, Range 25-50, Count 3"],
+ ])("secondary info keywords", async (entityStates: string[], secondaryInfo: string, expectedSecondaryInfo: string) => {
+
+ const hass = new HomeAssistantMock();
+ const entities = entityStates.map((state, i) => {
+ const batt = hass.addEntity(`Batt ${i + 1}`, state);
+ return batt.entity_id;
+ });
+ const groupEntity = hass.addEntity("My group", "30", { entity_id: entities }, "group");
+
+ const cardElem = hass.addCard("battery-state-card", {
+ title: "Header",
+ entities: [],
+ //sort: "state",
+ collapse: [
+ {
+ group_id: groupEntity.entity_id,
+ min: 25,
+ secondary_info: secondaryInfo
+ }
+ ]
+ });
+
+ // waiting for card to be updated/rendered
+ await cardElem.cardUpdated;
+
+ const card = new CardElements(cardElem);
+
+ expect(card.groupsCount).toBe(1);
+ expect(card.group(0).secondaryInfoText).toBe(expectedSecondaryInfo);
+ });
+});
\ No newline at end of file
diff --git a/test/helpers.ts b/test/helpers.ts
index 593f95f..43c09d9 100644
--- a/test/helpers.ts
+++ b/test/helpers.ts
@@ -26,6 +26,10 @@ export class CardElements {
return this.card.shadowRoot!.querySelectorAll(".card-content > * > battery-state-entity").length;
}
+ get groupsCount() {
+ return this.card.shadowRoot!.querySelectorAll(".card-content > .expandWrapper").length;
+ }
+
get items(): EntityElements[] {
const result: EntityElements[] = [];
for (let index = 0; index < this.itemsCount; index++) {
@@ -35,6 +39,15 @@ export class CardElements {
return result;
}
+ get groups(): GroupElement[] {
+ const result: GroupElement[] = [];
+ for (let index = 0; index < this.groupsCount; index++) {
+ result.push(this.group(index));
+ }
+
+ return result;
+ }
+
item(index: number) {
const entity = this.card.shadowRoot!.querySelectorAll(".card-content > * > battery-state-entity")[index];
if (!entity) {
@@ -43,23 +56,35 @@ export class CardElements {
return new EntityElements(entity);
}
+
+ group(index: number) {
+ const group = this.card.shadowRoot!.querySelectorAll(".card-content > .expandWrapper")[index];
+ if (!group) {
+ throw new Error("Group element not found: " + index);
+ }
+
+ return new GroupElement(group);
+ }
}
export class EntityElements {
- constructor(private card: BatteryStateEntity) {
+ private root: HTMLElement;
+
+ constructor(private card: BatteryStateEntity, isShadowRoot: boolean = true) {
+ this.root = isShadowRoot ? card.shadowRoot! : card;
}
get iconName() {
- return this.card.shadowRoot?.querySelector("ha-icon")?.getAttribute("icon")
+ return this.root.querySelector("ha-icon")?.getAttribute("icon");
}
get nameText() {
- return this.card.shadowRoot?.querySelector(".name")?.textContent?.trim();
+ return this.root.querySelector(".name")?.textContent?.trim();
}
get secondaryInfo() {
- return this.card.shadowRoot?.querySelector(".secondary");
+ return this.root.querySelector(".secondary");
}
get secondaryInfoText() {
@@ -67,13 +92,45 @@ export class EntityElements {
}
get stateText() {
- return this.card.shadowRoot?.querySelector(".state")
+ return this.root.querySelector(".state")
?.textContent
?.trim()
.replace(String.fromCharCode(160), " "); // replace non breakable space
}
}
+export class GroupElement extends EntityElements {
+ constructor(private elem: HTMLElement) {
+ super(elem.querySelector(".toggler"), false);
+ }
+
+ private get batteryNodes(): NodeListOf {
+ return this.elem.querySelectorAll(".groupItems > * > battery-state-entity");
+ }
+
+ get itemsCount() {
+ return this.batteryNodes.length;
+ }
+
+ get items(): EntityElements[] {
+ const result: EntityElements[] = [];
+ for (let index = 0; index < this.itemsCount; index++) {
+ result.push(this.item(index));
+ }
+
+ return result;
+ }
+
+ item(index: number): EntityElements {
+ const entity = this.batteryNodes[index];
+ if (!entity) {
+ throw new Error("Card element not found: " + index);
+ }
+
+ return new EntityElements(entity);
+ }
+}
+
export class HomeAssistantMock> {