Skip to content

Commit

Permalink
Add test coverage for turbo-frame[busy]
Browse files Browse the repository at this point in the history
During the request lifecycle, `<turbo-frame>` elements will toggle the
`[busy]` boolean attribute to true when the request starts, and then
remove it when the request ends.

This commit adds functional test coverage for that behavior, along with
a `RemoteChannel` to transmit `MutationObserver` records from the
browser to the test harness.
  • Loading branch information
seanpdoyle committed Apr 10, 2021
1 parent 5a9f437 commit da75401
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 4 deletions.
6 changes: 4 additions & 2 deletions src/core/frames/frame_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest
// Form submission delegate

formSubmissionStarted(formSubmission: FormSubmission) {

const frame = this.findFrameElement(formSubmission.formElement)
frame.setAttribute("busy", "")
}

formSubmissionSucceededWithResponse(formSubmission: FormSubmission, response: FetchResponse) {
Expand All @@ -203,7 +204,8 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest
}

formSubmissionFinished(formSubmission: FormSubmission) {

const frame = this.findFrameElement(formSubmission.formElement)
frame.removeAttribute("busy")
}

// View delegate
Expand Down
7 changes: 6 additions & 1 deletion src/tests/fixtures/form.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,15 @@
</div>
<hr>
<div id="targets-frame">
<form action="/__turbo/redirect" method="post" data-turbo-frame="frame">
<form action="/__turbo/redirect" method="post" data-turbo-frame="frame" class="one">
<input type="hidden" name="path" value="/src/tests/fixtures/one.html">
<button type="submit">Submit</button>
</form>

<form action="/__turbo/redirect" method="post" data-turbo-frame="frame" class="frame">
<input type="hidden" name="path" value="/src/tests/fixtures/frames/frame.html">
<button type="submit">Submit</button>
</form>
</div>
<turbo-frame id="frame">
<h2>Frame: Form</h2>
Expand Down
8 changes: 8 additions & 0 deletions src/tests/fixtures/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@
function eventListener(event) {
eventLogs.push([event.type, event.detail])
}
window.mutationLogs = []

new MutationObserver((mutations) => {
for (const { attributeName, target } of mutations.filter(({ type }) => type == "attributes")) {
if (target instanceof Element) {
mutationLogs.push([attributeName, target.id, target.getAttribute(attributeName)])
}
}
}).observe(document, { subtree: true, childList: true, attributes: true })
})([
"turbo:before-cache",
"turbo:before-render",
Expand Down
19 changes: 18 additions & 1 deletion src/tests/functional/form_submission_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,23 @@ export class FormSubmissionTests extends TurboDriveTestCase {
this.assert.equal(await this.attributeForSelector("#frame", "src"), url.href, "redirects the target frame")
}

async "test frame form submission toggles the ancestor frame's [busy] attribute"() {
await this.clickSelector("#frame form.redirect input[type=submit]")

this.assert.equal(await this.nextAttributeMutationNamed("frame", "busy"), "", "sets [busy] on the #frame")
this.assert.equal(await this.nextAttributeMutationNamed("frame", "busy"), null, "removes [busy] from the #frame")
}

async "test frame form submission toggles the target frame's [busy] attribute"() {
await this.clickSelector('#targets-frame form.frame [type="submit"]')

this.assert.equal(await this.nextAttributeMutationNamed("frame", "busy"), "", "sets [busy] on the #frame")

const title = await this.querySelector("#frame h2")
this.assert.equal(await title.getVisibleText(), "Frame: Loaded")
this.assert.equal(await this.nextAttributeMutationNamed("frame", "busy"), null, "removes [busy] from the #frame")
}

async "test frame form submission with empty created response"() {
const htmlBefore = await this.outerHTMLForSelector("#frame")
const button = await this.querySelector("#frame form.created input[type=submit]")
Expand Down Expand Up @@ -300,7 +317,7 @@ export class FormSubmissionTests extends TurboDriveTestCase {

async "test form submission targets disabled frame"() {
this.remote.execute(() => document.getElementById("frame")?.setAttribute("disabled", ""))
await this.clickSelector('#targets-frame [type="submit"]')
await this.clickSelector('#targets-frame form.one [type="submit"]')
await this.nextBody

this.assert.equal(await this.pathname, "/src/tests/fixtures/one.html")
Expand Down
7 changes: 7 additions & 0 deletions src/tests/functional/frame_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ export class FrameTests extends TurboDriveTestCase {
this.assert.equal(otherEvents.length, 0, "no more events")
}

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

this.assert.equal(await this.nextAttributeMutationNamed("frame", "busy"), "", "sets [busy] on the #frame")
this.assert.equal(await this.nextAttributeMutationNamed("frame", "busy"), null, "removes [busy] from the #frame")
}

async "test following a link to a page without a matching frame results in an empty frame"() {
await this.clickSelector("#missing a")
await this.nextBeat
Expand Down
12 changes: 12 additions & 0 deletions src/tests/helpers/turbo_drive_test_case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { RemoteChannel } from "./remote_channel"
import { Element } from "@theintern/leadfoot"

type EventLog = [string, any]
type MutationLog = [string, string | null, string | null]

export class TurboDriveTestCase extends FunctionalTestCase {
eventLogChannel: RemoteChannel<EventLog> = new RemoteChannel(this.remote, "eventLogs")
mutationLogChannel: RemoteChannel<MutationLog> = new RemoteChannel(this.remote, "mutationLogs")
lastBody?: Element

async beforeTest() {
Expand Down Expand Up @@ -38,6 +40,16 @@ export class TurboDriveTestCase extends FunctionalTestCase {
return !records.some(([name]) => name == eventName)
}

async nextAttributeMutationNamed(elementId: string, attributeName: string): Promise<string | null> {
let record: MutationLog | undefined
while (!record) {
const records = await this.mutationLogChannel.read(1)
record = records.find(([name, id]) => name == attributeName && id == elementId)
}
const attributeValue = record[2]
return attributeValue
}

get nextBody(): Promise<Element> {
return (async () => {
let body
Expand Down

0 comments on commit da75401

Please sign in to comment.