mirror of
https://github.com/home-assistant/frontend.git
synced 2026-10-26 23:54:13 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02e9ef9c05 |
No files matched your search
@@ -1,6 +1,5 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { HassEntities, HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity/entity";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { stringCompare } from "../string/compare";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
@@ -248,7 +247,8 @@ const FIXED_DOMAIN_ATTRIBUTE_STATES = {
|
||||
};
|
||||
|
||||
export const getStatesDomain = (
|
||||
hass: HomeAssistant,
|
||||
states: HassEntities,
|
||||
language: string,
|
||||
domain: string,
|
||||
attribute?: string | undefined
|
||||
): string[] => {
|
||||
@@ -274,15 +274,13 @@ export const getStatesDomain = (
|
||||
case "person":
|
||||
if (!attribute) {
|
||||
result.push(
|
||||
...Object.entries(hass.states)
|
||||
...Object.entries(states)
|
||||
.filter(
|
||||
([entityId]) =>
|
||||
computeDomain(entityId) === "zone" && entityId !== "zone.home"
|
||||
)
|
||||
.map(([_entityId, stateObj]) => computeZoneTrackerState(stateObj))
|
||||
.sort((zone1, zone2) =>
|
||||
stringCompare(zone1, zone2, hass.locale.language)
|
||||
)
|
||||
.sort((zone1, zone2) => stringCompare(zone1, zone2, language))
|
||||
);
|
||||
}
|
||||
break;
|
||||
@@ -370,23 +368,24 @@ export const getValueAttribute = (
|
||||
): string | undefined => DOMAIN_VALUE_ATTRIBUTES[domain]?.[optionsAttribute];
|
||||
|
||||
export const getStates = (
|
||||
hass: HomeAssistant,
|
||||
state: HassEntity,
|
||||
states: HassEntities,
|
||||
language: string,
|
||||
stateObj: HassEntity,
|
||||
attribute: string | undefined = undefined
|
||||
): string[] => {
|
||||
const domain = computeStateDomain(state);
|
||||
const domain = computeStateDomain(stateObj);
|
||||
const result: string[] = [];
|
||||
|
||||
// Fixed values based on a domain
|
||||
result.push(...getStatesDomain(hass, domain, attribute));
|
||||
result.push(...getStatesDomain(states, language, domain, attribute));
|
||||
|
||||
// Dynamic values based on the entities
|
||||
const optionsAttribute = getOptionsAttribute(domain, attribute);
|
||||
if (optionsAttribute) {
|
||||
const options = state.attributes[optionsAttribute];
|
||||
const options = stateObj.attributes[optionsAttribute];
|
||||
// Sensors only expose their options when their device class is `enum`.
|
||||
const enumSensor =
|
||||
domain !== "sensor" || state.attributes.device_class === "enum";
|
||||
domain !== "sensor" || stateObj.attributes.device_class === "enum";
|
||||
if (enumSensor && Array.isArray(options)) {
|
||||
result.push(...options);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { ContextType } from "@lit/context";
|
||||
import type { HassEntities, HassEntity } from "home-assistant-js-websocket";
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { consume } from "../../common/decorators/consume";
|
||||
import { consumeEntityStates } from "../../common/decorators/consume-context-entry";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { getStates } from "../../common/entity/get_states";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import {
|
||||
formattersContext,
|
||||
internationalizationContext,
|
||||
statesContext,
|
||||
} from "../../data/context";
|
||||
import type { HomeAssistantFormatters, ValueChangedEvent } from "../../types";
|
||||
import "../ha-generic-picker";
|
||||
import type { PickerComboBoxItem } from "../ha-picker-combo-box";
|
||||
import type { PickerValueRenderer } from "../ha-picker-field";
|
||||
|
||||
@customElement("ha-entity-state-picker")
|
||||
export class HaEntityStatePicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public entityId?: string | string[];
|
||||
|
||||
@property() public attribute?: string;
|
||||
@@ -38,9 +45,26 @@ export class HaEntityStatePicker extends LitElement {
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@state()
|
||||
@consumeEntityStates({ entityIdPath: ["entityId"] })
|
||||
private _stateObjs?: Record<string, HassEntity>;
|
||||
|
||||
@consume({ context: statesContext, subscribe: true })
|
||||
private _states?: ContextType<typeof statesContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: formattersContext, subscribe: true })
|
||||
private _formatters!: ContextType<typeof formattersContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: ContextType<typeof internationalizationContext>;
|
||||
|
||||
private _getItems = memoizeOne(
|
||||
(
|
||||
hass: HomeAssistant,
|
||||
states: HassEntities | null | undefined,
|
||||
formatters: HomeAssistantFormatters,
|
||||
language: string,
|
||||
entityId: string | string[] | undefined,
|
||||
attribute: string | undefined,
|
||||
hideStates: string[] | undefined,
|
||||
@@ -50,20 +74,22 @@ export class HaEntityStatePicker extends LitElement {
|
||||
|
||||
const entitiesOptions = entityIds.map<PickerComboBoxItem[]>(
|
||||
(entityIdItem) => {
|
||||
const stateObj = hass.states[entityIdItem] || {
|
||||
entity_id: entityIdItem,
|
||||
attributes: {},
|
||||
};
|
||||
const stateObj =
|
||||
states?.[entityIdItem] ||
|
||||
({ entity_id: entityIdItem, attributes: {} } as HassEntity);
|
||||
|
||||
const states = getStates(hass, stateObj, attribute).filter(
|
||||
(s) => !hideStates?.includes(s)
|
||||
);
|
||||
const options = getStates(
|
||||
states ?? {},
|
||||
language,
|
||||
stateObj,
|
||||
attribute
|
||||
).filter((s) => !hideStates?.includes(s));
|
||||
|
||||
return states
|
||||
return options
|
||||
.map((s) => {
|
||||
const primary = attribute
|
||||
? hass.formatEntityAttributeValue(stateObj, attribute, s)
|
||||
: hass.formatEntityState(stateObj, s);
|
||||
? formatters.formatEntityAttributeValue(stateObj, attribute, s)
|
||||
: formatters.formatEntityState(stateObj, s);
|
||||
return {
|
||||
id: s,
|
||||
primary,
|
||||
@@ -102,7 +128,9 @@ export class HaEntityStatePicker extends LitElement {
|
||||
_section?: string
|
||||
): PickerComboBoxItem[] =>
|
||||
this._getItems(
|
||||
this.hass,
|
||||
this._states,
|
||||
this._formatters,
|
||||
this._i18n.locale.language,
|
||||
this.entityId,
|
||||
this.attribute,
|
||||
this.hideStates,
|
||||
@@ -121,19 +149,20 @@ export class HaEntityStatePicker extends LitElement {
|
||||
// entity to resolve the translated name; otherwise fall back to "State".
|
||||
if (this.attribute && this.entityId) {
|
||||
const entityId = ensureArray(this.entityId)[0];
|
||||
const stateObj = entityId ? this.hass.states[entityId] : undefined;
|
||||
const stateObj = entityId ? this._stateObjs?.[entityId] : undefined;
|
||||
if (stateObj) {
|
||||
return this.hass.formatEntityAttributeName(stateObj, this.attribute);
|
||||
return this._formatters.formatEntityAttributeName(
|
||||
stateObj,
|
||||
this.attribute
|
||||
);
|
||||
}
|
||||
}
|
||||
return this.hass.localize("ui.components.entity.entity-state-picker.state");
|
||||
return this._i18n.localize(
|
||||
"ui.components.entity.entity-state-picker.state"
|
||||
);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const noEntity = !ensureArray(this.entityId)?.length;
|
||||
|
||||
return html`
|
||||
@@ -147,8 +176,10 @@ export class HaEntityStatePicker extends LitElement {
|
||||
.value=${this.value}
|
||||
.getItems=${this._getFilteredItems}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.notFoundLabel=${this.hass.localize("ui.components.combo-box.no_match")}
|
||||
.customValueLabel=${this.hass.localize(
|
||||
.notFoundLabel=${this._i18n.localize(
|
||||
"ui.components.combo-box.no_match"
|
||||
)}
|
||||
.customValueLabel=${this._i18n.localize(
|
||||
"ui.components.entity.entity-state-picker.add_custom_state"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
|
||||
@@ -6,13 +6,10 @@ import { repeat } from "lit/directives/repeat";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { ANY_STATE_VALUE } from "./const";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "./ha-entity-state-picker";
|
||||
|
||||
@customElement("ha-entity-states-picker")
|
||||
export class HaEntityStatesPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public entityId?: string;
|
||||
|
||||
@property() public attribute?: string;
|
||||
@@ -52,10 +49,6 @@ export class HaEntityStatesPicker extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const value = this.value || [];
|
||||
const hide = [...(this.hideStates || []), ...value];
|
||||
const hideValue = value.includes(ANY_STATE_VALUE);
|
||||
@@ -68,7 +61,6 @@ export class HaEntityStatesPicker extends LitElement {
|
||||
<div>
|
||||
<ha-entity-state-picker
|
||||
.index=${index}
|
||||
.hass=${this.hass}
|
||||
.entityId=${this.entityId}
|
||||
.attribute=${this.attribute}
|
||||
.extraOptions=${this.extraOptions}
|
||||
@@ -94,7 +86,6 @@ export class HaEntityStatesPicker extends LitElement {
|
||||
: keyed(
|
||||
value.length,
|
||||
html`<ha-entity-state-picker
|
||||
.hass=${this.hass}
|
||||
.entityId=${this.entityId}
|
||||
.attribute=${this.attribute}
|
||||
.extraOptions=${this.extraOptions}
|
||||
|
||||
@@ -5,31 +5,15 @@ import {
|
||||
mdiPencil,
|
||||
} from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import type { HassEntities } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, nothing, type PropertyValues } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { consume } from "../../common/decorators/consume";
|
||||
import { transform } from "../../common/decorators/transform";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import {
|
||||
formattersContext,
|
||||
internationalizationContext,
|
||||
registriesContext,
|
||||
statesContext,
|
||||
} from "../../data/context";
|
||||
import type { ObjectSelector } from "../../data/selector";
|
||||
import {
|
||||
formatSelectorValue,
|
||||
type FormatSelectorValueOptions,
|
||||
} from "../../data/selector/format_selector_value";
|
||||
import { formatSelectorValue } from "../../data/selector/format_selector_value";
|
||||
import { showFormDialog } from "../../dialogs/form/show-form-dialog";
|
||||
import type {
|
||||
HomeAssistantFormatters,
|
||||
HomeAssistantInternationalization,
|
||||
HomeAssistantRegistries,
|
||||
} from "../../types";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { computeInitialHaFormData } from "../ha-form/compute-initial-ha-form-data";
|
||||
import type { HaFormSchema } from "../ha-form/types";
|
||||
import "../ha-input-helper-text";
|
||||
@@ -42,6 +26,8 @@ import { deepEqual } from "../../common/util/deep-equal";
|
||||
|
||||
@customElement("ha-selector-object")
|
||||
export class HaObjectSelector extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public selector!: ObjectSelector;
|
||||
|
||||
@property() public value?: any;
|
||||
@@ -61,28 +47,6 @@ export class HaObjectSelector extends LitElement {
|
||||
values?: Record<string, string>
|
||||
) => string;
|
||||
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: HomeAssistantInternationalization;
|
||||
|
||||
@state()
|
||||
@consume({ context: registriesContext, subscribe: true })
|
||||
private _registries!: HomeAssistantRegistries;
|
||||
|
||||
@state()
|
||||
@consume({ context: statesContext, subscribe: true })
|
||||
private _states!: HassEntities;
|
||||
|
||||
@state()
|
||||
@consume({ context: formattersContext, subscribe: true })
|
||||
@transform<
|
||||
HomeAssistantFormatters,
|
||||
HomeAssistantFormatters["formatEntityName"]
|
||||
>({
|
||||
transformer: (formatters) => formatters?.formatEntityName,
|
||||
})
|
||||
private _formatEntityName!: HomeAssistantFormatters["formatEntityName"];
|
||||
|
||||
@query("ha-yaml-editor", true) private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
private _valueChangedFromChild = false;
|
||||
@@ -119,12 +83,7 @@ export class HaObjectSelector extends LitElement {
|
||||
private _formatField(item: any, field: string): string {
|
||||
const fields = this.selector.object!.fields!;
|
||||
const value = formatSelectorValue(
|
||||
this._formatOptions(
|
||||
this._i18n,
|
||||
this._registries,
|
||||
this._states,
|
||||
this._formatEntityName
|
||||
),
|
||||
this.hass,
|
||||
item[field],
|
||||
fields[field]?.selector
|
||||
);
|
||||
@@ -189,14 +148,14 @@ export class HaObjectSelector extends LitElement {
|
||||
slot="end"
|
||||
.item=${item}
|
||||
.index=${index}
|
||||
.label=${this._i18n.localize("ui.common.edit")}
|
||||
.label=${this.hass.localize("ui.common.edit")}
|
||||
.path=${mdiPencil}
|
||||
@click=${this._editItem}
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
slot="end"
|
||||
.index=${index}
|
||||
.label=${this._i18n.localize("ui.common.delete")}
|
||||
.label=${this.hass.localize("ui.common.delete")}
|
||||
.path=${multiple ? mdiDelete : mdiClose}
|
||||
@click=${this._deleteItem}
|
||||
></ha-icon-button>
|
||||
@@ -221,7 +180,7 @@ export class HaObjectSelector extends LitElement {
|
||||
</ha-list-base>
|
||||
</ha-sortable>
|
||||
<ha-button appearance="filled" @click=${this._addItem}>
|
||||
${this._i18n.localize("ui.common.add")}
|
||||
${this.hass.localize("ui.common.add")}
|
||||
</ha-button>
|
||||
</div>
|
||||
`;
|
||||
@@ -237,7 +196,7 @@ export class HaObjectSelector extends LitElement {
|
||||
</ha-list-base>`
|
||||
: html`
|
||||
<ha-button appearance="filled" @click=${this._addItem}>
|
||||
${this._i18n.localize("ui.common.add")}
|
||||
${this.hass.localize("ui.common.add")}
|
||||
</ha-button>
|
||||
`
|
||||
}
|
||||
@@ -260,23 +219,6 @@ export class HaObjectSelector extends LitElement {
|
||||
} `;
|
||||
}
|
||||
|
||||
private _formatOptions = memoizeOne(
|
||||
(
|
||||
i18n: HomeAssistantInternationalization,
|
||||
registries: HomeAssistantRegistries,
|
||||
states: HassEntities,
|
||||
formatEntityName: HomeAssistantFormatters["formatEntityName"]
|
||||
): FormatSelectorValueOptions => ({
|
||||
locale: i18n.locale,
|
||||
localize: i18n.localize,
|
||||
states,
|
||||
devices: registries.devices,
|
||||
areas: registries.areas,
|
||||
floors: registries.floors,
|
||||
formatEntityName,
|
||||
})
|
||||
);
|
||||
|
||||
private _schema = memoizeOne((selector: ObjectSelector) => {
|
||||
if (!selector.object || !selector.object.fields) {
|
||||
return [];
|
||||
@@ -320,12 +262,12 @@ export class HaObjectSelector extends LitElement {
|
||||
};
|
||||
|
||||
const newItem = await showFormDialog(this, {
|
||||
title: this._i18n.localize("ui.common.add"),
|
||||
title: this.hass.localize("ui.common.add"),
|
||||
schema,
|
||||
data,
|
||||
computeLabel: this._computeLabel,
|
||||
computeHelper: this._computeHelper,
|
||||
submitText: this._i18n.localize("ui.common.add"),
|
||||
submitText: this.hass.localize("ui.common.add"),
|
||||
});
|
||||
|
||||
if (newItem === null) {
|
||||
@@ -348,12 +290,12 @@ export class HaObjectSelector extends LitElement {
|
||||
const index = ev.currentTarget.index;
|
||||
|
||||
const updatedItem = await showFormDialog(this, {
|
||||
title: this._i18n.localize("ui.common.edit"),
|
||||
title: this.hass.localize("ui.common.edit"),
|
||||
schema: this._schema(this.selector),
|
||||
data: item,
|
||||
computeLabel: this._computeLabel,
|
||||
computeHelper: this._computeHelper,
|
||||
submitText: this._i18n.localize("ui.common.save"),
|
||||
submitText: this.hass.localize("ui.common.save"),
|
||||
});
|
||||
|
||||
if (updatedItem === null) {
|
||||
|
||||
@@ -74,7 +74,6 @@ export class HaSelectorState extends SubscribeMixin(LitElement) {
|
||||
if (this.selector.state?.multiple) {
|
||||
return html`
|
||||
<ha-entity-states-picker
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityIds}
|
||||
.attribute=${
|
||||
this.selector.state?.attribute || this.context?.filter_attribute
|
||||
@@ -92,7 +91,6 @@ export class HaSelectorState extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
return html`
|
||||
<ha-entity-state-picker
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityIds}
|
||||
.attribute=${
|
||||
this.selector.state?.attribute || this.context?.filter_attribute
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { HassEntities } from "home-assistant-js-websocket";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { createDurationData } from "../../common/datetime/create_duration_data";
|
||||
import { formatDurationLong } from "../../common/datetime/format_duration";
|
||||
@@ -6,27 +5,12 @@ import { normalizeDuration } from "../../common/datetime/normalize_duration";
|
||||
import { computeAreaName } from "../../common/entity/compute_area_name";
|
||||
import { DEFAULT_ENTITY_NAME } from "../../common/entity/compute_entity_name_display";
|
||||
import { blankBeforeUnit } from "../../common/translations/blank_before_unit";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import type { HomeAssistantFormatters } from "../../types";
|
||||
import type { AreaRegistryEntry } from "../area/area_registry";
|
||||
import type { DeviceRegistryEntry } from "../device/device_registry";
|
||||
import type { FloorRegistryEntry } from "../floor_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { Selector } from "../selector";
|
||||
import { getDurationSelectorMode, getDurationSelectorUnits } from "../selector";
|
||||
import type { FrontendLocaleData } from "../translation";
|
||||
|
||||
export interface FormatSelectorValueOptions {
|
||||
locale: FrontendLocaleData;
|
||||
localize: LocalizeFunc;
|
||||
states: HassEntities;
|
||||
devices: Record<string, DeviceRegistryEntry>;
|
||||
areas: Record<string, AreaRegistryEntry>;
|
||||
floors: Record<string, FloorRegistryEntry>;
|
||||
formatEntityName: HomeAssistantFormatters["formatEntityName"];
|
||||
}
|
||||
|
||||
export const formatSelectorValue = (
|
||||
options: FormatSelectorValueOptions,
|
||||
hass: HomeAssistant,
|
||||
value: any,
|
||||
selector?: Selector
|
||||
) => {
|
||||
@@ -63,7 +47,7 @@ export const formatSelectorValue = (
|
||||
return number;
|
||||
}
|
||||
return unit_of_measurement
|
||||
? `${num}${blankBeforeUnit(unit_of_measurement, options.locale)}${unit_of_measurement}`
|
||||
? `${num}${blankBeforeUnit(unit_of_measurement, hass.locale)}${unit_of_measurement}`
|
||||
: num.toString();
|
||||
})
|
||||
.join(", ");
|
||||
@@ -73,7 +57,7 @@ export const formatSelectorValue = (
|
||||
const floors = ensureArray(value);
|
||||
return floors
|
||||
.map((floorId) => {
|
||||
const floor = options.floors[floorId];
|
||||
const floor = hass.floors[floorId];
|
||||
if (!floor) {
|
||||
return floorId;
|
||||
}
|
||||
@@ -86,7 +70,7 @@ export const formatSelectorValue = (
|
||||
const areas = ensureArray(value);
|
||||
return areas
|
||||
.map((areaId) => {
|
||||
const area = options.areas[areaId];
|
||||
const area = hass.areas[areaId];
|
||||
if (!area) {
|
||||
return areaId;
|
||||
}
|
||||
@@ -99,11 +83,11 @@ export const formatSelectorValue = (
|
||||
const entities = ensureArray(value);
|
||||
return entities
|
||||
.map((entityId) => {
|
||||
const stateObj = options.states[entityId];
|
||||
const stateObj = hass.states[entityId];
|
||||
if (!stateObj) {
|
||||
return entityId;
|
||||
}
|
||||
const name = options.formatEntityName(stateObj, DEFAULT_ENTITY_NAME);
|
||||
const name = hass.formatEntityName(stateObj, DEFAULT_ENTITY_NAME);
|
||||
return name || entityId;
|
||||
})
|
||||
.join(", ");
|
||||
@@ -113,7 +97,7 @@ export const formatSelectorValue = (
|
||||
const devices = ensureArray(value);
|
||||
return devices
|
||||
.map((deviceId) => {
|
||||
const device = options.devices[deviceId];
|
||||
const device = hass.devices[deviceId];
|
||||
if (!device) {
|
||||
return deviceId;
|
||||
}
|
||||
@@ -141,7 +125,7 @@ export const formatSelectorValue = (
|
||||
return Object.entries(fields)
|
||||
.filter(([key]) => key in item && item[key] != null)
|
||||
.map(([key, field]) =>
|
||||
formatSelectorValue(options, item[key], field.selector)
|
||||
formatSelectorValue(hass, item[key], field.selector)
|
||||
)
|
||||
.join(" = ");
|
||||
}
|
||||
@@ -159,13 +143,13 @@ export const formatSelectorValue = (
|
||||
data,
|
||||
getDurationSelectorUnits(selector.duration)
|
||||
);
|
||||
const formatted = formatDurationLong(options.locale, duration);
|
||||
const formatted = formatDurationLong(hass.locale, duration);
|
||||
const mode = getDurationSelectorMode(selector.duration);
|
||||
if (!formatted || mode === "positive") {
|
||||
return formatted;
|
||||
}
|
||||
const sign = negative ? "negative" : "positive";
|
||||
return options.localize(
|
||||
return hass.localize(
|
||||
`ui.components.selectors.duration.summary.${mode}_${sign}`,
|
||||
{ duration: formatted }
|
||||
);
|
||||
|
||||
@@ -1,40 +1,34 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { HassEntities, 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;
|
||||
let mockStates: HassEntities;
|
||||
|
||||
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;
|
||||
mockStates = {
|
||||
"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,
|
||||
};
|
||||
});
|
||||
|
||||
const createMockEntity = (
|
||||
@@ -53,7 +47,7 @@ describe("getStates", () => {
|
||||
describe("Fixed domain states", () => {
|
||||
it("should return alarm control panel states", () => {
|
||||
const entity = createMockEntity("alarm_control_panel.test");
|
||||
const result = getStates(mockHass, entity);
|
||||
const result = getStates(mockStates, "en", entity);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
@@ -76,7 +70,7 @@ describe("getStates", () => {
|
||||
|
||||
it("should return light states", () => {
|
||||
const entity = createMockEntity("light.test");
|
||||
const result = getStates(mockHass, entity);
|
||||
const result = getStates(mockStates, "en", entity);
|
||||
|
||||
expect(result).toContain("on");
|
||||
expect(result).toContain("off");
|
||||
@@ -87,7 +81,7 @@ describe("getStates", () => {
|
||||
|
||||
it("should return empty array plus unavailable states for button domain", () => {
|
||||
const entity = createMockEntity("button.test");
|
||||
const result = getStates(mockHass, entity);
|
||||
const result = getStates(mockStates, "en", entity);
|
||||
|
||||
expect(result).toContain("unavailable");
|
||||
expect(result).toContain("unknown");
|
||||
@@ -96,7 +90,7 @@ describe("getStates", () => {
|
||||
|
||||
it("should return weather states", () => {
|
||||
const entity = createMockEntity("weather.test");
|
||||
const result = getStates(mockHass, entity);
|
||||
const result = getStates(mockStates, "en", entity);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
@@ -126,7 +120,7 @@ describe("getStates", () => {
|
||||
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");
|
||||
const result = getStates(mockStates, "en", entity, "device_class");
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
@@ -166,7 +160,7 @@ describe("getStates", () => {
|
||||
|
||||
it("should return media player device classes", () => {
|
||||
const entity = createMockEntity("media_player.test");
|
||||
const result = getStates(mockHass, entity, "device_class");
|
||||
const result = getStates(mockStates, "en", entity, "device_class");
|
||||
|
||||
expect(result).toContain("tv");
|
||||
expect(result).toContain("speaker");
|
||||
@@ -176,7 +170,7 @@ describe("getStates", () => {
|
||||
|
||||
it("should return sensor device classes", () => {
|
||||
const entity = createMockEntity("sensor.test");
|
||||
const result = getStates(mockHass, entity, "device_class");
|
||||
const result = getStates(mockStates, "en", entity, "device_class");
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
@@ -223,7 +217,7 @@ describe("getStates", () => {
|
||||
|
||||
it("should return empty array for unknown attribute", () => {
|
||||
const entity = createMockEntity("light.test");
|
||||
const result = getStates(mockHass, entity, "unknown_attribute");
|
||||
const result = getStates(mockStates, "en", entity, "unknown_attribute");
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
@@ -234,7 +228,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("climate.test", "off", {
|
||||
hvac_modes: ["heat", "cool", "auto", "off"],
|
||||
});
|
||||
const result = getStates(mockHass, entity);
|
||||
const result = getStates(mockStates, "en", entity);
|
||||
|
||||
expect(result).toContain("heat");
|
||||
expect(result).toContain("cool");
|
||||
@@ -249,7 +243,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("climate.test", "heat", {
|
||||
fan_modes: ["low", "medium", "high", "auto"],
|
||||
});
|
||||
const result = getStates(mockHass, entity, "fan_mode");
|
||||
const result = getStates(mockStates, "en", entity, "fan_mode");
|
||||
|
||||
expect(result).toContain("low");
|
||||
expect(result).toContain("medium");
|
||||
@@ -262,7 +256,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("climate.test", "heat", {
|
||||
preset_modes: ["eco", "comfort", "sleep"],
|
||||
});
|
||||
const result = getStates(mockHass, entity, "preset_mode");
|
||||
const result = getStates(mockStates, "en", entity, "preset_mode");
|
||||
|
||||
expect(result).toContain("eco");
|
||||
expect(result).toContain("comfort");
|
||||
@@ -274,7 +268,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("climate.test", "cool", {
|
||||
swing_modes: ["on", "off", "vertical", "horizontal"],
|
||||
});
|
||||
const result = getStates(mockHass, entity, "swing_mode");
|
||||
const result = getStates(mockStates, "en", entity, "swing_mode");
|
||||
|
||||
expect(result).toContain("on");
|
||||
expect(result).toContain("off");
|
||||
@@ -287,7 +281,7 @@ describe("getStates", () => {
|
||||
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);
|
||||
const result = getStates(mockStates, "en", entity);
|
||||
|
||||
expect(result).toContain("home");
|
||||
expect(result).toContain("not_home");
|
||||
@@ -300,7 +294,7 @@ describe("getStates", () => {
|
||||
|
||||
it("should return zones for person without attributes", () => {
|
||||
const entity = createMockEntity("person.test");
|
||||
const result = getStates(mockHass, entity);
|
||||
const result = getStates(mockStates, "en", entity);
|
||||
|
||||
expect(result).toContain("home");
|
||||
expect(result).toContain("not_home");
|
||||
@@ -313,7 +307,7 @@ describe("getStates", () => {
|
||||
|
||||
it("should not return zones for device_tracker with attributes", () => {
|
||||
const entity = createMockEntity("device_tracker.test");
|
||||
const result = getStates(mockHass, entity, "source_type");
|
||||
const result = getStates(mockStates, "en", entity, "source_type");
|
||||
|
||||
expect(result).toContain("bluetooth");
|
||||
expect(result).toContain("bluetooth_le");
|
||||
@@ -328,7 +322,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("input_select.test", "option1", {
|
||||
options: ["option1", "option2", "option3"],
|
||||
});
|
||||
const result = getStates(mockHass, entity);
|
||||
const result = getStates(mockStates, "en", entity);
|
||||
|
||||
expect(result).toContain("option1");
|
||||
expect(result).toContain("option2");
|
||||
@@ -342,7 +336,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("select.test", "mode1", {
|
||||
options: ["mode1", "mode2", "mode3"],
|
||||
});
|
||||
const result = getStates(mockHass, entity);
|
||||
const result = getStates(mockStates, "en", entity);
|
||||
|
||||
expect(result).toContain("mode1");
|
||||
expect(result).toContain("mode2");
|
||||
@@ -358,7 +352,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("light.test", "on", {
|
||||
effect_list: ["rainbow", "colorloop", "pulse"],
|
||||
});
|
||||
const result = getStates(mockHass, entity, "effect");
|
||||
const result = getStates(mockStates, "en", entity, "effect");
|
||||
|
||||
expect(result).toContain("rainbow");
|
||||
expect(result).toContain("colorloop");
|
||||
@@ -370,7 +364,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("light.test", "on", {
|
||||
supported_color_modes: ["rgb", "xy", "hs"],
|
||||
});
|
||||
const result = getStates(mockHass, entity, "color_mode");
|
||||
const result = getStates(mockStates, "en", entity, "color_mode");
|
||||
|
||||
expect(result).toContain("rgb");
|
||||
expect(result).toContain("xy");
|
||||
@@ -380,7 +374,7 @@ describe("getStates", () => {
|
||||
|
||||
it("should return empty for light effect without effect_list", () => {
|
||||
const entity = createMockEntity("light.test", "on", {});
|
||||
const result = getStates(mockHass, entity, "effect");
|
||||
const result = getStates(mockStates, "en", entity, "effect");
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
@@ -391,7 +385,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("media_player.test", "playing", {
|
||||
sound_mode_list: ["stereo", "surround", "music"],
|
||||
});
|
||||
const result = getStates(mockHass, entity, "sound_mode");
|
||||
const result = getStates(mockStates, "en", entity, "sound_mode");
|
||||
|
||||
expect(result).toContain("stereo");
|
||||
expect(result).toContain("surround");
|
||||
@@ -403,7 +397,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("media_player.test", "playing", {
|
||||
source_list: ["spotify", "radio", "cd"],
|
||||
});
|
||||
const result = getStates(mockHass, entity, "source");
|
||||
const result = getStates(mockStates, "en", entity, "source");
|
||||
|
||||
expect(result).toContain("spotify");
|
||||
expect(result).toContain("radio");
|
||||
@@ -418,7 +412,7 @@ describe("getStates", () => {
|
||||
device_class: "enum",
|
||||
options: ["low", "medium", "high"],
|
||||
});
|
||||
const result = getStates(mockHass, entity);
|
||||
const result = getStates(mockStates, "en", entity);
|
||||
|
||||
expect(result).toContain("low");
|
||||
expect(result).toContain("medium");
|
||||
@@ -433,7 +427,7 @@ describe("getStates", () => {
|
||||
device_class: "temperature",
|
||||
options: ["low", "medium", "high"], // This shouldn't be used
|
||||
});
|
||||
const result = getStates(mockHass, entity);
|
||||
const result = getStates(mockStates, "en", entity);
|
||||
|
||||
expect(result).toContain("unavailable");
|
||||
expect(result).toContain("unknown");
|
||||
@@ -446,7 +440,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("event.test", "triggered", {
|
||||
event_types: ["button_press", "motion", "door_open"],
|
||||
});
|
||||
const result = getStates(mockHass, entity, "event_type");
|
||||
const result = getStates(mockStates, "en", entity, "event_type");
|
||||
|
||||
expect(result).toContain("button_press");
|
||||
expect(result).toContain("motion");
|
||||
@@ -458,7 +452,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("fan.test", "on", {
|
||||
preset_modes: ["breeze", "sleep", "auto"],
|
||||
});
|
||||
const result = getStates(mockHass, entity, "preset_mode");
|
||||
const result = getStates(mockStates, "en", entity, "preset_mode");
|
||||
|
||||
expect(result).toContain("breeze");
|
||||
expect(result).toContain("sleep");
|
||||
@@ -470,7 +464,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("humidifier.test", "on", {
|
||||
available_modes: ["normal", "eco", "boost"],
|
||||
});
|
||||
const result = getStates(mockHass, entity, "mode");
|
||||
const result = getStates(mockStates, "en", entity, "mode");
|
||||
|
||||
expect(result).toContain("normal");
|
||||
expect(result).toContain("eco");
|
||||
@@ -482,7 +476,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("remote.test", "on", {
|
||||
activity_list: ["watch_tv", "listen_music", "gaming"],
|
||||
});
|
||||
const result = getStates(mockHass, entity, "current_activity");
|
||||
const result = getStates(mockStates, "en", entity, "current_activity");
|
||||
|
||||
expect(result).toContain("watch_tv");
|
||||
expect(result).toContain("listen_music");
|
||||
@@ -494,7 +488,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("vacuum.test", "cleaning", {
|
||||
fan_speed_list: ["quiet", "standard", "high"],
|
||||
});
|
||||
const result = getStates(mockHass, entity, "fan_speed");
|
||||
const result = getStates(mockStates, "en", entity, "fan_speed");
|
||||
|
||||
expect(result).toContain("quiet");
|
||||
expect(result).toContain("standard");
|
||||
@@ -506,7 +500,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("water_heater.test", "eco", {
|
||||
operation_list: ["eco", "electric", "gas"],
|
||||
});
|
||||
const result = getStates(mockHass, entity);
|
||||
const result = getStates(mockStates, "en", entity);
|
||||
|
||||
expect(result).toContain("eco");
|
||||
expect(result).toContain("electric");
|
||||
@@ -520,7 +514,7 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("water_heater.test", "eco", {
|
||||
operation_list: ["eco", "electric", "gas"],
|
||||
});
|
||||
const result = getStates(mockHass, entity, "operation_mode");
|
||||
const result = getStates(mockStates, "en", entity, "operation_mode");
|
||||
|
||||
expect(result).toContain("eco");
|
||||
expect(result).toContain("electric");
|
||||
@@ -532,7 +526,7 @@ describe("getStates", () => {
|
||||
describe("Edge cases and boundary conditions", () => {
|
||||
it("should handle missing attributes gracefully", () => {
|
||||
const entity = createMockEntity("light.test", "off", {});
|
||||
const result = getStates(mockHass, entity);
|
||||
const result = getStates(mockStates, "en", entity);
|
||||
|
||||
// Should still return fixed states and unavailable states
|
||||
expect(result).toContain("on");
|
||||
@@ -546,14 +540,14 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("light.test", "on", {
|
||||
effect_list: undefined,
|
||||
});
|
||||
const result = getStates(mockHass, entity, "effect");
|
||||
const result = getStates(mockStates, "en", 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");
|
||||
const result = getStates(mockStates, "en", entity, "device_class");
|
||||
|
||||
expect(result).not.toContain("unavailable");
|
||||
expect(result).not.toContain("unknown");
|
||||
@@ -563,20 +557,16 @@ describe("getStates", () => {
|
||||
const entity = createMockEntity("light.test", "on", {
|
||||
// Simulate case where fixed and dynamic states might overlap
|
||||
});
|
||||
const result = getStates(mockHass, entity);
|
||||
const result = getStates(mockStates, "en", 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: {},
|
||||
};
|
||||
it("should handle empty states object", () => {
|
||||
const entity = createMockEntity("device_tracker.test");
|
||||
const result = getStates(emptyHass, entity);
|
||||
const result = getStates({}, "en", entity);
|
||||
|
||||
// Should still return fixed states
|
||||
expect(result).toContain("home");
|
||||
@@ -587,19 +577,16 @@ describe("getStates", () => {
|
||||
});
|
||||
|
||||
it("names zones without friendly_name after their object id", () => {
|
||||
const hassWithoutFriendlyName = {
|
||||
...mockHass,
|
||||
states: {
|
||||
...mockHass.states,
|
||||
"zone.no_name": {
|
||||
entity_id: "zone.no_name",
|
||||
state: "zoning",
|
||||
attributes: {}, // No friendly_name
|
||||
} as HassEntity,
|
||||
},
|
||||
const statesWithoutFriendlyName = {
|
||||
...mockStates,
|
||||
"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);
|
||||
const result = getStates(statesWithoutFriendlyName, "en", entity);
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.arrayContaining([
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
formattersContext,
|
||||
internationalizationContext,
|
||||
registriesContext,
|
||||
statesContext,
|
||||
} from "../../../src/data/context";
|
||||
import type { ObjectSelector } from "../../../src/data/selector";
|
||||
import type {
|
||||
HomeAssistantFormatters,
|
||||
HomeAssistantInternationalization,
|
||||
} from "../../../src/types";
|
||||
import type { HomeAssistant } from "../../../src/types";
|
||||
import type { HaObjectSelector } from "../../../src/components/ha-selector/ha-selector-object";
|
||||
import type {
|
||||
FormDialogData,
|
||||
@@ -43,6 +33,16 @@ vi.mock("../../../src/components/ha-yaml-editor", () => {
|
||||
return {};
|
||||
});
|
||||
|
||||
const hass = {
|
||||
localize: (key: string) => key,
|
||||
locale: "en-US",
|
||||
floors: {},
|
||||
areas: {},
|
||||
devices: {},
|
||||
states: {},
|
||||
formatEntityName: () => "",
|
||||
} as unknown as HomeAssistant;
|
||||
|
||||
const selectorConfig = {
|
||||
object: {
|
||||
multiple: true,
|
||||
@@ -67,32 +67,13 @@ const mountSelector = async (
|
||||
value: FormDialogData | FormDialogData[] | "",
|
||||
config: ObjectSelector = selectorConfig
|
||||
) => {
|
||||
const host = document.createElement("div");
|
||||
new ContextProvider(host, {
|
||||
context: internationalizationContext,
|
||||
initialValue: {
|
||||
localize: (key: string) => key,
|
||||
locale: { language: "en" },
|
||||
} as unknown as HomeAssistantInternationalization,
|
||||
});
|
||||
new ContextProvider(host, {
|
||||
context: registriesContext,
|
||||
initialValue: { entities: {}, devices: {}, areas: {}, floors: {} },
|
||||
});
|
||||
new ContextProvider(host, { context: statesContext, initialValue: {} });
|
||||
new ContextProvider(host, {
|
||||
context: formattersContext,
|
||||
initialValue: {
|
||||
formatEntityName: () => "",
|
||||
} as unknown as HomeAssistantFormatters,
|
||||
});
|
||||
const selector = document.createElement(
|
||||
"ha-selector-object"
|
||||
) as HaObjectSelector;
|
||||
selector.hass = hass;
|
||||
selector.selector = config;
|
||||
selector.value = value;
|
||||
host.append(selector);
|
||||
document.body.append(host);
|
||||
document.body.append(selector);
|
||||
await selector.updateComplete;
|
||||
return selector;
|
||||
};
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
formatSelectorValue,
|
||||
type FormatSelectorValueOptions,
|
||||
} from "../../src/data/selector/format_selector_value";
|
||||
import { formatSelectorValue } from "../../src/data/selector/format_selector_value";
|
||||
import type { HomeAssistant } from "../../src/types";
|
||||
|
||||
// These selectors read none of the options.
|
||||
const options = {} as FormatSelectorValueOptions;
|
||||
// formatSelectorValue only touches hass for floor/area/entity/device
|
||||
// selectors, none of which these tests exercise.
|
||||
const hass = {} as HomeAssistant;
|
||||
|
||||
describe("formatSelectorValue", () => {
|
||||
it("returns an empty string for nullish values", () => {
|
||||
expect(formatSelectorValue(options, null)).toBe("");
|
||||
expect(formatSelectorValue(options, undefined)).toBe("");
|
||||
expect(formatSelectorValue(hass, null)).toBe("");
|
||||
expect(formatSelectorValue(hass, undefined)).toBe("");
|
||||
});
|
||||
|
||||
it("renders a plain text value", () => {
|
||||
expect(
|
||||
formatSelectorValue(options, "hello", { text: { type: "text" } })
|
||||
).toBe("hello");
|
||||
expect(formatSelectorValue(hass, "hello", { text: { type: "text" } })).toBe(
|
||||
"hello"
|
||||
);
|
||||
});
|
||||
|
||||
it("applies prefix and suffix for text selectors", () => {
|
||||
expect(
|
||||
formatSelectorValue(options, "5", {
|
||||
formatSelectorValue(hass, "5", {
|
||||
text: { type: "text", prefix: "$", suffix: " each" },
|
||||
})
|
||||
).toBe("$5 each");
|
||||
});
|
||||
|
||||
it("masks a password text value instead of revealing it", () => {
|
||||
const result = formatSelectorValue(options, "hunter2", {
|
||||
const result = formatSelectorValue(hass, "hunter2", {
|
||||
text: { type: "password" },
|
||||
});
|
||||
expect(result).not.toContain("hunter2");
|
||||
@@ -36,7 +35,7 @@ describe("formatSelectorValue", () => {
|
||||
});
|
||||
|
||||
it("masks every value of a multiple password selector", () => {
|
||||
const result = formatSelectorValue(options, ["one", "two"], {
|
||||
const result = formatSelectorValue(hass, ["one", "two"], {
|
||||
text: { type: "password" },
|
||||
});
|
||||
expect(result).not.toContain("one");
|
||||
@@ -46,7 +45,7 @@ describe("formatSelectorValue", () => {
|
||||
|
||||
it("masks a nested password field in an object selector preview", () => {
|
||||
const result = formatSelectorValue(
|
||||
options,
|
||||
hass,
|
||||
{ username: "admin", password: "hunter2" },
|
||||
{
|
||||
object: {
|
||||
@@ -64,7 +63,7 @@ describe("formatSelectorValue", () => {
|
||||
});
|
||||
|
||||
describe("formatSelectorValue duration selector", () => {
|
||||
const localizedOptions = {
|
||||
const localizedHass = {
|
||||
locale: { language: "en" },
|
||||
localize: (key: string, values?: Record<string, unknown>) => {
|
||||
const suffix = key.split("ui.components.selectors.duration.summary.")[1];
|
||||
@@ -75,12 +74,12 @@ describe("formatSelectorValue duration selector", () => {
|
||||
signed_positive: `+${values?.duration}`,
|
||||
}[suffix]!;
|
||||
},
|
||||
} as unknown as FormatSelectorValueOptions;
|
||||
} as unknown as HomeAssistant;
|
||||
|
||||
it("formats a positive duration without a sign", () => {
|
||||
expect(
|
||||
formatSelectorValue(
|
||||
localizedOptions,
|
||||
localizedHass,
|
||||
{ hours: 1, minutes: 30 },
|
||||
{ duration: {} }
|
||||
)
|
||||
@@ -90,13 +89,13 @@ describe("formatSelectorValue duration selector", () => {
|
||||
it("formats signed durations with an explicit sign", () => {
|
||||
expect(
|
||||
formatSelectorValue(
|
||||
localizedOptions,
|
||||
localizedHass,
|
||||
{ minutes: -30 },
|
||||
{ duration: { mode: "signed" } }
|
||||
)
|
||||
).toBe("-30 minutes");
|
||||
expect(
|
||||
formatSelectorValue(localizedOptions, "00:30:00", {
|
||||
formatSelectorValue(localizedHass, "00:30:00", {
|
||||
duration: { allow_negative: true },
|
||||
})
|
||||
).toBe("+30 minutes");
|
||||
@@ -105,24 +104,22 @@ describe("formatSelectorValue duration selector", () => {
|
||||
it("formats offsets as before or after", () => {
|
||||
expect(
|
||||
formatSelectorValue(
|
||||
localizedOptions,
|
||||
localizedHass,
|
||||
{ hours: -1, minutes: -30 },
|
||||
{ duration: { mode: "offset" } }
|
||||
)
|
||||
).toBe("1 hour, 30 minutes before");
|
||||
expect(
|
||||
formatSelectorValue(localizedOptions, "-00:10:00", {
|
||||
formatSelectorValue(localizedHass, "-00:10:00", {
|
||||
duration: { mode: "offset" },
|
||||
})
|
||||
).toBe("10 minutes before");
|
||||
expect(
|
||||
formatSelectorValue(localizedOptions, 45, {
|
||||
duration: { mode: "offset" },
|
||||
})
|
||||
formatSelectorValue(localizedHass, 45, { duration: { mode: "offset" } })
|
||||
).toBe("45 seconds after");
|
||||
expect(
|
||||
formatSelectorValue(
|
||||
localizedOptions,
|
||||
localizedHass,
|
||||
{ minutes: -5 },
|
||||
{ duration: { mode: "offset" } }
|
||||
)
|
||||
@@ -132,7 +129,7 @@ describe("formatSelectorValue duration selector", () => {
|
||||
it("returns an empty string for a zero offset", () => {
|
||||
expect(
|
||||
formatSelectorValue(
|
||||
localizedOptions,
|
||||
localizedHass,
|
||||
{ hours: 0, minutes: 0, seconds: 0 },
|
||||
{ duration: { mode: "offset" } }
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user