From 9d9ac173404617d795f4c25b1560b5658696755f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Tue, 23 Apr 2024 09:06:16 +0200 Subject: [PATCH] Use navigatable associated form unless submitter opts out (#1246) When using a submitter with [form=id] attribute, it should respect the form's navigatable state unless the submitter explicitly opts out of turbo. Implements the expected behavior outlined in this comment: https://github.com/hotwired/turbo/issues/1246#issuecomment-2070014930 --- src/core/session.js | 9 ++++++ .../fixtures/form_turbo_optin_submitter.html | 16 ++++++++++ .../form_turbo_option_submitter_tests.js | 30 +++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 src/tests/fixtures/form_turbo_optin_submitter.html create mode 100644 src/tests/functional/form_turbo_option_submitter_tests.js diff --git a/src/core/session.js b/src/core/session.js index cdb978348..0c33a5513 100644 --- a/src/core/session.js +++ b/src/core/session.js @@ -427,6 +427,8 @@ export class Session { submissionIsNavigatable(form, submitter) { if (this.formMode == "off") { return false + } else if (this.submitterReferencesNavigatable(form, submitter)) { + return true } else { const submitterIsNavigatable = submitter ? this.elementIsNavigatable(submitter) : true @@ -438,6 +440,13 @@ export class Session { } } + submitterReferencesNavigatable(form, submitter) { + const formReference = submitter.hasAttribute("form") + const optedOut = submitter.getAttribute("data-turbo") == "false" + + return formReference && !optedOut && this.elementIsNavigatable(form) + } + elementIsNavigatable(element) { const container = findClosestRecursively(element, "[data-turbo]") const withinFrame = findClosestRecursively(element, "turbo-frame") diff --git a/src/tests/fixtures/form_turbo_optin_submitter.html b/src/tests/fixtures/form_turbo_optin_submitter.html new file mode 100644 index 000000000..33fa04d88 --- /dev/null +++ b/src/tests/fixtures/form_turbo_optin_submitter.html @@ -0,0 +1,16 @@ + + + + + Form + + + + +
+ + +
+ + + diff --git a/src/tests/functional/form_turbo_option_submitter_tests.js b/src/tests/functional/form_turbo_option_submitter_tests.js new file mode 100644 index 000000000..31dd407a2 --- /dev/null +++ b/src/tests/functional/form_turbo_option_submitter_tests.js @@ -0,0 +1,30 @@ +import { test } from "@playwright/test" +import { assert } from "chai" +import { getFromLocalStorage, nextEventNamed, readEventLogs, setLocalStorageFromEvent } from "../helpers/page" + +test("allows submitting an associated data-turbo=true form without data-turbo=true on submitter", async ({ page }) => { + await page.goto("/src/tests/fixtures/form_turbo_optin_submitter.html") + await setLocalStorageFromEvent(page, "turbo:submit-start", "formSubmitStarted", "true") + await setLocalStorageFromEvent(page, "turbo:submit-end", "formSubmitEnded", "true") + await readEventLogs(page) + + await page.click("button[type=submit]") + + assert.ok(await formSubmitStarted(page), "fires turbo:submit-start") + + const { fetchOptions } = await nextEventNamed(page, "turbo:before-fetch-request") + + assert.ok(fetchOptions.headers["Accept"].includes("text/vnd.turbo-stream.html")) + + await nextEventNamed(page, "turbo:before-fetch-response") + + assert.ok(await formSubmitEnded(page), "fires turbo:submit-end") +}) + +function formSubmitStarted(page) { + return getFromLocalStorage(page, "formSubmitStarted") +} + +function formSubmitEnded(page) { + return getFromLocalStorage(page, "formSubmitEnded") +}