From 15db62778b8b7caa6661c84dbfdb22bc5141c77e 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 | 17 +++++++++ .../form_turbo_option_submitter_tests.js | 38 +++++++++++++++++++ 3 files changed, 64 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..caa7e8ad3 --- /dev/null +++ b/src/tests/fixtures/form_turbo_optin_submitter.html @@ -0,0 +1,17 @@ + + + + + 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..e2874adae --- /dev/null +++ b/src/tests/functional/form_turbo_option_submitter_tests.js @@ -0,0 +1,38 @@ +import { test } from "@playwright/test" +import { assert } from "chai" +import { getFromLocalStorage, nextEventNamed, readEventLogs, setLocalStorageFromEvent } from "../helpers/page" + +test.beforeEach(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) +}) + +test("allows submitting an associated data-turbo=true form without data-turbo=true on submitter", async ({ page }) => { + await page.click("#default-behavior-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") +}) + +test("prevents submitting an associated data-turbo=true form when explicitly opted out", async ({ page }) => { + await page.click("#opted-out-submit") + + assert.notOk(await formSubmitStarted(page), "fires turbo:submit-start") +}) + +function formSubmitStarted(page) { + return getFromLocalStorage(page, "formSubmitStarted") +} + +function formSubmitEnded(page) { + return getFromLocalStorage(page, "formSubmitEnded") +}