diff --git a/.github/workflows/continous-integration.yml b/.github/workflows/continous-integration.yml index 465ad3e3..3cdf9c04 100644 --- a/.github/workflows/continous-integration.yml +++ b/.github/workflows/continous-integration.yml @@ -24,7 +24,7 @@ jobs: - name: Build run: npm run build - name: Test - run: npm run test+coverage+all + run: npm run test+coverage - name: Coveralls uses: coverallsapp/github-action@v1.1.2 with: diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index d7f6a50d..37f14a13 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -32,7 +32,7 @@ jobs: run: npm run release - name: Test - run: npm run test+coverage+all + run: npm run test+coverage - name: Coveralls uses: coverallsapp/github-action@v1.1.2 diff --git a/package.json b/package.json index 47d1bc5d..c96a15c8 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,9 @@ "release": "rollup --environment RELEASE -c", "watch": "rollup -c --watch", "test": "jest", - "test+coverage": "jest --coverage --testPathPattern=test/other", - "test+coverage+all": "jest --coverage", + "test+integration": "jest --testPathPattern=test/entity", + "test+coverage": "jest --coverage", + "test+coverage+unit": "jest --coverage --testPathPattern=test/other", "test+debug": "SET DEBUG_MODE=1&&jest" }, "jest": { diff --git a/src/custom-elements/battery-state-entity.ts b/src/custom-elements/battery-state-entity.ts index 9f346a44..e799e683 100644 --- a/src/custom-elements/battery-state-entity.ts +++ b/src/custom-elements/battery-state-entity.ts @@ -79,16 +79,10 @@ export class BatteryStateEntity extends LovelaceCard { }; this.name = getName(this.config, this.hass); - var { state, level, unit_override} = getBatteryLevel(this.config, this.hass); + var { state, level, unit} = getBatteryLevel(this.config, this.hass); this.state = state; - - if (unit_override === undefined && level !== undefined && this.config.unit !== "" && this.config.unit !== null) { - this.unit = String.fromCharCode(160) + (this.config.unit || this.hass?.states[this.config.entity]?.attributes["unit_of_measurement"] || "%"); - } - else { - this.unit = unit_override; - } - + this.unit = unit; + const isCharging = getChargingState(this.config, this.state, this.hass); this.secondaryInfo = getSecondaryInfo(this.config, this.hass, isCharging); this.icon = getIcon(this.config, level, isCharging, this.hass); diff --git a/src/custom-elements/battery-state-entity.views.ts b/src/custom-elements/battery-state-entity.views.ts index 951fa1ad..28adb52d 100644 --- a/src/custom-elements/battery-state-entity.views.ts +++ b/src/custom-elements/battery-state-entity.views.ts @@ -56,6 +56,8 @@ ${icon(model.icon, model.iconColor)} ${secondaryInfo(model.secondaryInfo, model.hass)}
- ${model.state}${model.unit} + ${model.state}${unit(model.unit)}
-`; \ No newline at end of file +`; + +const unit = (unit: string | undefined) => unit && html` ${unit}`; \ No newline at end of file diff --git a/src/entity-fields/battery-level.ts b/src/entity-fields/battery-level.ts index 8c6ff25d..2104b605 100644 --- a/src/entity-fields/battery-level.ts +++ b/src/entity-fields/battery-level.ts @@ -24,7 +24,8 @@ export const getBatteryLevel = (config: IBatteryEntityConfig, hass?: HomeAssista const processedValue = stringProcessor.process(config.value_override.toString()); return { state: processedValue, - level: isNumber(processedValue) ? Number(processedValue) : undefined + level: isNumber(processedValue) ? Number(processedValue) : undefined, + unit: getUnit(processedValue, undefined, config, hass), } } @@ -101,16 +102,34 @@ export const getBatteryLevel = (config: IBatteryEntityConfig, hass?: HomeAssista // assuming it is a number followed by unit [displayValue, unit] = formattedState.split(" ", 2); - unit = String.fromCharCode(160) + unit; + unit = unit; } return { state: displayValue || state, level: isNumber(state) ? Number(state) : undefined, - unit_override: unit, + unit: getUnit(state, unit, config, hass), }; } +const getUnit = (state: string, unit: string | undefined, config: IBatteryEntityConfig, hass?: HomeAssistantExt): string | undefined => { + if (config.unit) { + // config unit override + unit = config.unit + } + else { + // default unit + unit = unit || hass?.states[config.entity]?.attributes["unit_of_measurement"] || "%" + } + + if (!isNumber(state)) { + // for non numeric states unit should not be rendered + unit = undefined; + } + + return unit; +} + interface IBatteryState { /** * Battery level @@ -125,5 +144,5 @@ interface IBatteryState { /** * Unit override */ - unit_override?: string + unit?: string } \ No newline at end of file diff --git a/test/other/entity-fields/battery-level.test.ts b/test/other/entity-fields/battery-level.test.ts index 95239c1f..06acc235 100644 --- a/test/other/entity-fields/battery-level.test.ts +++ b/test/other/entity-fields/battery-level.test.ts @@ -5,27 +5,30 @@ describe("Battery level", () => { test("is equal value_override setting when it is provided", () => { const hassMock = new HomeAssistantMock(true); - const { state, level } = getBatteryLevel({ entity: "any", value_override: "45" }, hassMock.hass); + const { state, level, unit } = getBatteryLevel({ entity: "any", value_override: "45" }, hassMock.hass); expect(level).toBe(45); expect(state).toBe("45"); + expect(unit).toBe("%"); }); test("is 'Unknown' when entity not found and no localized string", () => { const hassMock = new HomeAssistantMock(true); hassMock.hass.localize = () => null; - const { state, level } = getBatteryLevel({ entity: "any" }, hassMock.hass); + const { state, level, unit } = getBatteryLevel({ entity: "any" }, hassMock.hass); - expect(level).toBeUndefined() + expect(level).toBeUndefined(); expect(state).toBe("Unknown"); + expect(unit).toBeUndefined(); }); test("is 'Unknown' localized string when entity not found", () => { const hassMock = new HomeAssistantMock(true); - const { state, level } = getBatteryLevel({ entity: "any" }, hassMock.hass); + const { state, level, unit } = getBatteryLevel({ entity: "any" }, hassMock.hass); - expect(level).toBeUndefined() + expect(level).toBeUndefined(); expect(state).toBe("[state.default.unknown]"); + expect(unit).toBeUndefined(); }); test("is taken from attribute but attribute is missing", () => { @@ -33,10 +36,11 @@ describe("Battery level", () => { const hassMock = new HomeAssistantMock(true); hassMock.addEntity("Mocked entity", "OK", { battery_state: "45" }); - const { state, level } = getBatteryLevel({ entity: "mocked_entity", attribute: "battery_state_missing" }, hassMock.hass); + const { state, level, unit } = getBatteryLevel({ entity: "mocked_entity", attribute: "battery_state_missing" }, hassMock.hass); - expect(level).toBeUndefined() + expect(level).toBeUndefined(); expect(state).toBe("[state.default.unknown]"); + expect(unit).toBeUndefined(); }); test("is taken from attribute", () => { @@ -44,10 +48,11 @@ describe("Battery level", () => { const hassMock = new HomeAssistantMock(true); hassMock.addEntity("Mocked entity", "OK", { battery_state: "45" }); - const { state, level } = getBatteryLevel({ entity: "mocked_entity", attribute: "battery_state" }, hassMock.hass); + const { state, level, unit } = getBatteryLevel({ entity: "mocked_entity", attribute: "battery_state" }, hassMock.hass); expect(level).toBe(45); expect(state).toBe("45"); + expect(unit).toBe("%"); }); test("is taken from attribute - value includes percentage", () => { @@ -164,41 +169,70 @@ describe("Battery level", () => { }); test.each([ - ["ok", "100", 100, undefined], - ["empty", "0", 0, undefined], - ["20", "20", 20, undefined], - ["charge", "Empty", 0, "Empty"], - ["charge", "StateFromOtherEntity", 0, "{sensor.other_entity.state}"], + ["ok", "100", 100, "%", undefined], + ["empty", "0", 0, "%", undefined], + ["20", "20", 20, "%", undefined], + ["charge", "Empty", 0, "%", "Empty"], + ["charge", "StateFromOtherEntity", 0, "%", "{sensor.other_entity.state}"], ]) - ("state map applied", (entityState: string, expectedState: string, expectedLevel: number | undefined, display?: string) => { + ("state map applied", (entityState: string, expectedState: string, expectedLevel: number | undefined, expectedUnit: string | undefined, display?: string) => { const hassMock = new HomeAssistantMock(true); hassMock.addEntity("Mocked entity", entityState); hassMock.addEntity("Other entity", "StateFromOtherEntity", undefined, "sensor"); - const { state, level } = getBatteryLevel({ entity: "mocked_entity", state_map: [ { from: "ok", to: "100" }, { from: "empty", to: "0" }, { from: "charge", to: "0", display } ] }, hassMock.hass); + const { state, level, unit } = getBatteryLevel({ entity: "mocked_entity", state_map: [ { from: "ok", to: "100" }, { from: "empty", to: "0" }, { from: "charge", to: "0", display } ] }, hassMock.hass); expect(level).toBe(expectedLevel); expect(state).toBe(expectedState); + expect(unit).toBe(expectedUnit); }); test.each([ - [undefined, "45", "dbm", { state: "[45]", level: 45, unit_override: String.fromCharCode(160) + "[dbm]" }], // test default when the setting is not set in the config - [true, "45", "dbm", { state: "[45]", level: 45, unit_override: String.fromCharCode(160) + "[dbm]" }], // test when the setting is explicitly true - [false, "45", "dbm", { state: "45", level: 45, unit_override: undefined }], // test when the setting is turned off - [true, "45", "dbm", { state: "56", level: 56, unit_override: undefined }, [ { from: "45", to: "56" } ]], // test when the state was changed by state_map - [true, "45", "dbm", { state: "33", level: 45, unit_override: undefined }, [ { from: "45", to: "45", display: "33" } ]], // test when the display value was changed by state_map + [undefined, "45", "dbm", { state: "[45]", level: 45, unit: "[dbm]" }], // test default when the setting is not set in the config + [true, "45", "dbm", { state: "[45]", level: 45, unit: "[dbm]" }], // test when the setting is explicitly true + [false, "45", "dbm", { state: "45", level: 45, unit: "%" }], // test when the setting is turned off + [true, "45", "dbm", { state: "56", level: 56, unit: "%" }, [ { from: "45", to: "56" } ]], // test when the state was changed by state_map + [true, "45", "dbm", { state: "33", level: 45, unit: "%" }, [ { from: "45", to: "45", display: "33" } ]], // test when the display value was changed by state_map ]) - ("default HA formatting ", (defaultStateFormatting: boolean | undefined, entityState: string, unitOfMeasurement: string, expected: { state: string, level: number, unit_override?: string }, stateMap: IConvert[] | undefined = undefined) => { + ("default HA formatting ", (defaultStateFormatting: boolean | undefined, entityState: string, unitOfMeasurement: string, expected: { state: string, level: number, unit?: string }, stateMap: IConvert[] | undefined = undefined) => { const hassMock = new HomeAssistantMock(true); hassMock.addEntity("Mocked entity", entityState); hassMock.mockFunc("formatEntityState", (entityData: any) => `[${entityData.state}] [${unitOfMeasurement}]`); - const { state, level, unit_override } = getBatteryLevel({ entity: "mocked_entity", default_state_formatting: defaultStateFormatting, state_map: stateMap }, hassMock.hass); + const { state, level, unit } = getBatteryLevel({ entity: "mocked_entity", default_state_formatting: defaultStateFormatting, state_map: stateMap }, hassMock.hass); expect(level).toBe(expected.level); expect(state).toBe(expected.state); - expect(unit_override).toBe(expected.unit_override); + expect(unit).toBe(expected.unit); }); + + test.each([ + ["OK", undefined, undefined, undefined], + ["45", undefined, undefined, "%"], + ["45", "dBm", undefined, "dBm"], + ["45", "dBm", "rpm", "rpm"], + ])("unit is correct", (entityState: string, entityUnitOfMeasurement: string | undefined, configOverride: string | undefined, expectedUnit: string | undefined) => { + const hassMock = new HomeAssistantMock(true); + const entity = hassMock.addEntity("Mocked entity", entityState, { unit_of_measurement: entityUnitOfMeasurement }); + + const { unit } = getBatteryLevel({ entity: entity.entity_id, default_state_formatting: false, unit: configOverride }, hassMock.hass); + + expect(unit).toBe(expectedUnit); + }) + + test.each([ + ["OK", undefined, undefined, undefined], + ["45", undefined, undefined, "%"], + ["45", "dBm", undefined, "dBm"], + ["45", "dBm", "rpm", "rpm"], + ])("unit is correct when value_override is used", (entityState: string, entityUnitOfMeasurement: string | undefined, configOverride: string | undefined, expectedUnit: string | undefined) => { + const hassMock = new HomeAssistantMock(true); + const entity = hassMock.addEntity("Mocked entity", entityState, { unit_of_measurement: entityUnitOfMeasurement }); + + const { unit } = getBatteryLevel({ entity: entity.entity_id, default_state_formatting: false, unit: configOverride, value_override: "{state}" }, hassMock.hass); + + expect(unit).toBe(expectedUnit); + }) }); \ No newline at end of file