Skip to content

Commit

Permalink
Encode Visit Action into Turbo-Action: header
Browse files Browse the repository at this point in the history
When initiating a `Visit` from an `<a data-turbo-action="...">` element,
encode the Action value into the request's `Turbo-Action:` header.

Similarly, when submitting a `<form data-turbo-action="...">`, encode
the value into the header.

If a `<turbo-frame>` is navigated with an Action, encode the action
along with the `Turbo-Frame:` header.
  • Loading branch information
seanpdoyle committed Dec 15, 2023
1 parent af16eee commit 8e724d3
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 12 deletions.
8 changes: 7 additions & 1 deletion src/core/drive/form_submission.js
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions src/core/drive/visit.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ export class Visit {
if (this.acceptsStreamResponse) {
request.acceptResponseType(StreamMessage.contentType)
}

request.headers["Turbo-Action"] = this.action
}

requestStarted() {
Expand Down
4 changes: 4 additions & 0 deletions src/core/frames/frame_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
5 changes: 5 additions & 0 deletions src/tests/fixtures/form.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ <h1>Form</h1>
<input type="submit">
<button type="submit" name="greeting" id="secondary_submitter" data-turbo-confirm="Are you really sure?" formaction="/__turbo/redirect?path=/src/tests/fixtures/one.html" formmethod="post" value="secondary_submitter">Secondary action</button>
</form>
<form action="/__turbo/redirect" method="post" data-turbo-action="replace">
<input type="hidden" name="path" value="/src/tests/fixtures/form.html">
<input type="hidden" name="greeting" value="Hello from a replace redirect">
<button id="standard-post-form-replace-submit">form[method=post][data-turbo-action="replace"]</button>
</form>
</div>
<hr>
<div id="no-action">
Expand Down
8 changes: 8 additions & 0 deletions src/tests/functional/form_submission_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
21 changes: 11 additions & 10 deletions src/tests/functional/frame_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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")
})

Expand Down Expand Up @@ -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]")
})
Expand Down
11 changes: 10 additions & 1 deletion src/tests/functional/visit_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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")
})

Expand Down

0 comments on commit 8e724d3

Please sign in to comment.