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