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:
@@ -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[] = [];
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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}`;
|
||||||
|
|||||||
116
test/data/floor_registry.test.ts
Normal file
116
test/data/floor_registry.test.ts
Normal 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",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user