diff --git a/src/core/frames/frame_controller.ts b/src/core/frames/frame_controller.ts
index d0d507518..20fbd2224 100644
--- a/src/core/frames/frame_controller.ts
+++ b/src/core/frames/frame_controller.ts
@@ -158,7 +158,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) {
@@ -175,7 +176,8 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest
}
formSubmissionFinished(formSubmission: FormSubmission) {
-
+ const frame = this.findFrameElement(formSubmission.formElement)
+ frame.removeAttribute("busy")
}
// View delegate
diff --git a/src/tests/fixtures/form.html b/src/tests/fixtures/form.html
index ae2a97824..2bc7e6f14 100644
--- a/src/tests/fixtures/form.html
+++ b/src/tests/fixtures/form.html
@@ -111,10 +111,15 @@
-
+
+
Frame: Form
diff --git a/src/tests/fixtures/frames.html b/src/tests/fixtures/frames.html
index b139043b1..fb361f213 100644
--- a/src/tests/fixtures/frames.html
+++ b/src/tests/fixtures/frames.html
@@ -4,6 +4,7 @@
Frame
+
Frames
diff --git a/src/tests/fixtures/test.js b/src/tests/fixtures/test.js
index 0cfff6493..f92fc58af 100644
--- a/src/tests/fixtures/test.js
+++ b/src/tests/fixtures/test.js
@@ -10,6 +10,15 @@
eventLogs.push([event.type, event.detail])
}
+ window.mutationLogs = []
+
+ new MutationObserver((mutations) => {
+ for (const { attributeName, oldValue, target } of mutations.filter(({ type }) => type == "attributes")) {
+ if (target instanceof HTMLElement) {
+ mutationLogs.push([attributeName, target.id, target.getAttribute(attributeName)])
+ }
+ }
+ }).observe(document, { subtree: true, childList: true, attributes: true })
})([
"turbo:before-cache",
"turbo:before-render",
diff --git a/src/tests/functional/form_submission_tests.ts b/src/tests/functional/form_submission_tests.ts
index fe90a07cb..2e41d60fa 100644
--- a/src/tests/functional/form_submission_tests.ts
+++ b/src/tests/functional/form_submission_tests.ts
@@ -163,6 +163,23 @@ export class FormSubmissionTests extends TurboDriveTestCase {
this.assert.equal(await this.pathname, "/src/tests/fixtures/form.html")
}
+ 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]")
@@ -254,7 +271,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")
diff --git a/src/tests/functional/frame_tests.ts b/src/tests/functional/frame_tests.ts
index 8204ee013..06ba92ad3 100644
--- a/src/tests/functional/frame_tests.ts
+++ b/src/tests/functional/frame_tests.ts
@@ -1,10 +1,17 @@
-import { FunctionalTestCase } from "../helpers/functional_test_case"
+import { TurboDriveTestCase } from "../helpers/turbo_drive_test_case"
-export class FrameTests extends FunctionalTestCase {
+export class FrameTests extends TurboDriveTestCase {
async setup() {
await this.goToLocation("/src/tests/fixtures/frames.html")
}
+ 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
diff --git a/src/tests/helpers/turbo_drive_test_case.ts b/src/tests/helpers/turbo_drive_test_case.ts
index 5124e0708..5d852b70b 100644
--- a/src/tests/helpers/turbo_drive_test_case.ts
+++ b/src/tests/helpers/turbo_drive_test_case.ts
@@ -3,9 +3,11 @@ import { RemoteChannel } from "./remote_channel"
import { Element } from "@theintern/leadfoot"
type EventLog = [string, any]
+type MutationLog = [string, string, string | null]
export class TurboDriveTestCase extends FunctionalTestCase {
eventLogChannel: RemoteChannel = new RemoteChannel(this.remote, "eventLogs")
+ mutationLogChannel: RemoteChannel = new RemoteChannel(this.remote, "mutationLogs")
lastBody?: Element
async beforeTest() {
@@ -33,6 +35,15 @@ export class TurboDriveTestCase extends FunctionalTestCase {
return record[1]
}
+ async nextAttributeMutationNamed(elementId: string, attributeName: string): Promise {
+ let record: MutationLog | undefined
+ while (!record) {
+ const records = await this.mutationLogChannel.read(1)
+ record = records.find(([attribute, id]) => id == elementId && attribute == attributeName)
+ }
+ return record[2]
+ }
+
get nextBody(): Promise {
return (async () => {
let body