diff --git a/Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardNavigationDelegate.swift b/Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardNavigationDelegate.swift index 0bf4e24b8..f2f8c7e3a 100644 --- a/Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardNavigationDelegate.swift +++ b/Sources/CodexBarCore/OpenAIWeb/OpenAIDashboardNavigationDelegate.swift @@ -19,13 +19,20 @@ final class NavigationDelegate: NSObject, WKNavigationDelegate { } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { + if Self.shouldIgnoreNavigationError(error) { return } self.completeOnce(.failure(error)) } func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { + if Self.shouldIgnoreNavigationError(error) { return } self.completeOnce(.failure(error)) } + nonisolated static func shouldIgnoreNavigationError(_ error: Error) -> Bool { + let nsError = error as NSError + return nsError.domain == NSURLErrorDomain && nsError.code == NSURLErrorCancelled + } + private func completeOnce(_ result: Result) { guard !self.hasCompleted else { return } self.hasCompleted = true diff --git a/Tests/CodexBarTests/OpenAIDashboardNavigationDelegateTests.swift b/Tests/CodexBarTests/OpenAIDashboardNavigationDelegateTests.swift new file mode 100644 index 000000000..f9cb184eb --- /dev/null +++ b/Tests/CodexBarTests/OpenAIDashboardNavigationDelegateTests.swift @@ -0,0 +1,63 @@ +import Foundation +import Testing +import WebKit +@testable import CodexBarCore + +@Suite +struct OpenAIDashboardNavigationDelegateTests { + @Test("ignores NSURLErrorCancelled") + func ignoresCancelledNavigationError() { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled) + #expect(NavigationDelegate.shouldIgnoreNavigationError(error)) + } + + @Test("does not ignore non-cancelled URL errors") + func doesNotIgnoreOtherURLErrors() { + let error = NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut) + #expect(!NavigationDelegate.shouldIgnoreNavigationError(error)) + } + + @MainActor + @Test("cancelled failure is ignored until finish") + func cancelledFailureIsIgnoredUntilFinish() { + let webView = WKWebView() + var result: Result? + let delegate = NavigationDelegate { result = $0 } + + delegate.webView(webView, didFail: nil, withError: NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled)) + #expect(result == nil) + delegate.webView(webView, didFinish: nil) + + switch result { + case .success?: + #expect(Bool(true)) + default: + #expect(Bool(false)) + } + } + + @MainActor + @Test("cancelled provisional failure is ignored until real failure") + func cancelledProvisionalFailureIsIgnoredUntilRealFailure() { + let webView = WKWebView() + var result: Result? + let delegate = NavigationDelegate { result = $0 } + + delegate.webView( + webView, + didFailProvisionalNavigation: nil, + withError: NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled)) + #expect(result == nil) + + let timeout = NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut) + delegate.webView(webView, didFailProvisionalNavigation: nil, withError: timeout) + + switch result { + case let .failure(error as NSError)?: + #expect(error.domain == NSURLErrorDomain) + #expect(error.code == NSURLErrorTimedOut) + default: + #expect(Bool(false)) + } + } +}