616 lines
19 KiB
TypeScript
616 lines
19 KiB
TypeScript
import type { HassEntity } from "home-assistant-js-websocket";
|
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { getStates } from "../../../src/common/entity/get_states";
|
|
import type { HomeAssistant } from "../../../src/types";
|
|
|
|
describe("getStates", () => {
|
|
let mockHass: HomeAssistant;
|
|
|
|
beforeEach(() => {
|
|
mockHass = {
|
|
states: {
|
|
"zone.home": {
|
|
entity_id: "zone.home",
|
|
state: "zoning",
|
|
attributes: {
|
|
friendly_name: "Home",
|
|
},
|
|
} as HassEntity,
|
|
"zone.work": {
|
|
entity_id: "zone.work",
|
|
state: "zoning",
|
|
attributes: {
|
|
friendly_name: "Work",
|
|
},
|
|
} as HassEntity,
|
|
"zone.office": {
|
|
entity_id: "zone.office",
|
|
state: "zoning",
|
|
attributes: {
|
|
friendly_name: "Office",
|
|
},
|
|
} as HassEntity,
|
|
},
|
|
locale: {
|
|
language: "en",
|
|
},
|
|
} as any;
|
|
});
|
|
|
|
const createMockEntity = (
|
|
entity_id: string,
|
|
state = "on",
|
|
attributes: Record<string, any> = {}
|
|
): HassEntity => ({
|
|
entity_id,
|
|
state,
|
|
attributes,
|
|
context: { id: "test", parent_id: null, user_id: null },
|
|
last_changed: "2023-01-01T00:00:00Z",
|
|
last_updated: "2023-01-01T00:00:00Z",
|
|
});
|
|
|
|
describe("Fixed domain states", () => {
|
|
it("should return alarm control panel states", () => {
|
|
const entity = createMockEntity("alarm_control_panel.test");
|
|
const result = getStates(mockHass, entity);
|
|
|
|
expect(result).toEqual(
|
|
expect.arrayContaining([
|
|
"armed_away",
|
|
"armed_custom_bypass",
|
|
"armed_home",
|
|
"armed_night",
|
|
"armed_vacation",
|
|
"arming",
|
|
"disarmed",
|
|
"disarming",
|
|
"pending",
|
|
"triggered",
|
|
"unavailable",
|
|
"unknown",
|
|
])
|
|
);
|
|
expect(result.length).toBe(12);
|
|
});
|
|
|
|
it("should return light states", () => {
|
|
const entity = createMockEntity("light.test");
|
|
const result = getStates(mockHass, entity);
|
|
|
|
expect(result).toContain("on");
|
|
expect(result).toContain("off");
|
|
expect(result).toContain("unavailable");
|
|
expect(result).toContain("unknown");
|
|
expect(result.length).toBe(4);
|
|
});
|
|
|
|
it("should return empty array plus unavailable states for button domain", () => {
|
|
const entity = createMockEntity("button.test");
|
|
const result = getStates(mockHass, entity);
|
|
|
|
expect(result).toContain("unavailable");
|
|
expect(result).toContain("unknown");
|
|
expect(result.length).toBe(2);
|
|
});
|
|
|
|
it("should return weather states", () => {
|
|
const entity = createMockEntity("weather.test");
|
|
const result = getStates(mockHass, entity);
|
|
|
|
expect(result).toEqual(
|
|
expect.arrayContaining([
|
|
"clear-night",
|
|
"cloudy",
|
|
"exceptional",
|
|
"fog",
|
|
"hail",
|
|
"lightning-rainy",
|
|
"lightning",
|
|
"partlycloudy",
|
|
"pouring",
|
|
"rainy",
|
|
"snowy-rainy",
|
|
"snowy",
|
|
"sunny",
|
|
"windy-variant",
|
|
"windy",
|
|
"unavailable",
|
|
"unknown",
|
|
])
|
|
);
|
|
expect(result.length).toBe(17);
|
|
});
|
|
});
|
|
|
|
describe("Fixed domain attribute states", () => {
|
|
it("should return binary sensor device classes", () => {
|
|
const entity = createMockEntity("binary_sensor.test");
|
|
const result = getStates(mockHass, entity, "device_class");
|
|
|
|
expect(result).toEqual(
|
|
expect.arrayContaining([
|
|
"battery",
|
|
"battery_charging",
|
|
"co",
|
|
"cold",
|
|
"connectivity",
|
|
"door",
|
|
"garage_door",
|
|
"gas",
|
|
"heat",
|
|
"light",
|
|
"lock",
|
|
"moisture",
|
|
"motion",
|
|
"moving",
|
|
"occupancy",
|
|
"opening",
|
|
"plug",
|
|
"power",
|
|
"presence",
|
|
"problem",
|
|
"running",
|
|
"safety",
|
|
"smoke",
|
|
"sound",
|
|
"tamper",
|
|
"update",
|
|
"vibration",
|
|
"window",
|
|
])
|
|
);
|
|
expect(result.length).toBe(28);
|
|
});
|
|
|
|
it("should return media player device classes", () => {
|
|
const entity = createMockEntity("media_player.test");
|
|
const result = getStates(mockHass, entity, "device_class");
|
|
|
|
expect(result).toContain("tv");
|
|
expect(result).toContain("speaker");
|
|
expect(result).toContain("receiver");
|
|
expect(result.length).toBe(3);
|
|
});
|
|
|
|
it("should return sensor device classes", () => {
|
|
const entity = createMockEntity("sensor.test");
|
|
const result = getStates(mockHass, entity, "device_class");
|
|
|
|
expect(result).toEqual(
|
|
expect.arrayContaining([
|
|
"apparent_power",
|
|
"aqi",
|
|
"battery",
|
|
"carbon_dioxide",
|
|
"carbon_monoxide",
|
|
"current",
|
|
"date",
|
|
"duration",
|
|
"energy",
|
|
"frequency",
|
|
"gas",
|
|
"humidity",
|
|
"illuminance",
|
|
"monetary",
|
|
"nitrogen_dioxide",
|
|
"nitrogen_monoxide",
|
|
"nitrous_oxide",
|
|
"ozone",
|
|
"ph",
|
|
"pm1",
|
|
"pm10",
|
|
"pm25",
|
|
"power_factor",
|
|
"power",
|
|
"pressure",
|
|
"reactive_power",
|
|
"signal_strength",
|
|
"sulphur_dioxide",
|
|
"temperature",
|
|
"timestamp",
|
|
"volatile_organic_compounds",
|
|
"volatile_organic_compounds_parts",
|
|
"voltage",
|
|
"volume_flow_rate",
|
|
])
|
|
);
|
|
expect(result.length).toBe(34);
|
|
});
|
|
|
|
it("should return empty array for unknown attribute", () => {
|
|
const entity = createMockEntity("light.test");
|
|
const result = getStates(mockHass, entity, "unknown_attribute");
|
|
|
|
expect(result).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("Dynamic climate states", () => {
|
|
it("should return climate hvac_modes for no attribute", () => {
|
|
const entity = createMockEntity("climate.test", "off", {
|
|
hvac_modes: ["heat", "cool", "auto", "off"],
|
|
});
|
|
const result = getStates(mockHass, entity);
|
|
|
|
expect(result).toContain("heat");
|
|
expect(result).toContain("cool");
|
|
expect(result).toContain("auto");
|
|
expect(result).toContain("off");
|
|
expect(result).toContain("unavailable");
|
|
expect(result).toContain("unknown");
|
|
expect(result.length).toBe(6);
|
|
});
|
|
|
|
it("should return climate fan_modes for fan_mode attribute", () => {
|
|
const entity = createMockEntity("climate.test", "heat", {
|
|
fan_modes: ["low", "medium", "high", "auto"],
|
|
});
|
|
const result = getStates(mockHass, entity, "fan_mode");
|
|
|
|
expect(result).toContain("low");
|
|
expect(result).toContain("medium");
|
|
expect(result).toContain("high");
|
|
expect(result).toContain("auto");
|
|
expect(result.length).toBe(4);
|
|
});
|
|
|
|
it("should return preset_modes for preset_mode attribute", () => {
|
|
const entity = createMockEntity("climate.test", "heat", {
|
|
preset_modes: ["eco", "comfort", "sleep"],
|
|
});
|
|
const result = getStates(mockHass, entity, "preset_mode");
|
|
|
|
expect(result).toContain("eco");
|
|
expect(result).toContain("comfort");
|
|
expect(result).toContain("sleep");
|
|
expect(result.length).toBe(3);
|
|
});
|
|
|
|
it("should return swing_modes for swing_mode attribute", () => {
|
|
const entity = createMockEntity("climate.test", "cool", {
|
|
swing_modes: ["on", "off", "vertical", "horizontal"],
|
|
});
|
|
const result = getStates(mockHass, entity, "swing_mode");
|
|
|
|
expect(result).toContain("on");
|
|
expect(result).toContain("off");
|
|
expect(result).toContain("vertical");
|
|
expect(result).toContain("horizontal");
|
|
expect(result.length).toBe(4);
|
|
});
|
|
});
|
|
|
|
describe("Device tracker and person zones", () => {
|
|
it("should return zones for device_tracker without attributes", () => {
|
|
const entity = createMockEntity("device_tracker.test");
|
|
const result = getStates(mockHass, entity);
|
|
|
|
expect(result).toContain("home");
|
|
expect(result).toContain("not_home");
|
|
expect(result).toContain("Work");
|
|
expect(result).toContain("Office");
|
|
expect(result).toContain("unavailable");
|
|
expect(result).toContain("unknown");
|
|
expect(result.length).toBe(6);
|
|
});
|
|
|
|
it("should return zones for person without attributes", () => {
|
|
const entity = createMockEntity("person.test");
|
|
const result = getStates(mockHass, entity);
|
|
|
|
expect(result).toContain("home");
|
|
expect(result).toContain("not_home");
|
|
expect(result).toContain("Work");
|
|
expect(result).toContain("Office");
|
|
expect(result).toContain("unavailable");
|
|
expect(result).toContain("unknown");
|
|
expect(result.length).toBe(6);
|
|
});
|
|
|
|
it("should not return zones for device_tracker with attributes", () => {
|
|
const entity = createMockEntity("device_tracker.test");
|
|
const result = getStates(mockHass, entity, "source_type");
|
|
|
|
expect(result).toContain("bluetooth");
|
|
expect(result).toContain("bluetooth_le");
|
|
expect(result).toContain("router");
|
|
expect(result).toContain("gps");
|
|
expect(result.length).toBe(4);
|
|
});
|
|
});
|
|
|
|
describe("Select and input_select options", () => {
|
|
it("should return options for input_select", () => {
|
|
const entity = createMockEntity("input_select.test", "option1", {
|
|
options: ["option1", "option2", "option3"],
|
|
});
|
|
const result = getStates(mockHass, entity);
|
|
|
|
expect(result).toContain("option1");
|
|
expect(result).toContain("option2");
|
|
expect(result).toContain("option3");
|
|
expect(result).toContain("unavailable");
|
|
expect(result).toContain("unknown");
|
|
expect(result.length).toBe(5);
|
|
});
|
|
|
|
it("should return options for select", () => {
|
|
const entity = createMockEntity("select.test", "mode1", {
|
|
options: ["mode1", "mode2", "mode3"],
|
|
});
|
|
const result = getStates(mockHass, entity);
|
|
|
|
expect(result).toContain("mode1");
|
|
expect(result).toContain("mode2");
|
|
expect(result).toContain("mode3");
|
|
expect(result).toContain("unavailable");
|
|
expect(result).toContain("unknown");
|
|
expect(result.length).toBe(5);
|
|
});
|
|
});
|
|
|
|
describe("Light effects and color modes", () => {
|
|
it("should return effect list for light effect attribute", () => {
|
|
const entity = createMockEntity("light.test", "on", {
|
|
effect_list: ["rainbow", "colorloop", "pulse"],
|
|
});
|
|
const result = getStates(mockHass, entity, "effect");
|
|
|
|
expect(result).toContain("rainbow");
|
|
expect(result).toContain("colorloop");
|
|
expect(result).toContain("pulse");
|
|
expect(result.length).toBe(3);
|
|
});
|
|
|
|
it("should return color modes for light color_mode attribute", () => {
|
|
const entity = createMockEntity("light.test", "on", {
|
|
supported_color_modes: ["rgb", "xy", "hs"],
|
|
});
|
|
const result = getStates(mockHass, entity, "color_mode");
|
|
|
|
expect(result).toContain("rgb");
|
|
expect(result).toContain("xy");
|
|
expect(result).toContain("hs");
|
|
expect(result.length).toBe(3);
|
|
});
|
|
|
|
it("should return empty for light effect without effect_list", () => {
|
|
const entity = createMockEntity("light.test", "on", {});
|
|
const result = getStates(mockHass, entity, "effect");
|
|
|
|
expect(result).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("Media player attributes", () => {
|
|
it("should return sound modes for sound_mode attribute", () => {
|
|
const entity = createMockEntity("media_player.test", "playing", {
|
|
sound_mode_list: ["stereo", "surround", "music"],
|
|
});
|
|
const result = getStates(mockHass, entity, "sound_mode");
|
|
|
|
expect(result).toContain("stereo");
|
|
expect(result).toContain("surround");
|
|
expect(result).toContain("music");
|
|
expect(result.length).toBe(3);
|
|
});
|
|
|
|
it("should return source list for source attribute", () => {
|
|
const entity = createMockEntity("media_player.test", "playing", {
|
|
source_list: ["spotify", "radio", "cd"],
|
|
});
|
|
const result = getStates(mockHass, entity, "source");
|
|
|
|
expect(result).toContain("spotify");
|
|
expect(result).toContain("radio");
|
|
expect(result).toContain("cd");
|
|
expect(result.length).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe("Sensor enum options", () => {
|
|
it("should return options for enum sensor", () => {
|
|
const entity = createMockEntity("sensor.test", "low", {
|
|
device_class: "enum",
|
|
options: ["low", "medium", "high"],
|
|
});
|
|
const result = getStates(mockHass, entity);
|
|
|
|
expect(result).toContain("low");
|
|
expect(result).toContain("medium");
|
|
expect(result).toContain("high");
|
|
expect(result).toContain("unavailable");
|
|
expect(result).toContain("unknown");
|
|
expect(result.length).toBe(5);
|
|
});
|
|
|
|
it("should not return options for non-enum sensor", () => {
|
|
const entity = createMockEntity("sensor.test", "25", {
|
|
device_class: "temperature",
|
|
options: ["low", "medium", "high"], // This shouldn't be used
|
|
});
|
|
const result = getStates(mockHass, entity);
|
|
|
|
expect(result).toContain("unavailable");
|
|
expect(result).toContain("unknown");
|
|
expect(result.length).toBe(2);
|
|
});
|
|
});
|
|
|
|
describe("Other domain specific attributes", () => {
|
|
it("should return event types for event attribute", () => {
|
|
const entity = createMockEntity("event.test", "triggered", {
|
|
event_types: ["button_press", "motion", "door_open"],
|
|
});
|
|
const result = getStates(mockHass, entity, "event_type");
|
|
|
|
expect(result).toContain("button_press");
|
|
expect(result).toContain("motion");
|
|
expect(result).toContain("door_open");
|
|
expect(result.length).toBe(3);
|
|
});
|
|
|
|
it("should return preset modes for fan", () => {
|
|
const entity = createMockEntity("fan.test", "on", {
|
|
preset_modes: ["breeze", "sleep", "auto"],
|
|
});
|
|
const result = getStates(mockHass, entity, "preset_mode");
|
|
|
|
expect(result).toContain("breeze");
|
|
expect(result).toContain("sleep");
|
|
expect(result).toContain("auto");
|
|
expect(result.length).toBe(3);
|
|
});
|
|
|
|
it("should return available modes for humidifier", () => {
|
|
const entity = createMockEntity("humidifier.test", "on", {
|
|
available_modes: ["normal", "eco", "boost"],
|
|
});
|
|
const result = getStates(mockHass, entity, "mode");
|
|
|
|
expect(result).toContain("normal");
|
|
expect(result).toContain("eco");
|
|
expect(result).toContain("boost");
|
|
expect(result.length).toBe(3);
|
|
});
|
|
|
|
it("should return activity list for remote", () => {
|
|
const entity = createMockEntity("remote.test", "on", {
|
|
activity_list: ["watch_tv", "listen_music", "gaming"],
|
|
});
|
|
const result = getStates(mockHass, entity, "current_activity");
|
|
|
|
expect(result).toContain("watch_tv");
|
|
expect(result).toContain("listen_music");
|
|
expect(result).toContain("gaming");
|
|
expect(result.length).toBe(3);
|
|
});
|
|
|
|
it("should return fan speed list for vacuum", () => {
|
|
const entity = createMockEntity("vacuum.test", "cleaning", {
|
|
fan_speed_list: ["quiet", "standard", "high"],
|
|
});
|
|
const result = getStates(mockHass, entity, "fan_speed");
|
|
|
|
expect(result).toContain("quiet");
|
|
expect(result).toContain("standard");
|
|
expect(result).toContain("high");
|
|
expect(result.length).toBe(3);
|
|
});
|
|
|
|
it("should return operation list for water_heater", () => {
|
|
const entity = createMockEntity("water_heater.test", "eco", {
|
|
operation_list: ["eco", "electric", "gas"],
|
|
});
|
|
const result = getStates(mockHass, entity);
|
|
|
|
expect(result).toContain("eco");
|
|
expect(result).toContain("electric");
|
|
expect(result).toContain("gas");
|
|
expect(result).toContain("unavailable");
|
|
expect(result).toContain("unknown");
|
|
expect(result.length).toBe(5);
|
|
});
|
|
|
|
it("should return operation list for water_heater operation_mode attribute", () => {
|
|
const entity = createMockEntity("water_heater.test", "eco", {
|
|
operation_list: ["eco", "electric", "gas"],
|
|
});
|
|
const result = getStates(mockHass, entity, "operation_mode");
|
|
|
|
expect(result).toContain("eco");
|
|
expect(result).toContain("electric");
|
|
expect(result).toContain("gas");
|
|
expect(result.length).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe("Edge cases and boundary conditions", () => {
|
|
it("should handle missing attributes gracefully", () => {
|
|
const entity = createMockEntity("light.test", "off", {});
|
|
const result = getStates(mockHass, entity);
|
|
|
|
// Should still return fixed states and unavailable states
|
|
expect(result).toContain("on");
|
|
expect(result).toContain("off");
|
|
expect(result).toContain("unavailable");
|
|
expect(result).toContain("unknown");
|
|
expect(result.length).toBe(4);
|
|
});
|
|
|
|
it("should handle undefined attribute lists gracefully", () => {
|
|
const entity = createMockEntity("light.test", "on", {
|
|
effect_list: undefined,
|
|
});
|
|
const result = getStates(mockHass, entity, "effect");
|
|
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it("should not include unavailable states when attribute is specified", () => {
|
|
const entity = createMockEntity("binary_sensor.test");
|
|
const result = getStates(mockHass, entity, "device_class");
|
|
|
|
expect(result).not.toContain("unavailable");
|
|
expect(result).not.toContain("unknown");
|
|
});
|
|
|
|
it("should deduplicate states using Set", () => {
|
|
const entity = createMockEntity("light.test", "on", {
|
|
// Simulate case where fixed and dynamic states might overlap
|
|
});
|
|
const result = getStates(mockHass, entity);
|
|
|
|
// Each state should only appear once
|
|
const uniqueStates = [...new Set(result)];
|
|
expect(result.length).toBe(uniqueStates.length);
|
|
});
|
|
|
|
it("should handle empty hass.states object", () => {
|
|
const emptyHass = {
|
|
...mockHass,
|
|
states: {},
|
|
};
|
|
const entity = createMockEntity("device_tracker.test");
|
|
const result = getStates(emptyHass, entity);
|
|
|
|
// Should still return fixed states
|
|
expect(result).toContain("home");
|
|
expect(result).toContain("not_home");
|
|
expect(result).toContain("unavailable");
|
|
expect(result).toContain("unknown");
|
|
expect(result.length).toBe(4);
|
|
});
|
|
|
|
it("should handle zones without friendly_name", () => {
|
|
const hassWithoutFriendlyName = {
|
|
...mockHass,
|
|
states: {
|
|
...mockHass.states,
|
|
"zone.no_name": {
|
|
entity_id: "zone.no_name",
|
|
state: "zoning",
|
|
attributes: {}, // No friendly_name
|
|
} as HassEntity,
|
|
},
|
|
};
|
|
const entity = createMockEntity("device_tracker.test");
|
|
const result = getStates(hassWithoutFriendlyName, entity);
|
|
|
|
// Should not include zones without friendly_name
|
|
expect(result).toEqual(
|
|
expect.arrayContaining([
|
|
"home",
|
|
"not_home",
|
|
"unavailable",
|
|
"unknown",
|
|
"Work",
|
|
"Office",
|
|
])
|
|
);
|
|
expect(result.length).toBe(6);
|
|
});
|
|
});
|
|
});
|