Skip to content

Commit

Permalink
Merge pull request #620 from maxwroc/FixRelTime
Browse files Browse the repository at this point in the history
Fixed bug when relative time should be applied automatically
  • Loading branch information
maxwroc authored Dec 29, 2023
2 parents 8a6cba0 + 3fb1cf6 commit bec3dbc
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 47 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
"release": "rollup --environment RELEASE -c",
"watch": "rollup -c --watch",
"test": "jest",
"test+coverage": "jest --coverage --testPathPattern=test/other",
"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": {
Expand Down
12 changes: 3 additions & 9 deletions src/custom-elements/battery-state-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,10 @@ export class BatteryStateEntity extends LovelaceCard<IBatteryEntityConfig> {
};

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);
Expand Down
8 changes: 4 additions & 4 deletions src/custom-elements/battery-state-entity.views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ const replaceTags = (text: string, hass?: HomeAssistant): TemplateResult[] => {
result.push(html`${text.substring(currentPos, matchPos)}`);
}

console.log(matches);

result.push(html`<ha-relative-time .hass="${hass}" .datetime="${new Date(matches[1])}"></ha-relative-time>`);

currentPos += matchPos + matches[0].length;
Expand Down Expand Up @@ -58,6 +56,8 @@ ${icon(model.icon, model.iconColor)}
${secondaryInfo(model.secondaryInfo, model.hass)}
</div>
<div class="state">
${model.state}${model.unit}
${model.state}${unit(model.unit)}
</div>
`;
`;

const unit = (unit: string | undefined) => unit && html`&nbsp;${unit}`;
27 changes: 23 additions & 4 deletions src/entity-fields/battery-level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}

Expand Down Expand Up @@ -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
Expand All @@ -125,5 +144,5 @@ interface IBatteryState {
/**
* Unit override
*/
unit_override?: string
unit?: string
}
2 changes: 1 addition & 1 deletion src/entity-fields/get-secondary-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const getSecondaryInfo = (config: IBatteryEntityConfig, hass: HomeAssista

const dateVal = Date.parse(result);
// The RT tags will be converted to proper HA tags at the views layer
return isNaN(dateVal) ? result : "<rt>" + new Date(dateVal).getTime() + "</rt>";
return isNaN(dateVal) ? result : `<rt>${result}</rt>`;
}

return <any>null;
Expand Down
32 changes: 28 additions & 4 deletions test/entity/secondary-info.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ test("Secondary info date value - renders relative time element", async () => {
const hass = new HomeAssistantMock<BatteryStateEntity>();
const flowerBattery = hass.addEntity("Flower sensor battery level", "80", {});

let dateString = JSON.stringify(new Date(2022, 1, 24, 23, 45, 55));
dateString = dateString.substring(1, dateString.length - 1); // removing quotes
let dateStringSerialized = JSON.stringify(new Date(2022, 1, 24, 23, 45, 55));
const dateString = dateStringSerialized.substring(1, dateStringSerialized.length - 1); // removing quotes
flowerBattery.setLastUpdated(dateString);

const cardElem = hass.addCard("battery-state-entity", {
Expand All @@ -68,5 +68,29 @@ test("Secondary info date value - renders relative time element", async () => {
await cardElem.cardUpdated;

const entity = new EntityElements(cardElem);
expect((<HTMLElement>entity.secondaryInfo?.firstElementChild).tagName).toBe("HA-RELATIVE-TIME");
});
const relTimeElem = <HTMLElement>entity.secondaryInfo?.firstElementChild;
expect(relTimeElem.tagName).toBe("HA-RELATIVE-TIME");
expect(JSON.stringify((<any>relTimeElem).datetime)).toBe(dateStringSerialized);
});

// test("Secondary info date value - renders relative time element with text", async () => {
// const hass = new HomeAssistantMock<BatteryStateEntity>();
// const flowerBattery = hass.addEntity("Flower sensor battery level", "80", {});

// const date = new Date(2022, 1, 24, 23, 45, 55);
// let dateString = JSON.stringify(date);
// dateString = dateString.substring(1, dateString.length - 1); // removing quotes
// flowerBattery.setLastUpdated(dateString);

// const cardElem = hass.addCard("battery-state-entity", {
// entity: flowerBattery.entity_id,
// secondary_info: "Last updated: {last_updated}",
// });

// await cardElem.cardUpdated;

// const entity = new EntityElements(cardElem);
// const relTimeElem = <HTMLElement>entity.secondaryInfo?.firstElementChild;
// expect(relTimeElem.tagName).toBe("HA-RELATIVE-TIME");
// expect((<any>relTimeElem).datetime).toBe(date);
// });
80 changes: 57 additions & 23 deletions test/other/entity-fields/battery-level.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,54 @@ 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 = () => <string><unknown>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", () => {

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", () => {

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", () => {
Expand Down Expand Up @@ -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);
})
});
2 changes: 1 addition & 1 deletion test/other/entity-fields/get-secondary-info.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe("Secondary info", () => {
const secondaryInfoConfig = "{last_changed}";
const result = getSecondaryInfo({ entity: entity.entity_id, secondary_info: secondaryInfoConfig }, hassMock.hass, false);

expect(result).toBe("<rt>1644192000000</rt>");
expect(result).toBe("<rt>2022-02-07</rt>");
})

test("Secondary info config not set'", () => {
Expand Down

0 comments on commit bec3dbc

Please sign in to comment.