Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed bug when relative time should be applied automatically #620

Merged
merged 3 commits into from
Dec 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading