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

View File

@@ -68,13 +68,18 @@ export const getFloorAreaLookup = (
};
export const floorCompare =
(entries?: FloorRegistryEntry[], order?: string[]) =>
(entries?: HomeAssistant["floors"], order?: string[]) =>
(a: string, b: string) => {
const indexA = order ? order.indexOf(a) : -1;
const indexB = order ? order.indexOf(b) : -1;
if (indexA === -1 && indexB === -1) {
const nameA = entries?.[a]?.name ?? a;
const nameB = entries?.[b]?.name ?? b;
const floorA = entries?.[a];
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);
}
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 { generateEntityFilter } from "../../../../../common/entity/entity_filter";
import { stripPrefixFromEntityName } from "../../../../../common/entity/strip_prefix_from_entity_name";
import {
orderCompare,
stringCompare,
} from "../../../../../common/string/compare";
import { orderCompare } from "../../../../../common/string/compare";
import type { AreaRegistryEntry } from "../../../../../data/area_registry";
import { areaCompare } from "../../../../../data/area_registry";
import type { FloorRegistryEntry } from "../../../../../data/floor_registry";
import { floorCompare } from "../../../../../data/floor_registry";
import type { LovelaceCardConfig } from "../../../../../data/lovelace/config/card";
import type { HomeAssistant } from "../../../../../types";
import { supportsAlarmModesCardFeature } from "../../../card-features/hui-alarm-modes-card-feature";
@@ -304,18 +302,11 @@ export const getFloors = (
floorsOrder?: string[]
): FloorRegistryEntry[] => {
const floors = Object.values(entries);
const compare = orderCompare(floorsOrder || []);
const compare = floorCompare(entries, floorsOrder);
return floors.sort((floorA, floorB) => {
const order = 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);
});
return floors.sort((floorA, floorB) =>
compare(floorA.floor_id, floorB.floor_id)
);
};
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",
]);
});
});
});