Skip to content

Commit

Permalink
Prepare Frame Form Submission fetch headers
Browse files Browse the repository at this point in the history
Closes hotwired#86
Closes hotwired#110

When submitting a Form that is within a `<turbo-frame>` or targets a
`<turbo-frame>`, ensure that the `Turbo-Frame` header is present.

Since the constructive-style
`FetchRequestDelegate.additionalHeadersForRequest()` was replaced by the
mutative style `FetchRequestDelegate.prepareHeadersForRequest()`, this
commit changes the `FetchRequest.headers` property to be `readonly` and
created at constructor-time so that its values can be mutated prior to
the request's submission.

Testing adds listeners for `turbo:before-fetch-request` and
`turbo:before-fetch-response` so that the event logs can drain.

Co-authored-by: tleish <tleish@users.noreply.github.com>
  • Loading branch information
seanpdoyle and tleish committed Feb 7, 2021
1 parent 57a118e commit a6fe71d
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 14 deletions.
2 changes: 2 additions & 0 deletions src/core/frames/frame_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest
if (this.formSubmission.fetchRequest.isIdempotent) {
this.navigateFrame(element, this.formSubmission.fetchRequest.url.href)
} else {
const { fetchRequest } = this.formSubmission
this.prepareHeadersForRequest(fetchRequest.headers, fetchRequest)
this.formSubmission.start()
}
}
Expand Down
20 changes: 8 additions & 12 deletions src/http/fetch_request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface FetchRequestOptions {
export class FetchRequest {
readonly delegate: FetchRequestDelegate
readonly method: FetchMethod
readonly headers: FetchRequestHeaders
readonly url: URL
readonly body?: FetchRequestBody
readonly abortController = new AbortController
Expand All @@ -55,6 +56,7 @@ export class FetchRequest {
this.body = body
this.url = location
}
this.headers = prepareHeadersForRequest(this)
}

get location(): URL {
Expand Down Expand Up @@ -116,23 +118,17 @@ export class FetchRequest {
return this.method == FetchMethod.get
}

get headers() {
const headers = { ...this.defaultHeaders }
if (typeof this.delegate.prepareHeadersForRequest == "function") {
this.delegate.prepareHeadersForRequest(headers, this)
}
return headers
}

get abortSignal() {
return this.abortController.signal
}
}

get defaultHeaders() {
return {
"Accept": "text/html, application/xhtml+xml"
}
function prepareHeadersForRequest(fetchRequest: FetchRequest) {
const headers = { "Accept": "text/html, application/xhtml+xml" }
if (typeof fetchRequest.delegate.prepareHeadersForRequest == "function") {
fetchRequest.delegate.prepareHeadersForRequest(headers, fetchRequest)
}
return headers
}

function mergeFormDataEntries(url: URL, entries: [string, FormDataEntryValue][]): URL {
Expand Down
5 changes: 3 additions & 2 deletions src/tests/fixtures/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"turbo:before-visit",
"turbo:load",
"turbo:render",
"turbo:request-end",
"turbo:visit"
"turbo:visit",
"turbo:before-fetch-request",
"turbo:before-fetch-response",
])
16 changes: 16 additions & 0 deletions src/tests/functional/form_submission_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,14 @@ export class FormSubmissionTests extends TurboDriveTestCase {
this.assert.equal(htmlAfter, htmlBefore)
}

async "test frame form submission within a frame submits the Turbo-Frame header"() {
await this.clickSelector("#frame form.redirect input[type=submit]")

const { fetchOptions } = await this.nextEventNamed("turbo:before-fetch-request")

this.assert.ok(fetchOptions.headers["Turbo-Frame"], "submits with the Turbo-Frame header")
}

async "test invalid frame form submission with unprocessable entity status"() {
await this.clickSelector("#frame form.unprocessable_entity input[type=submit]")
await this.nextBeat
Expand Down Expand Up @@ -260,6 +268,14 @@ export class FormSubmissionTests extends TurboDriveTestCase {
this.assert.equal(await this.pathname, "/src/tests/fixtures/one.html")
}

async "test form submission targeting a frame submits the Turbo-Frame header"() {
await this.clickSelector('#targets-frame [type="submit"]')

const { fetchOptions } = await this.nextEventNamed("turbo:before-fetch-request")

this.assert.ok(fetchOptions.headers["Turbo-Frame"], "submits with the Turbo-Frame header")
}

get formSubmitted(): Promise<boolean> {
return this.hasSelector("html[data-form-submitted]")
}
Expand Down

0 comments on commit a6fe71d

Please sign in to comment.