From 725fd5126b9a9589d37ff3ecae9d4f122de10e8e Mon Sep 17 00:00:00 2001 From: Jay Ohms Date: Tue, 31 Oct 2023 06:59:37 -0400 Subject: [PATCH] Always go through the adapter to decide how to propose visits (#1054) * Always go through the adapter to decide whether to start a visit or update the window.location for external urls. This allows the turbo-ios and turbo-android adapters to see the proposed visits and handle them appropriately. * Add unit tests for the native adapter interface, so we can ensure that the proper methods are called for the native adapters * Remove unused vars --- src/core/drive/navigator.js | 12 +- src/core/native/browser_adapter.js | 7 +- .../unit/native_adapter_support_tests.js | 197 ++++++++++++++++++ 3 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 src/tests/unit/native_adapter_support_tests.js diff --git a/src/core/drive/navigator.js b/src/core/drive/navigator.js index be81794fd..00a743b8b 100644 --- a/src/core/drive/navigator.js +++ b/src/core/drive/navigator.js @@ -1,6 +1,6 @@ import { getVisitAction } from "../../util" import { FormSubmission } from "./form_submission" -import { expandURL, getAnchor, getRequestURL, locationIsVisitable } from "../url" +import { expandURL, getAnchor, getRequestURL } from "../url" import { Visit } from "./visit" import { PageSnapshot } from "./page_snapshot" @@ -11,11 +11,7 @@ export class Navigator { proposeVisit(location, options = {}) { if (this.delegate.allowsVisitingLocationWithAction(location, options.action)) { - if (locationIsVisitable(location, this.view.snapshot.rootLocation)) { - this.delegate.visitProposedToLocation(location, options) - } else { - window.location.href = location.toString() - } + this.delegate.visitProposedToLocation(location, options) } } @@ -55,6 +51,10 @@ export class Navigator { return this.delegate.view } + get rootLocation() { + return this.view.snapshot.rootLocation + } + get history() { return this.delegate.history } diff --git a/src/core/native/browser_adapter.js b/src/core/native/browser_adapter.js index c3f1086de..2351d10e4 100644 --- a/src/core/native/browser_adapter.js +++ b/src/core/native/browser_adapter.js @@ -1,6 +1,7 @@ import { ProgressBar } from "../drive/progress_bar" import { SystemStatusCode } from "../drive/visit" import { uuid, dispatch } from "../../util" +import { locationIsVisitable } from "../url" export class BrowserAdapter { progressBar = new ProgressBar() @@ -10,7 +11,11 @@ export class BrowserAdapter { } visitProposedToLocation(location, options) { - this.navigator.startVisit(location, options?.restorationIdentifier || uuid(), options) + if (locationIsVisitable(location, this.navigator.rootLocation)) { + this.navigator.startVisit(location, options?.restorationIdentifier || uuid(), options) + } else { + window.location.href = location.toString() + } } visitStarted(visit) { diff --git a/src/tests/unit/native_adapter_support_tests.js b/src/tests/unit/native_adapter_support_tests.js new file mode 100644 index 000000000..c199c1f3a --- /dev/null +++ b/src/tests/unit/native_adapter_support_tests.js @@ -0,0 +1,197 @@ +import * as Turbo from "../../index" +import { assert } from "@open-wc/testing" + +class NativeAdapterSupportTest { + proposedVisits = [] + startedVisits = [] + completedVisits = [] + startedVisitRequests = [] + completedVisitRequests = [] + failedVisitRequests = [] + finishedVisitRequests = [] + startedFormSubmissions = [] + finishedFormSubmissions = [] + + // Adapter interface + + visitProposedToLocation(location, options) { + this.proposedVisits.push({ location, options }) + } + + visitStarted(visit) { + this.startedVisits.push(visit) + } + + visitCompleted(visit) { + this.completedVisits.push(visit) + } + + visitRequestStarted(visit) { + this.startedVisitRequests.push(visit) + } + + visitRequestCompleted(visit) { + this.completedVisitRequests.push(visit) + } + + visitRequestFailedWithStatusCode(visit, _statusCode) { + this.failedVisitRequests.push(visit) + } + + visitRequestFinished(visit) { + this.finishedVisitRequests.push(visit) + } + + visitRendered(_visit) {} + + formSubmissionStarted(formSubmission) { + this.startedFormSubmissions.push(formSubmission) + } + + formSubmissionFinished(formSubmission) { + this.finishedFormSubmissions.push(formSubmission) + } + + pageInvalidated() {} +} + +let adapter + +setup(() => { + adapter = new NativeAdapterSupportTest() + Turbo.registerAdapter(adapter) +}) + +test("test navigator adapter is native adapter", async () => { + assert.equal(adapter, Turbo.navigator.adapter) +}) + +test("test visit proposal location is proposed to adapter", async () => { + const url = new URL(window.location.toString()) + + Turbo.navigator.proposeVisit(url) + assert.equal(adapter.proposedVisits.length, 1) + + const [visit] = adapter.proposedVisits + assert.equal(visit.location, url) +}) + +test("test visit proposal external location is proposed to adapter", async () => { + const url = new URL("https://example.com/") + + Turbo.navigator.proposeVisit(url) + assert.equal(adapter.proposedVisits.length, 1) + + const [visit] = adapter.proposedVisits + assert.equal(visit.location, url) +}) + +test("test visit started notifies adapter", async () => { + const locatable = window.location.toString() + + Turbo.navigator.startVisit(locatable) + assert.equal(adapter.startedVisits.length, 1) + + const [visit] = adapter.startedVisits + assert.equal(visit.location, locatable) +}) + +test("test visit completed notifies adapter", async () => { + const locatable = window.location.toString() + + Turbo.navigator.startVisit(locatable) + + const [startedVisit] = adapter.startedVisits + startedVisit.complete() + + const [completedVisit] = adapter.completedVisits + assert.equal(completedVisit.location, locatable) +}) + +test("test visit request started notifies adapter", async () => { + const locatable = window.location.toString() + + Turbo.navigator.startVisit(locatable) + + const [startedVisit] = adapter.startedVisits + startedVisit.startRequest() + assert.equal(adapter.startedVisitRequests.length, 1) + + const [startedVisitRequest] = adapter.startedVisitRequests + assert.equal(startedVisitRequest.location, locatable) +}) + +test("test visit request completed notifies adapter", async () => { + const locatable = window.location.toString() + + Turbo.navigator.startVisit(locatable) + + const [startedVisit] = adapter.startedVisits + startedVisit.recordResponse({ statusCode: 200, responseHTML: "responseHtml", redirected: false }) + assert.equal(adapter.completedVisitRequests.length, 1) + + const [completedVisitRequest] = adapter.completedVisitRequests + assert.equal(completedVisitRequest.location, locatable) +}) + +test("test visit request failed notifies adapter", async () => { + const locatable = window.location.toString() + + Turbo.navigator.startVisit(locatable) + + const [startedVisit] = adapter.startedVisits + startedVisit.recordResponse({ statusCode: 404, responseHTML: "responseHtml", redirected: false }) + assert.equal(adapter.failedVisitRequests.length, 1) + + const [failedVisitRequest] = adapter.failedVisitRequests + assert.equal(failedVisitRequest.location, locatable) +}) + +test("test visit request finished notifies adapter", async () => { + const locatable = window.location.toString() + + Turbo.navigator.startVisit(locatable) + + const [startedVisit] = adapter.startedVisits + startedVisit.finishRequest() + assert.equal(adapter.finishedVisitRequests.length, 1) + + const [finishedVisitRequest] = adapter.finishedVisitRequests + assert.equal(finishedVisitRequest.location, locatable) +}) + +test("test form submission started notifies adapter", async () => { + Turbo.navigator.formSubmissionStarted("formSubmissionStub") + assert.equal(adapter.startedFormSubmissions.length, 1) + + const [startedFormSubmission] = adapter.startedFormSubmissions + assert.equal(startedFormSubmission, "formSubmissionStub") +}) + +test("test form submission finished notifies adapter", async () => { + Turbo.navigator.formSubmissionFinished("formSubmissionStub") + assert.equal(adapter.finishedFormSubmissions.length, 1) + + const [finishedFormSubmission] = adapter.finishedFormSubmissions + assert.equal(finishedFormSubmission, "formSubmissionStub") +}) + + +test("test visit follows redirect and proposes replace visit to adapter", async () => { + const locatable = window.location.toString() + const redirectedLocation = "https://example.com" + + Turbo.navigator.startVisit(locatable) + + const [startedVisit] = adapter.startedVisits + startedVisit.redirectedToLocation = redirectedLocation + startedVisit.recordResponse({ statusCode: 200, responseHTML: "responseHtml", redirected: true }) + startedVisit.complete() + + assert.equal(adapter.completedVisitRequests.length, 1) + assert.equal(adapter.proposedVisits.length, 1) + + const [visit] = adapter.proposedVisits + assert.equal(visit.location, redirectedLocation) + assert.equal(visit.options.action, "replace") +})