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 4e7796302..567d658ad 100644
--- a/src/core/drive/visit.js
+++ b/src/core/drive/visit.js
@@ -286,6 +286,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/fixtures/visit.html b/src/tests/fixtures/visit.html
index f4cef2457..1b8fc68ae 100644
--- a/src/tests/fixtures/visit.html
+++ b/src/tests/fixtures/visit.html
@@ -13,6 +13,7 @@
Visit
Same-origin link
+ Same-origin link replace
Same-origin link with ?key=value
Sample response
Same page link
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 24aa89295..9fff3d493 100644
--- a/src/tests/functional/visit_tests.js
+++ b/src/tests/functional/visit_tests.js
@@ -114,6 +114,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-link-replace")
+ const { fetchOptions } = await nextEventNamed(page, "turbo:before-fetch-request")
+
+ assert.equal(fetchOptions.headers["Turbo-Action"], "replace", "encodes the action into Turbo-Action")
+})
+
test("turbo:before-fetch-response open new site", async ({ page }) => {
page.evaluate(() =>
addEventListener(