Skip to content

Commit

Permalink
Prepare Frame Form Submission fetch headers
Browse files Browse the repository at this point in the history
Closes #86
Closes #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 12, 2021
1 parent d9b0c72 commit d696226
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 12 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 @@ -130,6 +130,8 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest
const frame = this.findFrameElement(element, submitter)
frame.src = 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
2 changes: 2 additions & 0 deletions src/tests/fixtures/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@
"turbo:frame-load",
"turbo:frame-render",
"turbo:frame-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 @@ -273,6 +273,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 @@ -351,6 +359,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 d696226

Please sign in to comment.