From a15372a224c93fe2e771c4b11cb02bb216a77c43 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Wed, 19 Aug 2026 15:09:17 -0500 Subject: [PATCH] fix: stop connection screens crashing outside the connection log provider --- .../connection/ConnectionLogPanel.tsx | 13 ++++-- .../connection-log/ConnectionLogContext.tsx | 6 +++ .../connection/ConnectionScreen.test.tsx | 46 +++++++++++++++++++ 3 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 src/ui/tests/components/connection/ConnectionScreen.test.tsx diff --git a/src/ui/components/connection/ConnectionLogPanel.tsx b/src/ui/components/connection/ConnectionLogPanel.tsx index 92b0ad81..0075a8fa 100644 --- a/src/ui/components/connection/ConnectionLogPanel.tsx +++ b/src/ui/components/connection/ConnectionLogPanel.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from "react"; -import { useConnectionLog } from "@/ssh/connection-log/ConnectionLogContext.tsx"; +import { useOptionalConnectionLog } from "@/ssh/connection-log/ConnectionLogContext.tsx"; import { useTranslation } from "react-i18next"; import { copyToClipboard } from "@/lib/clipboard.ts"; import { Button } from "@/components/button.tsx"; @@ -34,20 +34,21 @@ export function ConnectionLogPanel({ className, }: ConnectionLogPanelProps) { const { t } = useTranslation(); + const connectionLog = useOptionalConnectionLog(); const { logs, clearLogs, isExpanded, toggleExpanded, setIsExpanded } = - useConnectionLog(); + connectionLog ?? {}; const lastLogRef = useRef(null); const [manuallyCollapsed, setManuallyCollapsed] = useState(false); useEffect(() => { - if (hasConnectionError) { + if (hasConnectionError && setIsExpanded) { setManuallyCollapsed(false); setIsExpanded(true); } }, [hasConnectionError, setIsExpanded]); useEffect(() => { - if (isConnected && !hasConnectionError && !isConnecting) { + if (isConnected && !hasConnectionError && !isConnecting && clearLogs) { clearLogs(); setManuallyCollapsed(false); } @@ -60,7 +61,9 @@ export function ConnectionLogPanel({ }, [logs]); const shouldShow = - !isConnected && (isConnecting || hasConnectionError || logs.length > 0); + !!connectionLog && + !isConnected && + (isConnecting || hasConnectionError || logs.length > 0); if (!shouldShow) { return null; diff --git a/src/ui/ssh/connection-log/ConnectionLogContext.tsx b/src/ui/ssh/connection-log/ConnectionLogContext.tsx index cf7a7e41..53da355f 100644 --- a/src/ui/ssh/connection-log/ConnectionLogContext.tsx +++ b/src/ui/ssh/connection-log/ConnectionLogContext.tsx @@ -88,3 +88,9 @@ export function useConnectionLog() { } return context; } + +// Loading and error screens render outside any provider, so they get undefined +// instead of a crash. +export function useOptionalConnectionLog() { + return useContext(ConnectionLogContext); +} diff --git a/src/ui/tests/components/connection/ConnectionScreen.test.tsx b/src/ui/tests/components/connection/ConnectionScreen.test.tsx new file mode 100644 index 00000000..feb59e01 --- /dev/null +++ b/src/ui/tests/components/connection/ConnectionScreen.test.tsx @@ -0,0 +1,46 @@ +import { describe, it, expect, vi, afterEach } from "vitest"; +import { render, screen, cleanup } from "@testing-library/react"; + +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ t: (key: string) => key }), +})); + +import { ConnectionScreen } from "../../../components/connection/ConnectionScreen"; +import { ConnectionLogProvider } from "../../../ssh/connection-log/ConnectionLogContext"; + +afterEach(cleanup); + +describe("ConnectionScreen", () => { + // Loading and host-not-found screens render before the provider is mounted, + // which used to throw and take down the whole RDP/VNC/Telnet tab. + it("renders without a ConnectionLogProvider", () => { + expect(() => + render(), + ).not.toThrow(); + + expect(screen.getByText("common.loading")).toBeTruthy(); + }); + + it("renders the disconnected state without a provider", () => { + expect(() => + render( + , + ), + ).not.toThrow(); + + expect(screen.getByText("guacamole.hostNotFound")).toBeTruthy(); + }); + + it("still shows the connection log when a provider is present", () => { + render( + + + , + ); + + expect(screen.getByText(/terminal\.connectionLogTitle/)).toBeTruthy(); + }); +});