Add navigate command to the external bus (#25516)

pull/25529/head
Timothy 2025-05-19 15:22:19 +02:00 committed by GitHub
parent 2a4c6c9af5
commit 8b87188075
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 316 additions and 8 deletions

View File

@ -7,6 +7,7 @@ This is the entry point for providing external app stuff from app entrypoint.
import { fireEvent } from "../common/dom/fire_event";
import { mainWindow } from "../common/dom/get_main_window";
import { navigate } from "../common/navigate";
import { showAutomationEditor } from "../data/automation";
import type { HomeAssistantMain } from "../layouts/home-assistant-main";
import type {
@ -50,7 +51,7 @@ export const addExternalBarCodeListener = (
};
};
const handleExternalMessage = (
export const handleExternalMessage = (
hassMainEl: HomeAssistantMain,
msg: EMIncomingMessageCommands
): boolean => {
@ -64,6 +65,14 @@ const handleExternalMessage = (
success: true,
result: null,
});
} else if (msg.command === "navigate") {
navigate(msg.payload.path, msg.payload.options);
bus.fireMessage({
id: msg.id,
type: "result",
success: true,
result: null,
});
} else if (msg.command === "notifications/show") {
fireEvent(hassMainEl, "hass-show-notifications");
bus.fireMessage({

View File

@ -1,3 +1,4 @@
import type { NavigateOptions } from "../common/navigate";
import type { AutomationConfig } from "../data/automation";
const CALLBACK_EXTERNAL_BUS = "externalBus";
@ -178,31 +179,40 @@ type EMOutgoingMessageWithoutAnswer =
| EMOutgoingMessageImprovScan
| EMOutgoingMessageImprovConfigureDevice;
interface EMIncomingMessageRestart {
export interface EMIncomingMessageRestart {
id: number;
type: "command";
command: "restart";
}
export interface EMIncomingMessageNavigate {
id: number;
type: "command";
command: "navigate";
payload: {
path: string;
options?: NavigateOptions;
};
}
interface EMIncomingMessageShowNotifications {
export interface EMIncomingMessageShowNotifications {
id: number;
type: "command";
command: "notifications/show";
}
interface EMIncomingMessageToggleSidebar {
export interface EMIncomingMessageToggleSidebar {
id: number;
type: "command";
command: "sidebar/toggle";
}
interface EMIncomingMessageShowSidebar {
export interface EMIncomingMessageShowSidebar {
id: number;
type: "command";
command: "sidebar/show";
}
interface EMIncomingMessageShowAutomationEditor {
export interface EMIncomingMessageShowAutomationEditor {
id: number;
type: "command";
command: "automation/editor/show";
@ -250,14 +260,14 @@ export interface ImprovDiscoveredDevice {
name: string;
}
interface EMIncomingMessageImprovDeviceDiscovered extends EMMessage {
export interface EMIncomingMessageImprovDeviceDiscovered extends EMMessage {
id: number;
type: "command";
command: "improv/discovered_device";
payload: ImprovDiscoveredDevice;
}
interface EMIncomingMessageImprovDeviceSetupDone extends EMMessage {
export interface EMIncomingMessageImprovDeviceSetupDone extends EMMessage {
id: number;
type: "command";
command: "improv/device_setup_done";
@ -265,6 +275,7 @@ interface EMIncomingMessageImprovDeviceSetupDone extends EMMessage {
export type EMIncomingMessageCommands =
| EMIncomingMessageRestart
| EMIncomingMessageNavigate
| EMIncomingMessageShowNotifications
| EMIncomingMessageToggleSidebar
| EMIncomingMessageShowSidebar

View File

@ -0,0 +1,288 @@
import { describe, expect, it, beforeEach, vi } from "vitest";
import { fireEvent } from "../../src/common/dom/fire_event";
import { mainWindow } from "../../src/common/dom/get_main_window";
import { navigate } from "../../src/common/navigate";
import {
handleExternalMessage,
addExternalBarCodeListener,
} from "../../src/external_app/external_app_entrypoint";
import { showAutomationEditor } from "../../src/data/automation";
import type {
EMIncomingMessageRestart,
EMIncomingMessageNavigate,
EMIncomingMessageShowNotifications,
EMIncomingMessageToggleSidebar,
EMIncomingMessageShowSidebar,
EMIncomingMessageShowAutomationEditor,
EMIncomingMessageImprovDeviceDiscovered,
EMIncomingMessageImprovDeviceSetupDone,
EMIncomingMessageBarCodeScanResult,
EMIncomingMessageBarCodeScanAborted,
} from "../../src/external_app/external_messaging";
vi.mock("../../src/common/dom/fire_event", () => ({
fireEvent: vi.fn(),
}));
vi.mock("../../src/common/navigate", () => ({
navigate: vi.fn(),
}));
vi.mock("../../src/data/automation", () => ({
showAutomationEditor: vi.fn(),
}));
describe("handleExternalMessage", () => {
let hassMainEl: any;
let fireMessage: any;
let reconnect: any;
beforeEach(() => {
fireMessage = vi.fn();
reconnect = vi.fn();
hassMainEl = {
hass: {
auth: {
external: {
fireMessage,
},
},
connection: {
reconnect,
},
},
};
vi.clearAllMocks();
});
it("handles restart command", () => {
const msg: EMIncomingMessageRestart = {
type: "command",
command: "restart",
id: 1,
};
const result = handleExternalMessage(hassMainEl, msg);
expect(reconnect).toHaveBeenCalledWith(true);
expect(fireMessage).toHaveBeenCalledWith({
id: 1,
type: "result",
success: true,
result: null,
});
expect(result).toBe(true);
});
it("handles navigate command", () => {
const msg: EMIncomingMessageNavigate = {
type: "command",
command: "navigate",
id: 2,
payload: { path: "/test", options: { replace: true } },
};
const result = handleExternalMessage(hassMainEl, msg);
expect(navigate).toHaveBeenCalledWith("/test", { replace: true });
expect(fireMessage).toHaveBeenCalledWith({
id: 2,
type: "result",
success: true,
result: null,
});
expect(result).toBe(true);
});
it("handles notifications/show command", () => {
const msg: EMIncomingMessageShowNotifications = {
type: "command",
command: "notifications/show",
id: 3,
};
const result = handleExternalMessage(hassMainEl, msg);
expect(fireEvent).toHaveBeenCalledWith(
hassMainEl,
"hass-show-notifications"
);
expect(fireMessage).toHaveBeenCalledWith({
id: 3,
type: "result",
success: true,
result: null,
});
expect(result).toBe(true);
});
it("handles sidebar/toggle command when dialog is open", () => {
vi.spyOn(mainWindow.history, "state", "get").mockReturnValue({
open: true,
});
const msg: EMIncomingMessageToggleSidebar = {
type: "command",
command: "sidebar/toggle",
id: 4,
};
const result = handleExternalMessage(hassMainEl, msg);
expect(fireEvent).not.toHaveBeenCalled();
expect(fireMessage).toHaveBeenCalledWith({
id: 4,
type: "result",
success: false,
error: { code: "not_allowed", message: "dialog open" },
});
expect(result).toBe(true);
});
it("handles sidebar/toggle command when dialog is not open", () => {
vi.spyOn(mainWindow.history, "state", "get").mockReturnValue({
open: false,
});
const msg: EMIncomingMessageToggleSidebar = {
type: "command",
command: "sidebar/toggle",
id: 5,
};
const result = handleExternalMessage(hassMainEl, msg);
expect(fireEvent).toHaveBeenCalledWith(hassMainEl, "hass-toggle-menu");
expect(fireMessage).toHaveBeenCalledWith({
id: 5,
type: "result",
success: true,
result: null,
});
expect(result).toBe(true);
});
it("handles sidebar/show command when dialog is open", () => {
vi.spyOn(mainWindow.history, "state", "get").mockReturnValue({
open: true,
});
const msg: EMIncomingMessageShowSidebar = {
type: "command",
command: "sidebar/show",
id: 6,
};
const result = handleExternalMessage(hassMainEl, msg);
expect(fireEvent).not.toHaveBeenCalled();
expect(fireMessage).toHaveBeenCalledWith({
id: 6,
type: "result",
success: false,
error: { code: "not_allowed", message: "dialog open" },
});
expect(result).toBe(true);
});
it("handles sidebar/show command when dialog is not open", () => {
vi.spyOn(mainWindow.history, "state", "get").mockReturnValue({
open: false,
});
const msg: EMIncomingMessageShowSidebar = {
type: "command",
command: "sidebar/show",
id: 7,
};
const result = handleExternalMessage(hassMainEl, msg);
expect(fireEvent).toHaveBeenCalledWith(hassMainEl, "hass-toggle-menu", {
open: true,
});
expect(fireMessage).toHaveBeenCalledWith({
id: 7,
type: "result",
success: true,
result: null,
});
expect(result).toBe(true);
});
it("handles automation/editor/show command", () => {
const msg: EMIncomingMessageShowAutomationEditor = {
type: "command",
command: "automation/editor/show",
id: 8,
payload: { config: { id: "42" } },
};
const result = handleExternalMessage(hassMainEl, msg);
expect(showAutomationEditor).toHaveBeenCalledWith({ id: "42" });
expect(fireMessage).toHaveBeenCalledWith({
id: 8,
type: "result",
success: true,
result: null,
});
expect(result).toBe(true);
});
it("handles improv/discovered_device command", () => {
const msg: EMIncomingMessageImprovDeviceDiscovered = {
type: "command",
command: "improv/discovered_device",
id: 9,
payload: { name: "helloworld" },
};
const result = handleExternalMessage(hassMainEl, msg);
expect(fireEvent).toHaveBeenCalledWith(window, "improv-discovered-device", {
name: "helloworld",
});
expect(fireMessage).toHaveBeenCalledWith({
id: 9,
type: "result",
success: true,
result: null,
});
expect(result).toBe(true);
});
it("handles improv/device_setup_done command", () => {
const msg: EMIncomingMessageImprovDeviceSetupDone = {
type: "command",
command: "improv/device_setup_done",
id: 10,
};
const result = handleExternalMessage(hassMainEl, msg);
expect(fireEvent).toHaveBeenCalledWith(window, "improv-device-setup-done");
expect(fireMessage).toHaveBeenCalledWith({
id: 10,
type: "result",
success: true,
result: null,
});
expect(result).toBe(true);
});
it("handles bar_code/scan_result command and notifies listeners", () => {
const listener = vi.fn();
addExternalBarCodeListener(listener);
const msg: EMIncomingMessageBarCodeScanResult = {
type: "command",
command: "bar_code/scan_result",
id: 11,
payload: { rawValue: "123456789", format: "aztec" },
};
const result = handleExternalMessage(hassMainEl, msg);
expect(listener).toHaveBeenCalledWith(msg);
expect(fireMessage).toHaveBeenCalledWith({
id: 11,
type: "result",
success: true,
result: null,
});
expect(result).toBe(true);
});
it("handles bar_code/aborted command and notifies listeners", () => {
const listener = vi.fn();
addExternalBarCodeListener(listener);
const msg: EMIncomingMessageBarCodeScanAborted = {
type: "command",
command: "bar_code/aborted",
id: 12,
payload: { reason: "canceled" },
};
const result = handleExternalMessage(hassMainEl, msg);
expect(listener).toHaveBeenCalledWith(msg);
expect(fireMessage).toHaveBeenCalledWith({
id: 12,
type: "result",
success: true,
result: null,
});
expect(result).toBe(true);
});
});