Add more unit tests for common/entity (#24182)

* Add new entity tests

* Improve canToggleDomain test
This commit is contained in:
Wendelin 2025-02-14 21:55:23 +01:00 committed by GitHub
parent 7369b7e0d5
commit 63a98155cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 435 additions and 8 deletions

View File

@ -0,0 +1,45 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { describe, it, expect } from "vitest";
import {
batteryIcon,
batteryLevelIcon,
} from "../../../src/common/entity/battery_icon";
describe("batteryIcon", () => {
it("should return correct icon for battery level", () => {
const stateObj: HassEntity = { state: "50" } as HassEntity;
expect(batteryIcon(stateObj)).toBe("mdi:battery-50");
});
it("should return correct icon for battery level with state", () => {
const stateObj: HassEntity = { state: "50" } as HassEntity;
expect(batteryIcon(stateObj, "20")).toBe("mdi:battery-20");
});
});
describe("batteryLevelIcon", () => {
it("should return correct icon for battery level", () => {
expect(batteryLevelIcon(50)).toBe("mdi:battery-50");
});
it("should return correct icon for charging battery", () => {
expect(batteryLevelIcon(50, true)).toBe("mdi:battery-charging-50");
});
it("should return charging outline icon for charging battery with 9%", () => {
expect(batteryLevelIcon(9, true)).toBe("mdi:battery-charging-outline");
});
it("should return alert icon for low battery", () => {
expect(batteryLevelIcon(5)).toBe("mdi:battery-alert-variant-outline");
});
it("should return unknown icon for invalid battery level", () => {
expect(batteryLevelIcon("invalid")).toBe("mdi:battery-unknown");
});
it("should return battery icon for on/off", () => {
expect(batteryLevelIcon("off")).toBe("mdi:battery");
expect(batteryLevelIcon("on")).toBe("mdi:battery-alert");
});
});

View File

@ -1,6 +1,7 @@
import { assert, describe, it } from "vitest"; import { assert, describe, it } from "vitest";
import { canToggleDomain } from "../../../src/common/entity/can_toggle_domain"; import { canToggleDomain } from "../../../src/common/entity/can_toggle_domain";
import type { HomeAssistant } from "../../../src/types";
describe("canToggleDomain", () => { describe("canToggleDomain", () => {
const hass: any = { const hass: any = {
@ -9,10 +10,6 @@ describe("canToggleDomain", () => {
turn_on: null, // Service keys only need to be present for test turn_on: null, // Service keys only need to be present for test
turn_off: null, turn_off: null,
}, },
lock: {
lock: null,
unlock: null,
},
sensor: { sensor: {
custom_service: null, custom_service: null,
}, },
@ -23,10 +20,6 @@ describe("canToggleDomain", () => {
assert.isTrue(canToggleDomain(hass, "light")); assert.isTrue(canToggleDomain(hass, "light"));
}); });
it("Detects locks toggle", () => {
assert.isTrue(canToggleDomain(hass, "lock"));
});
it("Detects sensors do not toggle", () => { it("Detects sensors do not toggle", () => {
assert.isFalse(canToggleDomain(hass, "sensor")); assert.isFalse(canToggleDomain(hass, "sensor"));
}); });
@ -34,4 +27,58 @@ describe("canToggleDomain", () => {
it("Detects binary sensors do not toggle", () => { it("Detects binary sensors do not toggle", () => {
assert.isFalse(canToggleDomain(hass, "binary_sensor")); assert.isFalse(canToggleDomain(hass, "binary_sensor"));
}); });
it("Detects covers toggle", () => {
assert.isTrue(
canToggleDomain(
{
services: {
cover: {
open_cover: null,
},
},
} as unknown as HomeAssistant,
"cover"
)
);
assert.isFalse(
canToggleDomain(
{
services: {
cover: {
open: null,
},
},
} as unknown as HomeAssistant,
"cover"
)
);
});
it("Detects lock toggle", () => {
assert.isTrue(
canToggleDomain(
{
services: {
lock: {
lock: null,
},
},
} as unknown as HomeAssistant,
"lock"
)
);
assert.isFalse(
canToggleDomain(
{
services: {
lock: {
unlock: null,
},
},
} as unknown as HomeAssistant,
"lock"
)
);
});
}); });

View File

@ -0,0 +1,31 @@
import { describe, it, expect } from "vitest";
import { batteryStateColorProperty } from "../../../../src/common/entity/color/battery_color";
describe("battery_color", () => {
it("should return green for high battery level", () => {
let color = batteryStateColorProperty("70");
expect(color).toBe("--state-sensor-battery-high-color");
color = batteryStateColorProperty("200");
expect(color).toBe("--state-sensor-battery-high-color");
});
it("should return yellow for medium battery level", () => {
let color = batteryStateColorProperty("69.99");
expect(color).toBe("--state-sensor-battery-medium-color");
color = batteryStateColorProperty("30");
expect(color).toBe("--state-sensor-battery-medium-color");
});
it("should return red for low battery level", () => {
let color = batteryStateColorProperty("29.999");
expect(color).toBe("--state-sensor-battery-low-color");
color = batteryStateColorProperty("-20");
expect(color).toBe("--state-sensor-battery-low-color");
});
// add nan test
it("should return undefined for non-numeric state", () => {
const color = batteryStateColorProperty("not a number");
expect(color).toBe(undefined);
});
});

View File

@ -0,0 +1,56 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { describe, it, expect } from "vitest";
import {
computeStateName,
computeStateNameFromEntityAttributes,
} from "../../../src/common/entity/compute_state_name";
describe("computeStateName", () => {
it("should return friendly_name if it exists", () => {
const stateObj = {
entity_id: "light.living_room",
attributes: { friendly_name: "Living Room Light" },
} as HassEntity;
expect(computeStateName(stateObj)).toBe("Living Room Light");
});
it("should return object id if friendly_name does not exist", () => {
const stateObj = {
entity_id: "light.living_room",
attributes: {},
} as HassEntity;
expect(computeStateName(stateObj)).toBe("living room");
});
});
describe("computeStateNameFromEntityAttributes", () => {
it("should return friendly_name if it exists", () => {
const entityId = "light.living_room";
const attributes = { friendly_name: "Living Room Light" };
expect(computeStateNameFromEntityAttributes(entityId, attributes)).toBe(
"Living Room Light"
);
});
it("should return friendly_name 0", () => {
const entityId = "light.living_room";
const attributes = { friendly_name: 0 };
expect(computeStateNameFromEntityAttributes(entityId, attributes)).toBe(
"0"
);
});
it("should return empty if friendly_name is null", () => {
const entityId = "light.living_room";
const attributes = { friendly_name: null };
expect(computeStateNameFromEntityAttributes(entityId, attributes)).toBe("");
});
it("should return object id if friendly_name does not exist", () => {
const entityId = "light.living_room";
const attributes = {};
expect(computeStateNameFromEntityAttributes(entityId, attributes)).toBe(
"living room"
);
});
});

View File

@ -0,0 +1,54 @@
import type { HassEntity } from "home-assistant-js-websocket";
import {
mdiArrowCollapseHorizontal,
mdiArrowDown,
mdiArrowExpandHorizontal,
mdiArrowUp,
} from "@mdi/js";
import { describe, it, expect } from "vitest";
import {
computeOpenIcon,
computeCloseIcon,
} from "../../../src/common/entity/cover_icon";
describe("computeOpenIcon", () => {
it("returns mdiArrowExpandHorizontal for awning, door, gate, and curtain", () => {
const stateObj = { attributes: { device_class: "awning" } } as HassEntity;
expect(computeOpenIcon(stateObj)).toBe(mdiArrowExpandHorizontal);
stateObj.attributes.device_class = "door";
expect(computeOpenIcon(stateObj)).toBe(mdiArrowExpandHorizontal);
stateObj.attributes.device_class = "gate";
expect(computeOpenIcon(stateObj)).toBe(mdiArrowExpandHorizontal);
stateObj.attributes.device_class = "curtain";
expect(computeOpenIcon(stateObj)).toBe(mdiArrowExpandHorizontal);
});
it("returns mdiArrowUp for other device classes", () => {
const stateObj = { attributes: { device_class: "window" } } as HassEntity;
expect(computeOpenIcon(stateObj)).toBe(mdiArrowUp);
});
});
describe("computeCloseIcon", () => {
it("returns mdiArrowCollapseHorizontal for awning, door, gate, and curtain", () => {
const stateObj = { attributes: { device_class: "awning" } } as HassEntity;
expect(computeCloseIcon(stateObj)).toBe(mdiArrowCollapseHorizontal);
stateObj.attributes.device_class = "door";
expect(computeCloseIcon(stateObj)).toBe(mdiArrowCollapseHorizontal);
stateObj.attributes.device_class = "gate";
expect(computeCloseIcon(stateObj)).toBe(mdiArrowCollapseHorizontal);
stateObj.attributes.device_class = "curtain";
expect(computeCloseIcon(stateObj)).toBe(mdiArrowCollapseHorizontal);
});
it("returns mdiArrowDown for other device classes", () => {
const stateObj = { attributes: { device_class: "window" } } as HassEntity;
expect(computeCloseIcon(stateObj)).toBe(mdiArrowDown);
});
});

View File

@ -0,0 +1,194 @@
import { describe, it, expect, vi } from "vitest";
import {
isDeletableEntity,
deleteEntity,
} from "../../../src/common/entity/delete_entity";
import type { HomeAssistant } from "../../../src/types";
import type { EntityRegistryEntry } from "../../../src/data/entity_registry";
import type { IntegrationManifest } from "../../../src/data/integration";
import type { ConfigEntry } from "../../../src/data/config_entries";
import type { Helper } from "../../../src/panels/config/helpers/const";
describe("isDeletableEntity", () => {
it("should return true for restored entities", () => {
const hass = {
states: { "light.test": { attributes: { restored: true } } },
} as unknown as HomeAssistant;
const result = isDeletableEntity(hass, "light.test", [], [], [], []);
expect(result).toBe(true);
});
it("should return false for non-restored entities without config entry", () => {
const hass = {
states: { "light.test": { attributes: {} } },
} as unknown as HomeAssistant;
const entityRegistry = [
{ entity_id: "light.test" },
] as EntityRegistryEntry[];
const result = isDeletableEntity(
hass,
"light.test",
[],
entityRegistry,
[],
[]
);
expect(result).toBe(false);
});
it("should return true for helper domain entities", () => {
const hass = {
states: { "input_boolean.test": { attributes: {} } },
config: { components: ["input_boolean"] },
} as unknown as HomeAssistant;
const entityRegistry = [
{ entity_id: "input_boolean.test", unique_id: "123" },
] as EntityRegistryEntry[];
const fetchedHelpers = [{ id: "123" }] as Helper[];
const result = isDeletableEntity(
hass,
"input_boolean.test",
[],
entityRegistry,
[],
fetchedHelpers
);
expect(result).toBe(true);
});
it("should return false for non-helper domain entities without restored attribute", () => {
const hass = {
states: { "light.test": { attributes: {} } },
} as unknown as HomeAssistant;
const entityRegistry = [
{ entity_id: "light.test" },
] as EntityRegistryEntry[];
const result = isDeletableEntity(
hass,
"light.test",
[],
entityRegistry,
[],
[]
);
expect(result).toBe(false);
});
it("should return true for entities with helper integration type", () => {
const hass = {
states: { "light.test": { attributes: {} } },
} as unknown as HomeAssistant;
const entityRegistry = [
{ entity_id: "light.test", config_entry_id: "config_1" },
] as EntityRegistryEntry[];
const configEntries = [
{ entry_id: "config_1", domain: "light" },
] as ConfigEntry[];
const manifests = [
{ domain: "light", integration_type: "helper" },
] as IntegrationManifest[];
const result = isDeletableEntity(
hass,
"light.test",
manifests,
entityRegistry,
configEntries,
[]
);
expect(result).toBe(true);
});
});
describe("deleteEntity", () => {
it("should call removeEntityRegistryEntry for restored entities", () => {
const removeEntityRegistryEntry = vi.fn();
const hass = {
states: { "light.test": { attributes: { restored: true } } },
callWS: removeEntityRegistryEntry,
} as unknown as HomeAssistant;
const entityRegistry = [
{ entity_id: "light.test" },
] as EntityRegistryEntry[];
deleteEntity(hass, "light.test", [], entityRegistry, [], []);
expect(removeEntityRegistryEntry).toHaveBeenCalledWith({
type: "config/entity_registry/remove",
entity_id: "light.test",
});
});
it("should call deleteConfigEntry for entities with helper integration type", () => {
const deleteConfigEntry = vi.fn();
const hass = {
states: { "light.test": { attributes: {} } },
callApi: deleteConfigEntry,
} as unknown as HomeAssistant;
const entityRegistry = [
{ entity_id: "light.test", config_entry_id: "config_1" },
] as EntityRegistryEntry[];
const configEntries = [
{ entry_id: "config_1", domain: "light" },
] as ConfigEntry[];
const manifests = [
{ domain: "light", integration_type: "helper" },
] as IntegrationManifest[];
deleteEntity(
hass,
"light.test",
manifests,
entityRegistry,
configEntries,
[]
);
expect(deleteConfigEntry).toHaveBeenCalledOnce();
});
it("should call HELPERS_CRUD.delete for helper domain entities", () => {
const deleteCall = vi.fn();
const hass = {
states: { "input_boolean.test": { attributes: {} } },
config: { components: ["input_boolean"] },
callWS: deleteCall,
} as unknown as HomeAssistant;
const entityRegistry = [
{ entity_id: "input_boolean.test", unique_id: "123" },
] as EntityRegistryEntry[];
const fetchedHelpers = [{ id: "123" }] as Helper[];
deleteEntity(
hass,
"input_boolean.test",
[],
entityRegistry,
[],
fetchedHelpers
);
expect(deleteCall).toHaveBeenCalledWith({
type: "input_boolean/delete",
input_boolean_id: "123",
});
});
it("should call removeEntityRegistryEntry for helper domain entities", () => {
const removeEntityRegistryEntry = vi.fn();
const hass = {
states: { "input_boolean.test": { attributes: { restored: true } } },
config: { components: ["input_boolean"] },
callWS: removeEntityRegistryEntry,
} as unknown as HomeAssistant;
const entityRegistry = [
{ entity_id: "input_boolean.test", unique_id: "124" },
] as EntityRegistryEntry[];
const fetchedHelpers = [{ id: "123" }] as Helper[];
deleteEntity(
hass,
"input_boolean.test",
[],
entityRegistry,
[],
fetchedHelpers
);
expect(removeEntityRegistryEntry).toHaveBeenCalledWith({
type: "config/entity_registry/remove",
entity_id: "input_boolean.test",
});
});
});