diff --git a/src/core/drive/navigator.ts b/src/core/drive/navigator.ts
index 768cf7123..8dc56ddc4 100644
--- a/src/core/drive/navigator.ts
+++ b/src/core/drive/navigator.ts
@@ -40,15 +40,10 @@ export class Navigator {
this.currentVisit.start()
}
- submitForm(form: HTMLFormElement, submitter?: HTMLElement) {
+ submitForm(formSubmission: FormSubmission) {
this.stop()
- this.formSubmission = new FormSubmission(this, form, submitter, true)
-
- if (this.formSubmission.isIdempotent) {
- this.proposeVisit(this.formSubmission.fetchRequest.url, { action: this.getActionForFormSubmission(this.formSubmission) })
- } else {
- this.formSubmission.start()
- }
+ this.formSubmission = formSubmission
+ this.formSubmission.start()
}
stop() {
diff --git a/src/core/session.ts b/src/core/session.ts
index b9c5b597e..f476fa32a 100644
--- a/src/core/session.ts
+++ b/src/core/session.ts
@@ -15,6 +15,7 @@ import { Action, Position, StreamSource, isAction } from "./types"
import { dispatch } from "../util"
import { PageView, PageViewDelegate } from "./drive/page_view"
import { Visit, VisitOptions } from "./drive/visit"
+import { FormSubmission } from "./drive/form_submission"
import { PageSnapshot } from "./drive/page_snapshot"
import { FrameElement } from "../elements/frame_element"
import { FetchResponse } from "../http/fetch_response"
@@ -135,7 +136,7 @@ export class Session implements FormSubmitObserverDelegate, HistoryDelegate, Lin
}
followedLinkToLocation(link: Element, location: URL) {
- const action = this.getActionForLink(link)
+ const action = this.getActionForLink(link) || "advance"
this.convertLinkWithMethodClickToFormSubmission(link) || this.visit(location.href, { action })
}
@@ -197,7 +198,18 @@ export class Session implements FormSubmitObserverDelegate, HistoryDelegate, Lin
}
formSubmitted(form: HTMLFormElement, submitter?: HTMLElement) {
- this.navigator.submitForm(form, submitter)
+ const formSubmission = new FormSubmission(this.navigator, form, submitter, true)
+ const { isIdempotent, fetchRequest: { url } } = formSubmission
+
+ if (isIdempotent) {
+ const action = submitter && this.applicationAllowsFollowingLinkToLocation(submitter, url) ?
+ this.getActionForLink(submitter) || this.getActionForLink(form) :
+ this.getActionForLink(form)
+
+ this.visit(url, { action: action || "advance" })
+ } else {
+ this.navigator.submitForm(formSubmission)
+ }
}
// Page observer delegate
@@ -330,9 +342,9 @@ export class Session implements FormSubmitObserverDelegate, HistoryDelegate, Lin
// Private
- getActionForLink(link: Element): Action {
+ private getActionForLink(link: Element): Action | null {
const action = link.getAttribute("data-turbo-action")
- return isAction(action) ? action : "advance"
+ return isAction(action) ? action : null
}
get snapshot() {
diff --git a/src/tests/fixtures/navigation.html b/src/tests/fixtures/navigation.html
index 7abe7224f..0a886c022 100644
--- a/src/tests/fixtures/navigation.html
+++ b/src/tests/fixtures/navigation.html
@@ -33,10 +33,11 @@
Navigation
-
Same-origin data-turbo-action=replace link
- - + +Same-origin data-turbo=false link
Same-origin unannotated link inside data-turbo=false container
Same-origin data-turbo=true link inside data-turbo=false container
diff --git a/src/tests/fixtures/test.js b/src/tests/fixtures/test.js index 0cc3ea174..1c01362d2 100644 --- a/src/tests/fixtures/test.js +++ b/src/tests/fixtures/test.js @@ -19,6 +19,7 @@ } }).observe(document, { subtree: true, childList: true, attributes: true }) })([ + "turbo:click", "turbo:before-cache", "turbo:before-render", "turbo:before-visit", diff --git a/src/tests/functional/navigation_tests.ts b/src/tests/functional/navigation_tests.ts index 55da888aa..dd34b45ba 100644 --- a/src/tests/functional/navigation_tests.ts +++ b/src/tests/functional/navigation_tests.ts @@ -37,6 +37,21 @@ export class NavigationTests extends TurboDriveTestCase { this.assert.equal(await this.visitAction, "advance") } + async "test following a same-origin location link"() { + await this.drainEventLog + + const link = await this.querySelector("#same-origin-unannotated-link") + const href = await link.getProperty("href") + await link.click() + await this.nextBody + + const [ eventName, { url }, id ] = await this.nextEvent() + + this.assert.equal(eventName, "turbo:click") + this.assert.equal(url, href, "turbo:click detail.url is href") + this.assert.equal(id, "same-origin-unannotated-link", "turbo:click target is link") + } + async "test following a same-origin unannotated custom element link"() { await this.nextBeat await this.remote.execute(() => { @@ -49,13 +64,44 @@ export class NavigationTests extends TurboDriveTestCase { this.assert.equal(await this.visitAction, "advance") } - async "test following a same-origin unannotated form[method=GET]"() { - this.clickSelector("#same-origin-unannotated-form button") + async "test submitting a same-origin unannotated form[method=GET]"() { + this.clickSelector("#same-origin-unannotated-submitter") await this.nextBody this.assert.equal(await this.pathname, "/src/tests/fixtures/one.html") this.assert.equal(await this.visitAction, "advance") } + async "test submitting a same-origin form by clicking a submitter"() { + await this.drainEventLog + + const form = await this.querySelector("#same-origin-unannotated-form") + const action = await form.getProperty("action") + const button = await this.querySelector("#same-origin-unannotated-submitter") + await button.click() + await this.nextBody + + const [ eventName, { url }, id ] = await this.nextEvent() + + this.assert.equal(eventName, "turbo:click") + this.assert.equal(url, action, "turbo:click detail.url is href") + this.assert.equal(id, "same-origin-unannotated-submitter", "turbo:click target is submitter") + } + + async "test submitting a same-origin form by clicking a submitter with formaction"() { + await this.drainEventLog + + const button = await this.querySelector("#same-origin-submitter-formaction") + const action = await this.expandURL(await button.getProperty("formAction")) + await button.click() + await this.nextBody + + const [ eventName, { url }, id ] = await this.nextEvent() + + this.assert.equal(eventName, "turbo:click") + this.assert.equal(url, action, "turbo:click detail.url is formaction") + this.assert.equal(id, "same-origin-submitter-formaction", "turbo:click target is submitter") + } + async "test following a same-origin data-turbo-action=replace link"() { this.clickSelector("#same-origin-replace-link") await this.nextBody @@ -232,6 +278,7 @@ export class NavigationTests extends TurboDriveTestCase { async "test same-page anchor visits do not trigger visit events"() { const events = [ + "turbo:click", "turbo:before-visit", "turbo:visit", "turbo:before-cache", @@ -241,7 +288,6 @@ export class NavigationTests extends TurboDriveTestCase { ] for (const eventName in events) { - await this.goToLocation("/src/tests/fixtures/navigation.html") await this.clickSelector('a[href="#main"]') this.assert.ok(await this.noNextEventNamed(eventName), `same-page links do not trigger ${eventName} events`) } diff --git a/src/tests/helpers/functional_test_case.ts b/src/tests/helpers/functional_test_case.ts index 7d17c2e30..09d778265 100644 --- a/src/tests/helpers/functional_test_case.ts +++ b/src/tests/helpers/functional_test_case.ts @@ -121,10 +121,18 @@ export class FunctionalTestCase extends InternTestCase { return await this.remote.execute(callback, args) } + async expandURL(pathname: string | null | undefined) { + return await this.evaluate((pathname) => new URL(pathname || "", document.baseURI), [pathname]) + } + get head(): Promise