Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Source/Turbo/WebView/ScriptMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ extension ScriptMessage {
enum Name: String {
case pageLoaded
case pageLoadFailed
case turboIsReady
case errorRaised
case visitProposed
case visitProposalScrollingToAnchor
Expand Down
5 changes: 5 additions & 0 deletions Source/Turbo/WebView/WebViewBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ extension WebViewBridge: ScriptMessageHandlerDelegate {
switch message.name {
case .pageLoaded:
pageLoadDelegate?.webView(self, didLoadPageWithRestorationIdentifier: message.restorationIdentifier!)
case .turboIsReady:
let isReady = message.data["isReady"] as? Bool ?? false
if !isReady {
delegate?.webView(self, didFailInitialPageLoadWithError: .load(.notReady))
}
case .pageLoadFailed:
delegate?.webView(self, didFailInitialPageLoadWithError: .load(.notPresent))
case .formSubmissionStarted:
Expand Down
7 changes: 7 additions & 0 deletions Source/Turbo/WebView/turbo.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
registerAdapter() {
if (window.Turbo) {
Turbo.registerAdapter(this)
this.turboIsReady(true)
} else if (window.Turbolinks) {
Turbolinks.controller.adapter = this
this.turboIsReady(true)
} else {
throw new Error("Failed to register the TurboNative adapter")
}
Expand All @@ -30,6 +32,10 @@
this.postMessageAfterNextRepaint("pageLoaded", { restorationIdentifier })
}

turboIsReady(isReady) {
this.postMessage("turboIsReady", { isReady: isReady })
}

pageLoadFailed() {
this.postMessage("pageLoadFailed")
}
Expand Down Expand Up @@ -231,6 +237,7 @@

setTimeout(() => {
if (!window.Turbo && !window.Turbolinks) {
window.turboNative.turboIsReady(false)
window.turboNative.pageLoadFailed()
}
}, TURBO_LOAD_TIMEOUT)
Expand Down
18 changes: 18 additions & 0 deletions Tests/Turbo/ScriptMessageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,24 @@ class ScriptMessageTests: XCTestCase {
let message = ScriptMessage(message: script)
XCTAssertNil(message)
}

func test_parse_turboIsReady_withFalse_returnsMessage() throws {
let data: [String: Any] = ["isReady": false, "timestamp": 0]
let script = FakeScriptMessage(body: ["name": "turboIsReady", "data": data] as [String: Any])

let message = try XCTUnwrap(ScriptMessage(message: script))
XCTAssertEqual(message.name, .turboIsReady)
XCTAssertEqual(message.data["isReady"] as? Bool, false)
}

func test_parse_turboIsReady_withTrue_returnsMessage() throws {
let data: [String: Any] = ["isReady": true, "timestamp": 0]
let script = FakeScriptMessage(body: ["name": "turboIsReady", "data": data] as [String: Any])

let message = try XCTUnwrap(ScriptMessage(message: script))
XCTAssertEqual(message.name, .turboIsReady)
XCTAssertEqual(message.data["isReady"] as? Bool, true)
}
}

// Can't instantiate a WKScriptMessage directly
Expand Down
18 changes: 11 additions & 7 deletions Tests/Turbo/SessionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,19 @@ class SessionTests: XCTestCase {
}

@MainActor
func test_coldBootVisit_whenVisitFailsFromMissingLibrary_providesAnPageLoadError() async throws {
await visit("/missing-library", timeout: turboTimeout + defaultTimeout)
func test_coldBootVisit_whenVisitFailsFromMissingLibrary_providesNotReadyAndNotPresentErrors() async throws {
let expectation = self.expectation(description: "Wait for both load errors.")
expectation.expectedFulfillmentCount = 2
sessionDelegate.didChange = { expectation.fulfill() }

XCTAssertTrue(sessionDelegate.sessionDidFailRequestCalled)
XCTAssertTrue(sessionDelegate.sessionDidFinishRequestCalled)
let visitable = TestVisitable(url: url("/missing-library"))
session.visit(visitable)
await fulfillment(of: [expectation], timeout: turboTimeout + defaultTimeout)

XCTAssertNotNil(sessionDelegate.failedRequestError)
let error = try XCTUnwrap(sessionDelegate.failedRequestError)
XCTAssertEqual(error, .load(.notPresent))
XCTAssertTrue(sessionDelegate.sessionDidFailRequestCalled)
XCTAssertEqual(sessionDelegate.allFailedRequestErrors.count, 2)
XCTAssertEqual(sessionDelegate.allFailedRequestErrors[0], .load(.notReady))
XCTAssertEqual(sessionDelegate.allFailedRequestErrors[1], .load(.notPresent))
}

// MARK: - Server
Expand Down
2 changes: 2 additions & 0 deletions Tests/Turbo/Test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class TestSessionDelegate: NSObject, SessionDelegate {
var sessionDidStartRequestCalled = false
var sessionDidFinishRequestCalled = false
var failedRequestError: HotwireNativeError? = nil
var allFailedRequestErrors: [HotwireNativeError] = []
var sessionDidFailRequestCalled = false { didSet { didChange?() }}
var sessionDidProposeVisitCalled = false
var sessionDidProposeVisitToCrossOriginRedirectWasCalled = false
Expand Down Expand Up @@ -83,6 +84,7 @@ class TestSessionDelegate: NSObject, SessionDelegate {
func session(_ session: Session, didFailRequestForVisitable visitable: Visitable, error: HotwireNativeError) {
sessionDidFailRequestCalled = true
failedRequestError = error
allFailedRequestErrors.append(error)
}

func session(_ session: Session, didProposeVisit proposal: VisitProposal) {
Expand Down