diff --git a/test/common/entity/compute_area_name.test.ts b/test/common/entity/compute_area_name.test.ts new file mode 100644 index 0000000000..cd02371ad2 --- /dev/null +++ b/test/common/entity/compute_area_name.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { computeAreaName } from "../../../src/common/entity/compute_area_name"; +import type { AreaRegistryEntry } from "../../../src/data/area_registry"; + +describe("computeAreaName", () => { + it("returns the trimmed name if present", () => { + const area: AreaRegistryEntry = { + name: "Living Room", + } as AreaRegistryEntry; + expect(computeAreaName(area)).toBe("Living Room"); + }); + + it("trims whitespace from the name", () => { + const area: AreaRegistryEntry = { + name: " Kitchen ", + } as AreaRegistryEntry; + expect(computeAreaName(area)).toBe("Kitchen"); + }); + + it("returns undefined if name is missing", () => { + const area: AreaRegistryEntry = {} as AreaRegistryEntry; + expect(computeAreaName(area)).toBeUndefined(); + }); + + it("returns empty string if name is only whitespace", () => { + const area: AreaRegistryEntry = { name: " " } as AreaRegistryEntry; + expect(computeAreaName(area)).toBe(""); + }); +}); diff --git a/test/common/entity/compute_device_name.test.ts b/test/common/entity/compute_device_name.test.ts new file mode 100644 index 0000000000..459374e047 --- /dev/null +++ b/test/common/entity/compute_device_name.test.ts @@ -0,0 +1,130 @@ +import { describe, expect, it, vi } from "vitest"; +import { + computeDeviceName, + computeDeviceNameDisplay, + fallbackDeviceName, + getDuplicatedDeviceNames, +} from "../../../src/common/entity/compute_device_name"; + +describe("computeDeviceName", () => { + it("returns name_by_user if present", () => { + expect( + computeDeviceName({ + name_by_user: "User Name", + name: "Device Name", + } as any) + ).toBe("User Name"); + }); + + it("returns name if name_by_user is not present", () => { + expect(computeDeviceName({ name: "Device Name" } as any)).toBe( + "Device Name" + ); + expect( + computeDeviceName({ name_by_user: "", name: "Device Name" } as any) + ).toBe("Device Name"); + }); + + it("returns undefined if neither name_by_user nor name is present", () => { + expect(computeDeviceName({} as any)).toBeUndefined(); + }); + + it("trims whitespace", () => { + expect(computeDeviceName({ name_by_user: " User Name " } as any)).toBe( + "User Name" + ); + }); +}); + +describe("computeDeviceNameDisplay", () => { + const hass = { + localize: vi.fn((key, params) => { + if (key === "ui.panel.config.devices.unnamed_device") { + return `Unnamed (${params?.type})`; + } + if (key.startsWith("ui.panel.config.devices.type.")) { + return key.split(".").pop(); + } + return key; + }), + states: { + "light.test": { + entity_id: "light.test", + attributes: { friendly_name: "Test Light" }, + }, + }, + } as any; + + it("returns device name if present", () => { + expect(computeDeviceNameDisplay({ name: "Device" } as any, hass)).toBe( + "Device" + ); + }); + + it("returns fallback name from entities if device name not present", () => { + const entities: any = [{ entity_id: "light.test" }]; + expect(computeDeviceNameDisplay({} as any, hass, entities)).toBe( + "Test Light" + ); + }); + + it("returns localized unnamed device if no name or entities", () => { + expect( + computeDeviceNameDisplay({ entry_type: "router" } as any, hass) + ).toBe("Unnamed (router)"); + }); +}); + +describe("fallbackDeviceName", () => { + const hass = { + states: { + "sensor.temp": { + entity_id: "sensor.temp", + attributes: { friendly_name: "Temperature" }, + }, + "light.lamp": { + entity_id: "light.lamp", + attributes: { friendly_name: "Lamp" }, + }, + }, + } as any; + + it("returns the first entity's friendly name", () => { + const entities: any = [ + { entity_id: "sensor.temp" }, + { entity_id: "light.lamp" }, + ]; + expect(fallbackDeviceName(hass, entities)).toBe("Temperature"); + }); + + it("returns undefined if no entities have state", () => { + expect( + fallbackDeviceName({ states: {} } as any, [{ entity_id: "none" } as any]) + ).toBeUndefined(); + }); + + it("works with string entity ids", () => { + expect(fallbackDeviceName(hass, ["light.lamp"])).toBe("Lamp"); + }); +}); + +describe("getDuplicatedDeviceNames", () => { + it("returns a set of duplicated device names", () => { + const devices: any = { + a: { name: "Device" }, + b: { name: "Device" }, + c: { name: "Unique" }, + }; + const result = getDuplicatedDeviceNames(devices); + expect(result.has("Device")).toBe(true); + expect(result.has("Unique")).toBe(false); + }); + + it("returns empty set if no duplicates", () => { + const devices = { + a: { name: "A" }, + b: { name: "B" }, + }; + expect(getDuplicatedDeviceNames(devices as any).size).toBe(0); + }); +}); diff --git a/test/common/entity/compute_entity_name.test.ts b/test/common/entity/compute_entity_name.test.ts new file mode 100644 index 0000000000..978e009dc3 --- /dev/null +++ b/test/common/entity/compute_entity_name.test.ts @@ -0,0 +1,137 @@ +import { describe, expect, it, vi } from "vitest"; +import * as computeDeviceNameModule from "../../../src/common/entity/compute_device_name"; +import { + computeEntityEntryName, + computeEntityName, +} from "../../../src/common/entity/compute_entity_name"; +import * as computeStateNameModule from "../../../src/common/entity/compute_state_name"; +import * as stripPrefixModule from "../../../src/common/entity/strip_prefix_from_entity_name"; + +describe("computeEntityName", () => { + it("returns state name if entity not in registry", () => { + vi.spyOn(computeStateNameModule, "computeStateName").mockReturnValue( + "Kitchen Light" + ); + const stateObj = { + entity_id: "light.kitchen", + attributes: { friendly_name: "Kitchen Light" }, + state: "on", + }; + const hass = { + entities: {}, + devices: {}, + states: { + "light.kitchen": stateObj, + }, + }; + expect(computeEntityName(stateObj as any, hass as any)).toBe( + "Kitchen Light" + ); + vi.restoreAllMocks(); + }); + + it("returns entity entry name if present", () => { + const stateObj = { + entity_id: "light.kitchen", + attributes: {}, + state: "on", + }; + const hass = { + entities: { + "light.kitchen": { + entity_id: "light.kitchen", + name: "Ceiling Light", + }, + }, + devices: {}, + states: { + "light.kitchen": stateObj, + }, + }; + expect(computeEntityName(stateObj as any, hass as any)).toBe( + "Ceiling Light" + ); + }); +}); + +describe("computeEntityEntryName", () => { + it("returns entry.name if no device", () => { + const entry = { entity_id: "light.kitchen", name: "Ceiling Light" }; + const hass = { devices: {}, states: {} }; + expect(computeEntityEntryName(entry as any, hass as any)).toBe( + "Ceiling Light" + ); + }); + + it("returns device-stripped name if device present", () => { + vi.spyOn(computeDeviceNameModule, "computeDeviceName").mockReturnValue( + "Kitchen" + ); + vi.spyOn(stripPrefixModule, "stripPrefixFromEntityName").mockImplementation( + (name, prefix) => name.replace(prefix + " ", "") + ); + const entry = { + entity_id: "light.kitchen", + name: "Kitchen Light", + device_id: "dev1", + }; + const hass = { + devices: { dev1: {} }, + states: {}, + }; + expect(computeEntityEntryName(entry as any, hass as any)).toBe("Light"); + vi.restoreAllMocks(); + }); + + it("returns undefined if device name equals entity name", () => { + vi.spyOn(computeDeviceNameModule, "computeDeviceName").mockReturnValue( + "Kitchen Light" + ); + const entry = { + entity_id: "light.kitchen", + name: "Kitchen Light", + device_id: "dev1", + }; + const hass = { + devices: { dev1: {} }, + states: {}, + }; + expect(computeEntityEntryName(entry as any, hass as any)).toBeUndefined(); + vi.restoreAllMocks(); + }); + + it("falls back to state name if no name and no device", () => { + vi.spyOn(computeStateNameModule, "computeStateName").mockReturnValue( + "Fallback Name" + ); + const entry = { entity_id: "light.kitchen" }; + const hass = { + devices: {}, + states: { + "light.kitchen": { entity_id: "light.kitchen" }, + }, + }; + expect(computeEntityEntryName(entry as any, hass as any)).toBe( + "Fallback Name" + ); + vi.restoreAllMocks(); + }); + + it("returns original_name if present", () => { + const entry = { entity_id: "light.kitchen", original_name: "Old Name" }; + const hass = { + devices: {}, + states: {}, + }; + expect(computeEntityEntryName(entry as any, hass as any)).toBe("Old Name"); + }); + + it("returns undefined if no name, original_name, or device", () => { + const entry = { entity_id: "light.kitchen" }; + const hass = { + devices: {}, + states: {}, + }; + expect(computeEntityEntryName(entry as any, hass as any)).toBeUndefined(); + }); +}); diff --git a/test/common/entity/compute_floor_name.test.ts b/test/common/entity/compute_floor_name.test.ts new file mode 100644 index 0000000000..6cdd78f147 --- /dev/null +++ b/test/common/entity/compute_floor_name.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from "vitest"; +import { computeFloorName } from "../../../src/common/entity/compute_floor_name"; +import type { FloorRegistryEntry } from "../../../src/data/floor_registry"; + +describe("computeFloorName", () => { + it("returns the trimmed name if present", () => { + const floor: FloorRegistryEntry = { name: "Living Room" } as any; + expect(computeFloorName(floor)).toBe("Living Room"); + }); + + it("trims whitespace from the name", () => { + const floor: FloorRegistryEntry = { name: " Upstairs " } as any; + expect(computeFloorName(floor)).toBe("Upstairs"); + }); + + it("returns empty string if name is empty", () => { + const floor: FloorRegistryEntry = { name: "" } as any; + expect(computeFloorName(floor)).toBe(""); + }); + + it("returns undefined if name is undefined", () => { + const floor: FloorRegistryEntry = { name: undefined } as any; + expect(computeFloorName(floor)).toBeUndefined(); + }); +});