From 486a23c77118ac2c05da53ba7705ae68041319ac Mon Sep 17 00:00:00 2001 From: scgopi Date: Sun, 30 Aug 2026 09:13:38 -0700 Subject: [PATCH] Stop the test suite popping the 'no application to open the URL' dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every xcodebuild test run executed AuthPortForwardingTests, which drove GhosttyRuntime.handleAction with a real x-graphcode-test://sign-in open; handleAction passed it straight to NSWorkspace.shared.open, and since nothing claims that scheme LaunchServices popped its 'no application set to open the URL' dialog once per run — again and again as worktree loops verify their work. The opener is now an injectable closure (still NSWorkspace in the app) and the test installs a recorder, which also lets it assert the intercepted URL actually reaches the opener. --- .../Infrastructure/Ghostty/GhosttyRuntime.swift | 8 +++++++- graphcode/Tests/AuthPortForwardingTests.swift | 17 ++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/graphcode/Sources/Infrastructure/Ghostty/GhosttyRuntime.swift b/graphcode/Sources/Infrastructure/Ghostty/GhosttyRuntime.swift index 0aa8e7d3..dc2ea7a9 100644 --- a/graphcode/Sources/Infrastructure/Ghostty/GhosttyRuntime.swift +++ b/graphcode/Sources/Infrastructure/Ghostty/GhosttyRuntime.swift @@ -145,6 +145,12 @@ final class GhosttyRuntime: @unchecked Sendable { return Unmanaged.fromOpaque(userdata).takeUnretainedValue() } + /// Who actually opens an intercepted link. Tests swap in a recorder so a URL no + /// application claims (the test-only `x-graphcode-test://` scheme) never reaches + /// LaunchServices — with the real opener, every `xcodebuild test` run popped the + /// "There is no application set to open the URL" dialog. + static var openURL: (URL) -> Void = { NSWorkspace.shared.open($0) } + /// The one action graphcode handles: a ⌘-clicked link. Everything else returns /// false and libghostty's defaults apply — the same posture as before, just no /// longer for URLs, because who opens a link is where remote sign-ins are won. @@ -184,7 +190,7 @@ final class GhosttyRuntime: @unchecked Sendable { Task { await RemoteAuthPortForwarder.shared.ensureForwarding(port: port, to: location) } } } - DispatchQueue.main.async { NSWorkspace.shared.open(url) } + DispatchQueue.main.async { Self.openURL(url) } return true } diff --git a/graphcode/Tests/AuthPortForwardingTests.swift b/graphcode/Tests/AuthPortForwardingTests.swift index 14491bd8..2a8ff565 100644 --- a/graphcode/Tests/AuthPortForwardingTests.swift +++ b/graphcode/Tests/AuthPortForwardingTests.swift @@ -2,6 +2,7 @@ import Foundation import GhosttyKit import GraphcodeKit import Testing +import os @testable import graphcode @@ -91,7 +92,7 @@ struct AuthPortForwardingTests { } @Test - func onlySchemeCarryingUnknownURLsAreIntercepted() { + func onlySchemeCarryingUnknownURLsAreIntercepted() async { // ⌘⇧-click on a local folder's surface must not regress: a clicked *file path* // (Ghostty hands over a bare absolute path for resolved links) and the // editor-opening scrollback kinds return false, so libghostty's own opener runs @@ -99,9 +100,19 @@ struct AuthPortForwardingTests { #expect(!linkOpenHandled("/Users/dev/notes.txt")) #expect(!linkOpenHandled("https://example.com/dump", kind: GHOSTTY_ACTION_OPEN_URL_KIND_TEXT)) #expect(!linkOpenHandled("https://example.com/dump", kind: GHOSTTY_ACTION_OPEN_URL_KIND_HTML)) - // A real link is ours — the interception the forwarder rides on. A test-only - // scheme, so the open attempt resolves to no application. + // A real link is ours — the interception the forwarder rides on. The opener is a + // test recorder, so the URL lands in a list instead of LaunchServices: nothing + // claims this scheme, and the real opener popped the "no application set to open + // the URL" dialog on every test run. + let opened = OSAllocatedUnfairLock(initialState: [URL]()) + let systemOpener = GhosttyRuntime.openURL + GhosttyRuntime.openURL = { url in opened.withLock { $0.append(url) } } + defer { GhosttyRuntime.openURL = systemOpener } #expect(linkOpenHandled("x-graphcode-test://sign-in")) + // The open is dispatched to the main queue; a sentinel enqueued after it runs + // once the recorded open has happened. + await MainActor.run {} + #expect(opened.withLock { $0 } == [URL(string: "x-graphcode-test://sign-in")!]) } @Test