From 8e724d3919af3bacfa9bf1096a39a1560c375dc3 Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Fri, 19 Aug 2022 12:38:54 -0400 Subject: [PATCH] Encode Visit Action into `Turbo-Action:` header When initiating a `Visit` from an `` element, encode the Action value into the request's `Turbo-Action:` header. Similarly, when submitting a `
`, encode the value into the header. If a `` is navigated with an Action, encode the action along with the `Turbo-Frame:` header. --- src/core/drive/form_submission.js | 8 ++++++- src/core/drive/visit.js | 2 ++ src/core/frames/frame_controller.js | 4 ++++ src/tests/fixtures/form.html | 5 +++++ src/tests/functional/form_submission_tests.js | 8 +++++++ src/tests/functional/frame_tests.js | 21 ++++++++++--------- src/tests/functional/visit_tests.js | 11 +++++++++- 7 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/core/drive/form_submission.js b/src/core/drive/form_submission.js index e2fa643b2..4d6a9ef49 100644 --- a/src/core/drive/form_submission.js +++ b/src/core/drive/form_submission.js @@ -1,6 +1,6 @@ import { FetchRequest, FetchMethod, fetchMethodFromString, fetchEnctypeFromString, isSafe } from "../../http/fetch_request" import { expandURL } from "../url" -import { dispatch, getAttribute, getMetaContent, hasAttribute } from "../../util" +import { dispatch, getAttribute, getMetaContent, getVisitAction, hasAttribute } from "../../util" import { StreamMessage } from "../streams/stream_message" export const FormSubmissionState = { @@ -111,6 +111,12 @@ export class FormSubmission { if (this.requestAcceptsTurboStreamResponse(request)) { request.acceptResponseType(StreamMessage.contentType) } + + const turboAction = getVisitAction(this.submitter, this.formElement) + + if (turboAction) { + request.headers["Turbo-Action"] = turboAction + } } requestStarted(_request) { diff --git a/src/core/drive/visit.js b/src/core/drive/visit.js index 132819507..5224df2ba 100644 --- a/src/core/drive/visit.js +++ b/src/core/drive/visit.js @@ -294,6 +294,8 @@ export class Visit { if (this.acceptsStreamResponse) { request.acceptResponseType(StreamMessage.contentType) } + + request.headers["Turbo-Action"] = this.action } requestStarted() { diff --git a/src/core/frames/frame_controller.js b/src/core/frames/frame_controller.js index 5940ba761..fabdef7ff 100644 --- a/src/core/frames/frame_controller.js +++ b/src/core/frames/frame_controller.js @@ -197,6 +197,10 @@ export class FrameController { if (this.currentNavigationElement?.hasAttribute("data-turbo-stream")) { request.acceptResponseType(StreamMessage.contentType) } + + if (this.action) { + request.headers["Turbo-Action"] = this.action + } } requestStarted(_request) { diff --git a/src/tests/fixtures/form.html b/src/tests/fixtures/form.html index 707640393..b132178a6 100644 --- a/src/tests/fixtures/form.html +++ b/src/tests/fixtures/form.html @@ -101,6 +101,11 @@

Form

+
+ + + +

diff --git a/src/tests/functional/form_submission_tests.js b/src/tests/functional/form_submission_tests.js index 987038448..2d75e679d 100644 --- a/src/tests/functional/form_submission_tests.js +++ b/src/tests/functional/form_submission_tests.js @@ -197,6 +197,14 @@ test("supports modifying the submission in a turbo:before-fetch-request listener assert.equal(getSearchParam(page.url(), "greeting"), "Hello from a redirect") }) +test("standard POST encodes [data-turbo-action] into Turbo-Action", async ({ page }) => { + await page.click("#standard-post-form-replace-submit") + + const { fetchOptions } = await nextEventNamed(page, "turbo:before-fetch-request") + + assert.equal(fetchOptions.headers["Turbo-Action"], "replace", "encodes the action to the Turbo-Action header") +}) + test("standard POST form submission merges values from both searchParams and body", async ({ page }) => { await page.click("#form-action-post-redirect-self-q-b") await nextBody(page) diff --git a/src/tests/functional/frame_tests.js b/src/tests/functional/frame_tests.js index b090aff8a..363a8465a 100644 --- a/src/tests/functional/frame_tests.js +++ b/src/tests/functional/frame_tests.js @@ -642,13 +642,13 @@ test("navigating a frame with a form[method=get] that does not redirect still up test("navigating turbo-frame[data-turbo-action=advance] from within pushes URL state", async ({ page }) => { await page.click("#add-turbo-action-to-frame") await page.click("#link-frame") + const { fetchOptions } = await nextEventOnTarget(page, "frame", "turbo:before-fetch-request") await nextEventNamed(page, "turbo:load") - const title = await page.textContent("h1") - const frameTitle = await page.textContent("#frame h2") - - assert.equal(title, "Frames") - assert.equal(frameTitle, "Frame: Loaded") + assert.equal(fetchOptions.headers["Turbo-Frame"], "frame", "encodes Turbo-Frame header") + assert.equal(fetchOptions.headers["Turbo-Action"], "advance", "encodes Turbo-Action header") + assert.equal(await page.textContent("h1"), "Frames") + assert.equal(await page.textContent("#frame h2"), "Frame: Loaded") assert.equal(pathname(page.url()), "/src/tests/fixtures/frames/frame.html") }) @@ -736,15 +736,16 @@ test("navigating frame with a[data-turbo-action=advance] pushes URL state", asyn test("navigating frame with form[method=get][data-turbo-action=advance] pushes URL state", async ({ page }) => { await page.click("#form-get-frame-action-advance button") + const { fetchOptions } = await nextEventOnTarget(page, "form-get-frame-action-advance", "turbo:before-fetch-request") await nextEventNamed(page, "turbo:load") - const title = await page.textContent("h1") - const frameTitle = await page.textContent("#frame h2") - const src = (await attributeForSelector(page, "#frame", "src")) ?? "" + const src = (await attributeForSelector(page, "#frame", "src")) || "" + assert.equal(fetchOptions.headers["Turbo-Frame"], "frame", "encodes the frame [id] to the Turbo-Frame header") + assert.equal(fetchOptions.headers["Turbo-Action"], "advance", "encodes the action to the Turbo-Action header") assert.ok(src.includes("/src/tests/fixtures/frames/frame.html"), "updates src attribute") - assert.equal(title, "Frames") - assert.equal(frameTitle, "Frame: Loaded") + assert.equal(await page.textContent("h1"), "Frames") + assert.equal(await page.textContent("#frame h2"), "Frame: Loaded") assert.equal(pathname(page.url()), "/src/tests/fixtures/frames/frame.html") assert.ok(await hasSelector(page, "#frame[complete]"), "marks the frame as [complete]") }) diff --git a/src/tests/functional/visit_tests.js b/src/tests/functional/visit_tests.js index 782871747..f5c63eae0 100644 --- a/src/tests/functional/visit_tests.js +++ b/src/tests/functional/visit_tests.js @@ -117,6 +117,13 @@ test("turbo:before-fetch-request event.detail encodes searchParams", async ({ pa assert.ok(url.includes("/src/tests/fixtures/one.html?key=value")) }) +test("turbo:before-fetch-request encodes the action", async ({ page }) => { + await page.click("#same-origin-replace-link") + const { fetchOptions: { headers } } = await nextEventNamed(page, "turbo:before-fetch-request") + + assert.equal(headers["Turbo-Action"], "replace", "encodes the action into Turbo-Action") +}) + test("turbo:before-fetch-response open new site", async ({ page }) => { page.evaluate(() => addEventListener( @@ -251,8 +258,10 @@ test("Visit direction attribute when navigating forward", async ({ page }) => { }) test("Visit direction attribute on a replace visit", async ({ page }) => { - page.click("#same-origin-replace-link") + await page.click("#same-origin-replace-link") + const { fetchOptions: { headers } } = await nextEventNamed(page, "turbo:before-fetch-request") + assert.equal(headers["Turbo-Action"], "replace", "encodes the action into Turbo-Action") await assertVisitDirectionAttribute(page, "none") })