From 335359aa46cf3eafc70e01c1f5ee579cb68f12ba Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Tue, 23 Nov 2021 19:11:29 -0500 Subject: [PATCH] Read Frame Action when navigating programmatically Closes [hotwired/turbo#468][] [hotwired/turbo#468]: https://github.com/hotwired/turbo/issues/468 --- src/core/frames/frame_controller.ts | 40 +++++++++++++++++++---------- src/tests/functional/frame_tests.ts | 13 ++++++++++ 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/core/frames/frame_controller.ts b/src/core/frames/frame_controller.ts index ce98a5fae..f65106c9c 100644 --- a/src/core/frames/frame_controller.ts +++ b/src/core/frames/frame_controller.ts @@ -12,7 +12,7 @@ import { FrameView } from "./frame_view" import { LinkInterceptor, LinkInterceptorDelegate } from "./link_interceptor" import { FrameRenderer } from "./frame_renderer" import { session } from "../index" -import { isAction } from "../types" +import { Action, isAction } from "../types" export class FrameController implements AppearanceObserverDelegate, FetchRequestDelegate, FormInterceptorDelegate, FormSubmissionDelegate, FrameElementDelegate, LinkInterceptorDelegate, ViewDelegate> { readonly element: FrameElement @@ -86,6 +86,8 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest this.currentURL = this.sourceURL if (this.sourceURL) { try { + const action = getVisitAction(this.element) + if (action) this.proposeVisitWithAction(this.element, action) this.element.loaded = this.visit(expandURL(this.sourceURL)) this.appearanceObserver.stop() await this.element.loaded @@ -93,6 +95,8 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest } catch (error) { this.currentURL = previousURL throw error + } finally { + this.fetchResponseLoaded = () => {} } } } @@ -260,23 +264,25 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest frame.src = url } - private proposeVisitIfNavigatedWithAction(frame: FrameElement, element: Element, submitter?: HTMLElement) { - const action = getAttribute("data-turbo-action", submitter, element, frame) - - if (isAction(action)) { - const { visitCachedSnapshot } = new SnapshotSubstitution(frame) - frame.delegate.fetchResponseLoaded = (fetchResponse: FetchResponse) => { - if (frame.src) { - const { statusCode, redirected } = fetchResponse - const responseHTML = frame.ownerDocument.documentElement.outerHTML - const response = { statusCode, redirected, responseHTML } + private proposeVisitWithAction(frame: FrameElement, action: Action) { + const { visitCachedSnapshot } = new SnapshotSubstitution(frame) + frame.delegate.fetchResponseLoaded = (fetchResponse: FetchResponse) => { + if (frame.src) { + const { statusCode, redirected } = fetchResponse + const responseHTML = frame.ownerDocument.documentElement.outerHTML + const response = { statusCode, redirected, responseHTML } - session.visit(frame.src, { action, response, visitCachedSnapshot, willRender: false }) - } + session.visit(frame.src, { action, response, visitCachedSnapshot, willRender: false }) } } } + private proposeVisitIfNavigatedWithAction(frame: FrameElement, element: Element, submitter?: HTMLElement) { + const action = getVisitAction(submitter, element, frame) + + if (action) this.proposeVisitWithAction(frame, action) + } + private findFrameElement(element: Element, submitter?: HTMLElement) { const id = getAttribute("data-turbo-frame", submitter, element) || this.element.getAttribute("target") return getFrameElementById(id) ?? this.element @@ -411,6 +417,14 @@ class SnapshotSubstitution { } } +function getVisitAction(...elements: (Element|undefined)[]): Action | null { + const action = getAttribute("data-turbo-action", ...elements) + + return isAction(action) ? + action : + null +} + function getFrameElementById(id: string | null) { if (id != null) { const element = document.getElementById(id) diff --git a/src/tests/functional/frame_tests.ts b/src/tests/functional/frame_tests.ts index b2550e832..ffb4745bd 100644 --- a/src/tests/functional/frame_tests.ts +++ b/src/tests/functional/frame_tests.ts @@ -360,6 +360,19 @@ export class FrameTests extends TurboDriveTestCase { this.assert.equal(await this.pathname, "/src/tests/fixtures/frames/frame.html") } + async "test navigating turbo-frame[data-turbo-action=advance] programmatically pushes URL state"() { + await this.clickSelector("#add-turbo-action-to-frame") + await this.evaluate( + element => element.setAttribute("src", "/src/tests/fixtures/frames/frame.html"), + await this.querySelector("#frame") + ) + await this.nextEventNamed("turbo:load") + + this.assert.equal(await (await this.querySelector("h1")).getVisibleText(), "Frames") + this.assert.equal(await (await this.querySelector("#frame h2")).getVisibleText(), "Frame: Loaded") + this.assert.equal(await this.pathname, "/src/tests/fixtures/frames/frame.html") + } + async "test navigating turbo-frame[data-turbo-action=advance] to the same URL clears the [aria-busy] and [data-turbo-preview] state"() { await this.clickSelector("#link-outside-frame-action-advance") await this.nextEventNamed("turbo:load")