1
0
mirror of https://github.com/home-assistant/frontend.git synced 2025-12-20 02:38:53 +00:00

Consolidate floor sorting with floorCompare() (#27553)

* data/floor_registry: Fix `floorCompare()` argument type

* data/floor_registry: Add test suite for `floorCompare()` fn

* data/floor_registry: Add level-based sorting to `floorCompare()`

Update `floorCompare()` to include level-based sorting between custom order
and name sorting, matching the pattern used in the `getFloors()` helper.

Sort priority:
1. Custom order (if provided)
2. Floor level (lower levels first, with 0 fallback for null)
3. Floor name (alphabetical)

This makes `floorCompare()` consistent with the floor sorting logic used
throughout the codebase and prepares it for actual use.

* areas-strategy-helper: Use `floorCompare()` in `getFloors()` helper

Replace duplicated floor sorting logic in `getFloors()` with the
centralized `floorCompare()` function, matching the pattern used
for areas with `areaCompare()`.

This eliminates code duplication and ensures consistent floor sorting
across the codebase.

* data/area_floor: Use `floorCompare()` in `getAreasAndFloors()`

Replace duplicated floor sorting logic in `getAreasAndFloors()` with
`floorCompare()`, passing `haFloors` directly since it's already in
the correct format (Record<string, FloorRegistryEntry>).

This ensures consistent floor sorting across the codebase.
This commit is contained in:
Tobias Bieniek
2025-10-18 20:16:30 +02:00
committed by GitHub
parent 82ac26b326
commit 5f71938d60
4 changed files with 138 additions and 26 deletions

View File

@@ -1,7 +1,6 @@
import { computeAreaName } from "../common/entity/compute_area_name"; import { computeAreaName } from "../common/entity/compute_area_name";
import { computeDomain } from "../common/entity/compute_domain"; import { computeDomain } from "../common/entity/compute_domain";
import { computeFloorName } from "../common/entity/compute_floor_name"; import { computeFloorName } from "../common/entity/compute_floor_name";
import { stringCompare } from "../common/string/compare";
import type { HaDevicePickerDeviceFilterFunc } from "../components/device/ha-device-picker"; import type { HaDevicePickerDeviceFilterFunc } from "../components/device/ha-device-picker";
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box"; import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
import type { HomeAssistant } from "../types"; import type { HomeAssistant } from "../types";
@@ -13,7 +12,11 @@ import {
} from "./device_registry"; } from "./device_registry";
import type { HaEntityPickerEntityFilterFunc } from "./entity"; import type { HaEntityPickerEntityFilterFunc } from "./entity";
import type { EntityRegistryDisplayEntry } from "./entity_registry"; import type { EntityRegistryDisplayEntry } from "./entity_registry";
import { getFloorAreaLookup, type FloorRegistryEntry } from "./floor_registry"; import {
floorCompare,
getFloorAreaLookup,
type FloorRegistryEntry,
} from "./floor_registry";
export interface FloorComboBoxItem extends PickerComboBoxItem { export interface FloorComboBoxItem extends PickerComboBoxItem {
type: "floor" | "area"; type: "floor" | "area";
@@ -184,6 +187,8 @@ export const getAreasAndFloors = (
(area) => !area.floor_id || !floorAreaLookup[area.floor_id] (area) => !area.floor_id || !floorAreaLookup[area.floor_id]
); );
const compare = floorCompare(haFloors);
// @ts-ignore // @ts-ignore
const floorAreaEntries: [ const floorAreaEntries: [
FloorRegistryEntry | undefined, FloorRegistryEntry | undefined,
@@ -193,12 +198,7 @@ export const getAreasAndFloors = (
const floor = floors.find((fl) => fl.floor_id === floorId)!; const floor = floors.find((fl) => fl.floor_id === floorId)!;
return [floor, floorAreas] as const; return [floor, floorAreas] as const;
}) })
.sort(([floorA], [floorB]) => { .sort(([floorA], [floorB]) => compare(floorA.floor_id, floorB.floor_id));
if (floorA.level !== floorB.level) {
return (floorA.level ?? 0) - (floorB.level ?? 0);
}
return stringCompare(floorA.name, floorB.name);
});
const items: FloorComboBoxItem[] = []; const items: FloorComboBoxItem[] = [];

View File

@@ -68,13 +68,18 @@ export const getFloorAreaLookup = (
}; };
export const floorCompare = export const floorCompare =
(entries?: FloorRegistryEntry[], order?: string[]) => (entries?: HomeAssistant["floors"], order?: string[]) =>
(a: string, b: string) => { (a: string, b: string) => {
const indexA = order ? order.indexOf(a) : -1; const indexA = order ? order.indexOf(a) : -1;
const indexB = order ? order.indexOf(b) : -1; const indexB = order ? order.indexOf(b) : -1;
if (indexA === -1 && indexB === -1) { if (indexA === -1 && indexB === -1) {
const nameA = entries?.[a]?.name ?? a; const floorA = entries?.[a];
const nameB = entries?.[b]?.name ?? b; const floorB = entries?.[b];
if (floorA && floorB && floorA.level !== floorB.level) {
return (floorA.level ?? 0) - (floorB.level ?? 0);
}
const nameA = floorA?.name ?? a;
const nameB = floorB?.name ?? b;
return stringCompare(nameA, nameB); return stringCompare(nameA, nameB);
} }
if (indexA === -1) { if (indexA === -1) {

View File

@@ -3,13 +3,11 @@ import { computeStateName } from "../../../../../common/entity/compute_state_nam
import type { EntityFilterFunc } from "../../../../../common/entity/entity_filter"; import type { EntityFilterFunc } from "../../../../../common/entity/entity_filter";
import { generateEntityFilter } from "../../../../../common/entity/entity_filter"; import { generateEntityFilter } from "../../../../../common/entity/entity_filter";
import { stripPrefixFromEntityName } from "../../../../../common/entity/strip_prefix_from_entity_name"; import { stripPrefixFromEntityName } from "../../../../../common/entity/strip_prefix_from_entity_name";
import { import { orderCompare } from "../../../../../common/string/compare";
orderCompare,
stringCompare,
} from "../../../../../common/string/compare";
import type { AreaRegistryEntry } from "../../../../../data/area_registry"; import type { AreaRegistryEntry } from "../../../../../data/area_registry";
import { areaCompare } from "../../../../../data/area_registry"; import { areaCompare } from "../../../../../data/area_registry";
import type { FloorRegistryEntry } from "../../../../../data/floor_registry"; import type { FloorRegistryEntry } from "../../../../../data/floor_registry";
import { floorCompare } from "../../../../../data/floor_registry";
import type { LovelaceCardConfig } from "../../../../../data/lovelace/config/card"; import type { LovelaceCardConfig } from "../../../../../data/lovelace/config/card";
import type { HomeAssistant } from "../../../../../types"; import type { HomeAssistant } from "../../../../../types";
import { supportsAlarmModesCardFeature } from "../../../card-features/hui-alarm-modes-card-feature"; import { supportsAlarmModesCardFeature } from "../../../card-features/hui-alarm-modes-card-feature";
@@ -304,18 +302,11 @@ export const getFloors = (
floorsOrder?: string[] floorsOrder?: string[]
): FloorRegistryEntry[] => { ): FloorRegistryEntry[] => {
const floors = Object.values(entries); const floors = Object.values(entries);
const compare = orderCompare(floorsOrder || []); const compare = floorCompare(entries, floorsOrder);
return floors.sort((floorA, floorB) => { return floors.sort((floorA, floorB) =>
const order = compare(floorA.floor_id, floorB.floor_id); compare(floorA.floor_id, floorB.floor_id)
if (order !== 0) { );
return order;
}
if (floorA.level !== floorB.level) {
return (floorA.level ?? 0) - (floorB.level ?? 0);
}
return stringCompare(floorA.name, floorB.name);
});
}; };
export const computeAreaPath = (areaId: string): string => `areas-${areaId}`; export const computeAreaPath = (areaId: string): string => `areas-${areaId}`;

View File

@@ -0,0 +1,116 @@
import { describe, expect, it } from "vitest";
import { floorCompare } from "../../src/data/floor_registry";
import type { FloorRegistryEntry } from "../../src/data/floor_registry";
describe("floorCompare", () => {
describe("floorCompare()", () => {
it("sorts by floor ID alphabetically", () => {
const floors = ["basement", "attic", "ground"];
expect(floors.sort(floorCompare())).toEqual([
"attic",
"basement",
"ground",
]);
});
it("handles numeric strings in natural order", () => {
const floors = ["floor10", "floor2", "floor1"];
expect(floors.sort(floorCompare())).toEqual([
"floor1",
"floor2",
"floor10",
]);
});
});
describe("floorCompare(entries)", () => {
it("sorts by level, then by name", () => {
const entries = {
floor1: { name: "Ground Floor", level: 0 } as FloorRegistryEntry,
floor2: { name: "First Floor", level: 1 } as FloorRegistryEntry,
floor3: { name: "Basement", level: -1 } as FloorRegistryEntry,
};
const floors = ["floor1", "floor2", "floor3"];
expect(floors.sort(floorCompare(entries))).toEqual([
"floor3",
"floor1",
"floor2",
]);
});
it("treats null level as 0", () => {
const entries = {
floor1: { name: "Ground Floor", level: 0 } as FloorRegistryEntry,
floor2: { name: "First Floor", level: 1 } as FloorRegistryEntry,
floor3: { name: "Basement", level: null } as FloorRegistryEntry,
};
const floors = ["floor2", "floor3", "floor1"];
expect(floors.sort(floorCompare(entries))).toEqual([
"floor3",
"floor1",
"floor2",
]);
});
it("sorts by name when levels are equal", () => {
const entries = {
floor1: { name: "Suite B", level: 1 } as FloorRegistryEntry,
floor2: { name: "Suite A", level: 1 } as FloorRegistryEntry,
};
const floors = ["floor1", "floor2"];
expect(floors.sort(floorCompare(entries))).toEqual(["floor2", "floor1"]);
});
it("falls back to floor ID when entry not found", () => {
const entries = {
floor1: { name: "Ground Floor" } as FloorRegistryEntry,
};
const floors = ["xyz", "floor1", "abc"];
expect(floors.sort(floorCompare(entries))).toEqual([
"abc",
"floor1",
"xyz",
]);
});
});
describe("floorCompare(entries, order)", () => {
it("follows order array", () => {
const entries = {
basement: { name: "Basement" } as FloorRegistryEntry,
ground: { name: "Ground Floor" } as FloorRegistryEntry,
first: { name: "First Floor" } as FloorRegistryEntry,
};
const order = ["first", "ground", "basement"];
const floors = ["basement", "first", "ground"];
expect(floors.sort(floorCompare(entries, order))).toEqual([
"first",
"ground",
"basement",
]);
});
it("places items not in order array at the end, sorted by name", () => {
const entries = {
floor1: { name: "First Floor" } as FloorRegistryEntry,
floor2: { name: "Ground Floor" } as FloorRegistryEntry,
floor3: { name: "Basement" } as FloorRegistryEntry,
};
const order = ["floor1"];
const floors = ["floor3", "floor2", "floor1"];
expect(floors.sort(floorCompare(entries, order))).toEqual([
"floor1",
"floor3",
"floor2",
]);
});
});
});