custom-card-helpers 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/dependabot.yml +13 -0
- package/.github/workflows/claude-code-review.yml +44 -0
- package/.github/workflows/claude.yml +50 -0
- package/.github/workflows/release.yml +4 -0
- package/.github/workflows/test.yml +1 -0
- package/dist/compute-name.d.ts +7 -0
- package/dist/compute-state-display.d.ts +1 -2
- package/dist/datetime/duration_to_seconds.d.ts +1 -1
- package/dist/datetime/seconds_to_duration.d.ts +1 -1
- package/dist/datetime/select_unit.d.ts +8 -0
- package/dist/get-lovelace.d.ts +5 -1
- package/dist/index.js +259 -23
- package/dist/index.js.map +1 -1
- package/dist/index.m.js +254 -23
- package/dist/index.m.js.map +1 -1
- package/dist/index.umd.js +263 -25
- package/dist/index.umd.js.map +1 -1
- package/dist/scroll-to-target.d.ts +1 -1
- package/dist/translations/localize.d.ts +1 -2
- package/dist/turn-on-off-entity.d.ts +2 -0
- package/dist/types.d.ts +156 -4
- package/package.json +7 -8
- package/src/compute-name.ts +7 -0
- package/src/compute-state-display.ts +2 -3
- package/src/cover_icon.ts +74 -6
- package/src/datetime/duration_to_seconds.ts +1 -1
- package/src/datetime/relative_time.ts +1 -2
- package/src/datetime/seconds_to_duration.ts +1 -1
- package/src/datetime/select_unit.ts +94 -0
- package/src/datetime/timer_time_remaining.ts +1 -1
- package/src/get-lovelace.ts +22 -16
- package/src/handle-action.ts +19 -2
- package/src/handle-click.ts +1 -1
- package/src/scroll-to-target.ts +1 -1
- package/src/state_icon.ts +1 -1
- package/src/translations/localize.ts +1 -3
- package/src/turn-on-off-entity.ts +10 -0
- package/src/types.ts +203 -3
- package/test/datetime.test.ts +2 -2
- package/test/icons.test.ts +51 -0
- package/test/service-actions.test.ts +19 -1
|
@@ -23,3 +23,13 @@ export const turnOnOffEntity = (
|
|
|
23
23
|
|
|
24
24
|
return hass.callService(serviceDomain, service, { entity_id: entityId });
|
|
25
25
|
};
|
|
26
|
+
|
|
27
|
+
export const turnOnEntity = (
|
|
28
|
+
hass: HomeAssistant,
|
|
29
|
+
entityId: string
|
|
30
|
+
): Promise<void> => turnOnOffEntity(hass, entityId, true);
|
|
31
|
+
|
|
32
|
+
export const turnOffEntity = (
|
|
33
|
+
hass: HomeAssistant,
|
|
34
|
+
entityId: string
|
|
35
|
+
): Promise<void> => turnOnOffEntity(hass, entityId, false);
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
HassEntities,
|
|
3
|
+
HassEntity,
|
|
3
4
|
HassConfig,
|
|
4
5
|
Auth,
|
|
5
6
|
Connection,
|
|
@@ -8,6 +9,37 @@ import {
|
|
|
8
9
|
HassServiceTarget,
|
|
9
10
|
} from "home-assistant-js-websocket";
|
|
10
11
|
import { HapticType } from "./haptic";
|
|
12
|
+
|
|
13
|
+
export interface DeviceRegistryEntry {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string | null;
|
|
16
|
+
name_by_user: string | null;
|
|
17
|
+
area_id: string | null;
|
|
18
|
+
/** Set when this device is a sub-device of another. @since Home Assistant 2026.4 */
|
|
19
|
+
parent_device_id?: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AreaRegistryEntry {
|
|
23
|
+
area_id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
floor_id: string | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface FloorRegistryEntry {
|
|
29
|
+
floor_id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
level: number | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** One part of a composed entity name, as accepted by `formatEntityName`. */
|
|
35
|
+
export type EntityNameItem =
|
|
36
|
+
| { type: "entity" | "device" | "area" | "floor" }
|
|
37
|
+
| { type: "text"; text: string };
|
|
38
|
+
|
|
39
|
+
export interface EntityNameOptions {
|
|
40
|
+
/** Joins the resolved parts. Defaults to a single space. */
|
|
41
|
+
separator?: string;
|
|
42
|
+
}
|
|
11
43
|
import { HASSDomEvent } from "./fire-event";
|
|
12
44
|
|
|
13
45
|
export interface ToggleMenuActionConfig extends BaseActionConfig {
|
|
@@ -21,7 +53,7 @@ export interface ToggleActionConfig extends BaseActionConfig {
|
|
|
21
53
|
export interface CallServiceActionConfig extends BaseActionConfig {
|
|
22
54
|
action: "call-service";
|
|
23
55
|
service: string;
|
|
24
|
-
|
|
56
|
+
data?: {
|
|
25
57
|
entity_id?: string | [string];
|
|
26
58
|
[key: string]: any;
|
|
27
59
|
};
|
|
@@ -54,6 +86,16 @@ export interface CustomActionConfig extends BaseActionConfig {
|
|
|
54
86
|
action: "fire-dom-event";
|
|
55
87
|
}
|
|
56
88
|
|
|
89
|
+
export interface TurnOnActionConfig extends BaseActionConfig {
|
|
90
|
+
action: "turn_on";
|
|
91
|
+
entity?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface TurnOffActionConfig extends BaseActionConfig {
|
|
95
|
+
action: "turn_off";
|
|
96
|
+
entity?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
57
99
|
/**
|
|
58
100
|
* `repeat` and `haptic` are specifically for use in custom cards like the Button-Card
|
|
59
101
|
*/
|
|
@@ -81,7 +123,18 @@ export type ActionConfig =
|
|
|
81
123
|
| MoreInfoActionConfig
|
|
82
124
|
| NoActionConfig
|
|
83
125
|
| CustomActionConfig
|
|
84
|
-
| ToggleMenuActionConfig
|
|
126
|
+
| ToggleMenuActionConfig
|
|
127
|
+
| TurnOnActionConfig
|
|
128
|
+
| TurnOffActionConfig;
|
|
129
|
+
|
|
130
|
+
export interface HuiRootElement extends HTMLElement {
|
|
131
|
+
lovelace: {
|
|
132
|
+
config: LovelaceConfig;
|
|
133
|
+
current_view: number;
|
|
134
|
+
[key: string]: any;
|
|
135
|
+
};
|
|
136
|
+
___curView: number;
|
|
137
|
+
}
|
|
85
138
|
|
|
86
139
|
export interface Window {
|
|
87
140
|
// Custom panel entry point url
|
|
@@ -187,6 +240,21 @@ export interface ServiceCallRequest {
|
|
|
187
240
|
target?: HassServiceTarget;
|
|
188
241
|
}
|
|
189
242
|
|
|
243
|
+
// Entry of the entity registry as exposed by the HA frontend in `hass.entities`.
|
|
244
|
+
export interface EntityRegistryDisplayEntry {
|
|
245
|
+
entity_id: string;
|
|
246
|
+
name?: string;
|
|
247
|
+
icon?: string;
|
|
248
|
+
device_id?: string;
|
|
249
|
+
area_id?: string;
|
|
250
|
+
labels: string[];
|
|
251
|
+
hidden?: boolean;
|
|
252
|
+
entity_category?: "config" | "diagnostic";
|
|
253
|
+
translation_key?: string;
|
|
254
|
+
platform?: string;
|
|
255
|
+
display_precision?: number;
|
|
256
|
+
}
|
|
257
|
+
|
|
190
258
|
export interface HomeAssistant {
|
|
191
259
|
auth: Auth;
|
|
192
260
|
connection: Connection;
|
|
@@ -218,6 +286,47 @@ export interface HomeAssistant {
|
|
|
218
286
|
};
|
|
219
287
|
};
|
|
220
288
|
|
|
289
|
+
// Registry data. Populated over the websocket connection after connect, and
|
|
290
|
+
// needed to resolve an entity's naming context. All optional: `floors` only
|
|
291
|
+
// exists from HA 2024.4, and marking the rest optional keeps this interface
|
|
292
|
+
// safe to construct in tests and mocks.
|
|
293
|
+
/** Entity registry keyed by entity_id. */
|
|
294
|
+
entities?: Record<string, EntityRegistryDisplayEntry>;
|
|
295
|
+
devices?: Record<string, DeviceRegistryEntry>;
|
|
296
|
+
areas?: Record<string, AreaRegistryEntry>;
|
|
297
|
+
floors?: Record<string, FloorRegistryEntry>;
|
|
298
|
+
|
|
299
|
+
// Display formatters, which apply the user's locale, precision and naming
|
|
300
|
+
// preferences the same way the built-in cards do. Optional because each was
|
|
301
|
+
// added in a specific Home Assistant version - check before calling, or gate
|
|
302
|
+
// on `hass.config.version`.
|
|
303
|
+
/** @since Home Assistant 2024.4 */
|
|
304
|
+
formatEntityState?: (stateObj: HassEntity, state?: string) => string;
|
|
305
|
+
/** @since Home Assistant 2024.4 */
|
|
306
|
+
formatEntityAttributeName?: (stateObj: HassEntity, attribute: string) => string;
|
|
307
|
+
/** @since Home Assistant 2024.4 */
|
|
308
|
+
formatEntityAttributeValue?: (
|
|
309
|
+
stateObj: HassEntity,
|
|
310
|
+
attribute: string,
|
|
311
|
+
value?: unknown
|
|
312
|
+
) => string;
|
|
313
|
+
/**
|
|
314
|
+
* Formats an entity's display name from its registry context.
|
|
315
|
+
*
|
|
316
|
+
* Accepts a plain string (returned as-is), a single name item, an array of
|
|
317
|
+
* items joined with the separator, or `undefined` for the default name.
|
|
318
|
+
*
|
|
319
|
+
* @since Home Assistant 2026.4 in this shape. The helper exists from 2025.10,
|
|
320
|
+
* but took bare type strings then, and only accepted `EntityNameItem`s
|
|
321
|
+
* between 2025.11 and 2026.3 - so feature detection is not sufficient, check
|
|
322
|
+
* `hass.config.version` before calling with a card's `name` option.
|
|
323
|
+
*/
|
|
324
|
+
formatEntityName?: (
|
|
325
|
+
stateObj: HassEntity,
|
|
326
|
+
name?: string | EntityNameItem | EntityNameItem[],
|
|
327
|
+
options?: EntityNameOptions
|
|
328
|
+
) => string;
|
|
329
|
+
|
|
221
330
|
dockedSidebar: boolean;
|
|
222
331
|
moreInfoEntityId: string;
|
|
223
332
|
user: CurrentUser;
|
|
@@ -238,6 +347,7 @@ export interface HomeAssistant {
|
|
|
238
347
|
) => Promise<Response>;
|
|
239
348
|
sendWS: (msg: MessageBase) => Promise<void>;
|
|
240
349
|
callWS: <T>(msg: MessageBase) => Promise<T>;
|
|
350
|
+
hassUrl: (path?: string) => string;
|
|
241
351
|
}
|
|
242
352
|
|
|
243
353
|
export enum NumberFormat {
|
|
@@ -256,10 +366,37 @@ export enum TimeFormat {
|
|
|
256
366
|
twenty_four = "24",
|
|
257
367
|
}
|
|
258
368
|
|
|
369
|
+
export enum TimeZone {
|
|
370
|
+
local = "local",
|
|
371
|
+
server = "server",
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export enum DateFormat {
|
|
375
|
+
language = "language",
|
|
376
|
+
system = "system",
|
|
377
|
+
DMY = "DMY",
|
|
378
|
+
MDY = "MDY",
|
|
379
|
+
YMD = "YMD",
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export enum FirstWeekday {
|
|
383
|
+
language = "language",
|
|
384
|
+
monday = "monday",
|
|
385
|
+
tuesday = "tuesday",
|
|
386
|
+
wednesday = "wednesday",
|
|
387
|
+
thursday = "thursday",
|
|
388
|
+
friday = "friday",
|
|
389
|
+
saturday = "saturday",
|
|
390
|
+
sunday = "sunday",
|
|
391
|
+
}
|
|
392
|
+
|
|
259
393
|
export interface FrontendLocaleData {
|
|
260
394
|
language: string;
|
|
261
395
|
number_format: NumberFormat;
|
|
262
396
|
time_format: TimeFormat;
|
|
397
|
+
date_format: DateFormat;
|
|
398
|
+
first_weekday: FirstWeekday;
|
|
399
|
+
time_zone: TimeZone;
|
|
263
400
|
}
|
|
264
401
|
|
|
265
402
|
export interface LovelaceCardConfig {
|
|
@@ -283,6 +420,69 @@ export interface LovelaceCardEditor extends HTMLElement {
|
|
|
283
420
|
setConfig(config: LovelaceCardConfig): void;
|
|
284
421
|
}
|
|
285
422
|
|
|
423
|
+
export interface LovelaceCardFeatureConfig {
|
|
424
|
+
type: string;
|
|
425
|
+
[key: string]: any;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export type LovelaceCardFeaturePosition = "bottom" | "inline";
|
|
429
|
+
|
|
430
|
+
export interface LovelaceCardFeatureContext {
|
|
431
|
+
entity_id?: string;
|
|
432
|
+
area_id?: string;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export interface LovelaceCardFeature extends HTMLElement {
|
|
436
|
+
hass?: HomeAssistant;
|
|
437
|
+
/** @deprecated Use `context` instead */
|
|
438
|
+
stateObj?: HassEntity;
|
|
439
|
+
context?: LovelaceCardFeatureContext;
|
|
440
|
+
setConfig(config: LovelaceCardFeatureConfig): void;
|
|
441
|
+
color?: string;
|
|
442
|
+
position?: LovelaceCardFeaturePosition;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export interface LovelaceCardFeatureEditor extends HTMLElement {
|
|
446
|
+
hass?: HomeAssistant;
|
|
447
|
+
lovelace?: LovelaceConfig;
|
|
448
|
+
context?: LovelaceCardFeatureContext;
|
|
449
|
+
setConfig(config: LovelaceCardFeatureConfig): void;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Loose description of a ha-form schema item
|
|
453
|
+
export interface HaFormSchema {
|
|
454
|
+
name?: string;
|
|
455
|
+
type?: string;
|
|
456
|
+
[key: string]: any;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Static side of a card feature class, e.g. `MyFeature satisfies LovelaceCardFeatureConstructor`
|
|
460
|
+
export interface LovelaceCardFeatureConstructor {
|
|
461
|
+
new (...args: any[]): LovelaceCardFeature;
|
|
462
|
+
getStubConfig?: (
|
|
463
|
+
hass: HomeAssistant,
|
|
464
|
+
context?: LovelaceCardFeatureContext
|
|
465
|
+
) => LovelaceCardFeatureConfig;
|
|
466
|
+
getConfigElement?: () => LovelaceCardFeatureEditor;
|
|
467
|
+
getConfigForm?: () => {
|
|
468
|
+
schema: HaFormSchema[];
|
|
469
|
+
assertConfig?: (config: LovelaceCardConfig) => void;
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Entry for `window.customCardFeatures`, shown in the card feature picker
|
|
474
|
+
export interface CustomCardFeatureEntry {
|
|
475
|
+
type: string;
|
|
476
|
+
name?: string;
|
|
477
|
+
configurable?: boolean;
|
|
478
|
+
isSupported?: (
|
|
479
|
+
hass: HomeAssistant,
|
|
480
|
+
context: LovelaceCardFeatureContext
|
|
481
|
+
) => boolean;
|
|
482
|
+
/** @deprecated Use `isSupported` instead */
|
|
483
|
+
supported?: (stateObj: HassEntity) => boolean;
|
|
484
|
+
}
|
|
485
|
+
|
|
286
486
|
export interface LovelaceConfig {
|
|
287
487
|
title?: string;
|
|
288
488
|
views: LovelaceViewConfig[];
|
|
@@ -339,7 +539,7 @@ export interface EntitiesCardEntityConfig extends EntityConfig {
|
|
|
339
539
|
| "brightness";
|
|
340
540
|
action_name?: string;
|
|
341
541
|
service?: string;
|
|
342
|
-
|
|
542
|
+
data?: Record<string, unknown>;
|
|
343
543
|
url?: string;
|
|
344
544
|
tap_action?: ActionConfig;
|
|
345
545
|
hold_action?: ActionConfig;
|
package/test/datetime.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import durationToSeconds from "../src/datetime/duration_to_seconds";
|
|
3
|
-
import secondsToDuration from "../src/datetime/seconds_to_duration";
|
|
2
|
+
import { durationToSeconds } from "../src/datetime/duration_to_seconds";
|
|
3
|
+
import { secondsToDuration } from "../src/datetime/seconds_to_duration";
|
|
4
4
|
import { timerTimeRemaining } from "../src/datetime/timer_time_remaining";
|
|
5
5
|
import { useAmPm } from "../src/datetime/use_am_pm";
|
|
6
6
|
import { TimeFormat } from "../src/types";
|
package/test/icons.test.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { coverIcon } from "../src/cover_icon";
|
|
|
4
4
|
import { domainIcon } from "../src/domain_icons";
|
|
5
5
|
import { inputDateTimeIcon } from "../src/input_datetime_icon";
|
|
6
6
|
import { sensorIcon } from "../src/sensor_icon";
|
|
7
|
+
import { stateIcon } from "../src/state_icon";
|
|
7
8
|
|
|
8
9
|
describe("domainIcon", () => {
|
|
9
10
|
it("returns fixed icons and state-based icons", () => {
|
|
@@ -54,6 +55,36 @@ describe("coverIcon", () => {
|
|
|
54
55
|
coverIcon({ state: "closed", attributes: { device_class: "garage" } } as any)
|
|
55
56
|
).toBe("mdi:garage");
|
|
56
57
|
});
|
|
58
|
+
|
|
59
|
+
const icon = (state: string, device_class?: string) =>
|
|
60
|
+
coverIcon({ state, attributes: { device_class } } as any);
|
|
61
|
+
|
|
62
|
+
it("returns transitional icons while opening and closing", () => {
|
|
63
|
+
expect(icon("opening", "garage")).toBe("mdi:arrow-up-box");
|
|
64
|
+
expect(icon("closing", "garage")).toBe("mdi:arrow-down-box");
|
|
65
|
+
expect(icon("opening", "gate")).toBe("mdi:gate-arrow-right");
|
|
66
|
+
expect(icon("opening", "curtain")).toBe("mdi:arrow-split-vertical");
|
|
67
|
+
expect(icon("closing", "curtain")).toBe("mdi:arrow-collapse-horizontal");
|
|
68
|
+
expect(icon("opening", "shade")).toBe("mdi:arrow-up-box");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("returns closed and open icons for newly supported classes", () => {
|
|
72
|
+
expect(icon("closed", "gate")).toBe("mdi:gate");
|
|
73
|
+
expect(icon("open", "gate")).toBe("mdi:gate-open");
|
|
74
|
+
expect(icon("closed", "shutter")).toBe("mdi:window-shutter");
|
|
75
|
+
expect(icon("open", "curtain")).toBe("mdi:curtains");
|
|
76
|
+
expect(icon("closed", "curtain")).toBe("mdi:curtains-closed");
|
|
77
|
+
expect(icon("closed", "blind")).toBe("mdi:blinds");
|
|
78
|
+
expect(icon("open", "damper")).toBe("mdi:circle");
|
|
79
|
+
expect(icon("closed", "damper")).toBe("mdi:circle-slice-8");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("falls back to window icons for unknown device classes", () => {
|
|
83
|
+
expect(icon("closed")).toBe("mdi:window-closed");
|
|
84
|
+
expect(icon("open")).toBe("mdi:window-open");
|
|
85
|
+
expect(icon("opening")).toBe("mdi:arrow-up-box");
|
|
86
|
+
expect(icon("closing")).toBe("mdi:arrow-down-box");
|
|
87
|
+
});
|
|
57
88
|
});
|
|
58
89
|
|
|
59
90
|
describe("inputDateTimeIcon", () => {
|
|
@@ -69,3 +100,23 @@ describe("inputDateTimeIcon", () => {
|
|
|
69
100
|
);
|
|
70
101
|
});
|
|
71
102
|
});
|
|
103
|
+
|
|
104
|
+
describe("stateIcon", () => {
|
|
105
|
+
it("returns correct icon for binary_sensor with device_class", () => {
|
|
106
|
+
const stateObj = {
|
|
107
|
+
entity_id: "binary_sensor.door",
|
|
108
|
+
state: "off",
|
|
109
|
+
attributes: { device_class: "door" },
|
|
110
|
+
} as any;
|
|
111
|
+
expect(stateIcon(stateObj)).toBe("mdi:door-closed");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("returns correct icon for binary_sensor on state", () => {
|
|
115
|
+
const stateObj = {
|
|
116
|
+
entity_id: "binary_sensor.door",
|
|
117
|
+
state: "on",
|
|
118
|
+
attributes: { device_class: "door" },
|
|
119
|
+
} as any;
|
|
120
|
+
expect(stateIcon(stateObj)).toBe("mdi:door-open");
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { toggleEntity } from "../src/toggle-entity";
|
|
3
3
|
import { turnOnOffEntities } from "../src/turn-on-off-entities";
|
|
4
|
-
import { turnOnOffEntity } from "../src/turn-on-off-entity";
|
|
4
|
+
import { turnOnOffEntity, turnOnEntity, turnOffEntity } from "../src/turn-on-off-entity";
|
|
5
5
|
|
|
6
6
|
describe("turnOnOffEntity", () => {
|
|
7
7
|
it("uses domain-specific lock and cover services", async () => {
|
|
@@ -46,6 +46,24 @@ describe("toggleEntity", () => {
|
|
|
46
46
|
});
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
describe("turnOnEntity / turnOffEntity", () => {
|
|
50
|
+
it("turnOnEntity calls turn_on service", async () => {
|
|
51
|
+
const callService = vi.fn().mockResolvedValue(undefined);
|
|
52
|
+
await turnOnEntity({ callService } as any, "scene.movie_night");
|
|
53
|
+
expect(callService).toHaveBeenCalledWith("scene", "turn_on", {
|
|
54
|
+
entity_id: "scene.movie_night",
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("turnOffEntity calls turn_off service", async () => {
|
|
59
|
+
const callService = vi.fn().mockResolvedValue(undefined);
|
|
60
|
+
await turnOffEntity({ callService } as any, "switch.kitchen");
|
|
61
|
+
expect(callService).toHaveBeenCalledWith("switch", "turn_off", {
|
|
62
|
+
entity_id: "switch.kitchen",
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
49
67
|
describe("turnOnOffEntities", () => {
|
|
50
68
|
it("batches service calls by service domain", () => {
|
|
51
69
|
const callService = vi.fn();
|