Skip to content

Commit

Permalink
Override Frame response target from server
Browse files Browse the repository at this point in the history
hotwired#257

---

While it's still unclear how servers will persist a `Turbo-Frame:`
header override across destructive actions and the `GET` requests that
result from their `303 See Other` responses, the client side concern is
more straightforward:

  Whenever a response with a `Turbo-Frame: _top` header is handled by a
  `FrameController`, propose a full-page Visit.

This commit adds test coverage for both `<a>` element initiated `GET`
requests, as well as `<form>` element initiated `POST` requests.
  • Loading branch information
seanpdoyle committed Sep 17, 2021
1 parent 60759ea commit 335df6e
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 4 deletions.
16 changes: 12 additions & 4 deletions src/core/frames/frame_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,12 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest
}

async requestSucceededWithResponse(request: FetchRequest, response: FetchResponse) {
await this.loadResponse(response)
this.resolveVisitPromise()
if (response.redirected && response.header("Turbo-Frame") == "_top") {
session.visit(response.location)
} else {
await this.loadResponse(response)
this.resolveVisitPromise()
}
}

requestFailedWithResponse(request: FetchRequest, response: FetchResponse) {
Expand All @@ -201,8 +205,12 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest
}

formSubmissionSucceededWithResponse(formSubmission: FormSubmission, response: FetchResponse) {
const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter)
frame.delegate.loadResponse(response)
if (response.redirected && response.header("Turbo-Frame") == "_top") {
session.visit(response.location)
} else {
const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter)
frame.delegate.loadResponse(response)
}
}

formSubmissionFailedWithResponse(formSubmission: FormSubmission, fetchResponse: FetchResponse) {
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 @@ -163,6 +163,11 @@ <h2>Frame: Form</h2>
<input type="hidden" name="path" value="/src/tests/fixtures/frames/form.html">
<input type="submit">
</form>
<form action="/__turbo/redirect" method="post" class="redirect turbo-frame-header">
<input type="hidden" name="turbo_frame" value="_top">
<input type="hidden" name="path" value="/__turbo/headers">
<button>response with Turbo-Frame: _top</button>
</form>
<form action="/__turbo/messages" method="post" class="created">
<input type="hidden" name="content" value="Hello!">
<input type="submit" style="">
Expand Down
1 change: 1 addition & 0 deletions src/tests/fixtures/frames.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ <h2>Frames: #nested-child</h2>
</turbo-frame>

<a id="frame-self" href="/src/tests/fixtures/frames/self.html" data-turbo-frame="frame">Visit self.html</a>
<a id="navigate-form-redirect-top" href="/__turbo/redirect?turbo_frame=_top&path=%2F__turbo%2Fheaders" data-turbo-frame="frame">Response with Turbo-Frame: _top</a>

<a id="navigate-form-redirect" href="/src/tests/fixtures/frames/form-redirect.html" data-turbo-frame="form-redirect">Visit form-redirect.html</a>
<turbo-frame id="form-redirect"></turbo-frame>
Expand Down
8 changes: 8 additions & 0 deletions src/tests/functional/form_submission_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,14 @@ export class FormSubmissionTests extends TurboDriveTestCase {
this.assert.equal(await title.getVisibleText(), "Frame: Unprocessable Entity")
}

async "test frame form submission response with Turbo-Frame=_top header"() {
await this.clickSelector("#frame form.redirect.turbo-frame-header button")
await this.nextBeat

const title = await this.querySelector("h1")
this.assert.equal(await title.getVisibleText(), "Request Headers")
}

async "test invalid frame form submission with internal server errror status"() {
await this.clickSelector("#frame form.internal_server_error input[type=submit]")
await this.nextBeat
Expand Down
8 changes: 8 additions & 0 deletions src/tests/functional/frame_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ export class FrameTests extends TurboDriveTestCase {
this.assert.equal(otherEvents.length, 0, "no more events")
}

async "test a frame request with Turbo-Frame=_top header in response"() {
await this.clickSelector("#navigate-form-redirect-top")
await this.nextBeat

const title = await this.querySelector("h1")
this.assert.equal(await title.getVisibleText(), "Request Headers")
}

async "test following a link driving a frame toggles the [busy] attribute"() {
await this.clickSelector("#hello a")

Expand Down
4 changes: 4 additions & 0 deletions src/tests/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ router.post("/reject", (request, response) => {

router.get("/headers", (request, response) => {
const template = fs.readFileSync("src/tests/fixtures/headers.html").toString()
const { turbo_frame } = request.query

if (typeof turbo_frame == "string") response.set("Turbo-Frame", turbo_frame)

response.type("html").status(200).send(template.replace('$HEADERS', JSON.stringify(request.headers, null, 4)))
})

Expand Down

0 comments on commit 335df6e

Please sign in to comment.