Skip to content

Commit

Permalink
Override Frame response target from server
Browse files Browse the repository at this point in the history
#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 Nov 2, 2021
1 parent 9fcb68d commit 27eb4b0
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 5 deletions.
27 changes: 22 additions & 5 deletions src/core/frames/frame_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,16 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest
}

async requestSucceededWithResponse(request: FetchRequest, response: FetchResponse) {
await this.loadResponse(response)
this.resolveVisitPromise()
const { location, redirected, statusCode } = response

if (redirected && response.header("Turbo-Frame") == "_top") {
const responseHTML = await response.responseHTML

session.visit(location, { response: { statusCode, redirected, responseHTML } })
} else {
await this.loadResponse(response)
this.resolveVisitPromise()
}
}

requestFailedWithResponse(request: FetchRequest, response: FetchResponse) {
Expand All @@ -197,9 +205,18 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest
frame.setAttribute("busy", "")
}

formSubmissionSucceededWithResponse(formSubmission: FormSubmission, response: FetchResponse) {
const frame = this.findFrameElement(formSubmission.formElement, formSubmission.submitter)
frame.delegate.loadResponse(response)
async formSubmissionSucceededWithResponse(formSubmission: FormSubmission, response: FetchResponse) {
const { location, redirected, statusCode } = response

if (redirected && response.header("Turbo-Frame") == "_top") {
const responseHTML = await response.responseHTML

session.view.clearSnapshotCache()
session.visit(location, { response: { statusCode, redirected, responseHTML } })
} 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 @@ -181,6 +181,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 @@ -65,6 +65,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
12 changes: 12 additions & 0 deletions src/tests/functional/form_submission_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,18 @@ 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.nextEventNamed("turbo:before-fetch-request")
await this.nextEventNamed("turbo:before-fetch-response")
const eventLog = await this.eventLogChannel.read()
await this.nextBody

this.assert.notOk(await this.hasSelector("#frame"), "Navigates entire page")
this.assert.equal(await (await this.querySelector("h1")).getVisibleText(), "Request Headers")
this.assert.notOk(eventLog.some(([ name ]) => name == "turbo:before-fetch-request" || name == "turbo:before-fetch-response"), "does not make subsequent requests")
}

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
12 changes: 12 additions & 0 deletions src/tests/functional/frame_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ 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.nextEventOnTarget("frame", "turbo:before-fetch-request")
await this.nextEventOnTarget("frame", "turbo:before-fetch-response")
const eventLog = await this.eventLogChannel.read()
await this.nextBody

this.assert.notOk(await this.hasSelector("#frame"), "Navigates entire page")
this.assert.equal(await (await this.querySelector("h1")).getVisibleText(), "Request Headers")
this.assert.notOk(eventLog.some(([ name ]) => name == "turbo:before-fetch-request" || name == "turbo:before-fetch-response"), "does not make subsequent requests")
}

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 27eb4b0

Please sign in to comment.