Skip to content

Commit

Permalink
Empty frame contents when a matching frame is missing
Browse files Browse the repository at this point in the history
  • Loading branch information
sstephenson committed Jan 12, 2021
1 parent cc86b22 commit 3263389
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 16 deletions.
18 changes: 11 additions & 7 deletions src/core/frames/frame_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,8 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest

async loadResponse(response: FetchResponse): Promise<void> {
const fragment = fragmentFromHTML(await response.responseHTML)
const element = await this.extractForeignFrameElement(fragment)

if (element) {
if (fragment) {
const element = await this.extractForeignFrameElement(fragment)
await nextAnimationFrame()
this.loadFrameElement(element)
this.scrollFrameIntoView(element)
Expand Down Expand Up @@ -195,7 +194,7 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest
return getFrameElementById(id) ?? this.element
}

private async extractForeignFrameElement(container: ParentNode): Promise<FrameElement | undefined> {
private async extractForeignFrameElement(container: ParentNode): Promise<FrameElement> {
let element
const id = CSS.escape(this.id)

Expand All @@ -207,6 +206,9 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest
await element.loaded
return await this.extractForeignFrameElement(element)
}

console.error(`Response has no matching <turbo-frame id="${id}"> element`)
return new FrameElement()
}

private loadFrameElement(frameElement: FrameElement) {
Expand Down Expand Up @@ -309,9 +311,11 @@ function readScrollLogicalPosition(value: string | null, defaultValue: ScrollLog
}
}

function fragmentFromHTML(html = "") {
const foreignDocument = document.implementation.createHTMLDocument()
return foreignDocument.createRange().createContextualFragment(html)
function fragmentFromHTML(html?: string) {
if (html) {
const foreignDocument = document.implementation.createHTMLDocument()
return foreignDocument.createRange().createContextualFragment(html)
}
}

function activateElement(element: Node | null) {
Expand Down
4 changes: 4 additions & 0 deletions src/tests/fixtures/frames.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,9 @@ <h2>Frames: #frame</h2>
<turbo-frame id="hello">
<h2>Frames: #hello</h2>
</turbo-frame>

<turbo-frame id="missing">
<a href="/src/tests/fixtures/frames/frame.html">Missing frame</a>
</turbo-frame>
</body>
</html>
16 changes: 8 additions & 8 deletions src/tests/functional/form_submission_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,22 @@ export class FormSubmissionTests extends TurboDriveTestCase {
}

async "test standard form submission with empty created response"() {
const htmlBefore = await this.getHTMLForSelector("body")
const htmlBefore = await this.outerHTMLForSelector("body")
const button = await this.querySelector("#standard form.created input[type=submit]")
await button.click()
await this.nextBeat

const htmlAfter = await this.getHTMLForSelector("body")
const htmlAfter = await this.outerHTMLForSelector("body")
this.assert.equal(htmlAfter, htmlBefore)
}

async "test standard form submission with empty no-content response"() {
const htmlBefore = await this.getHTMLForSelector("body")
const htmlBefore = await this.outerHTMLForSelector("body")
const button = await this.querySelector("#standard form.no-content input[type=submit]")
await button.click()
await this.nextBeat

const htmlAfter = await this.getHTMLForSelector("body")
const htmlAfter = await this.outerHTMLForSelector("body")
this.assert.equal(htmlAfter, htmlBefore)
}

Expand Down Expand Up @@ -75,22 +75,22 @@ export class FormSubmissionTests extends TurboDriveTestCase {
}

async "test frame form submission with empty created response"() {
const htmlBefore = await this.getHTMLForSelector("#frame")
const htmlBefore = await this.outerHTMLForSelector("#frame")
const button = await this.querySelector("#frame form.created input[type=submit]")
await button.click()
await this.nextBeat

const htmlAfter = await this.getHTMLForSelector("#frame")
const htmlAfter = await this.outerHTMLForSelector("#frame")
this.assert.equal(htmlAfter, htmlBefore)
}

async "test frame form submission with empty no-content response"() {
const htmlBefore = await this.getHTMLForSelector("#frame")
const htmlBefore = await this.outerHTMLForSelector("#frame")
const button = await this.querySelector("#frame form.no-content input[type=submit]")
await button.click()
await this.nextBeat

const htmlAfter = await this.getHTMLForSelector("#frame")
const htmlAfter = await this.outerHTMLForSelector("#frame")
this.assert.equal(htmlAfter, htmlBefore)
}

Expand Down
15 changes: 15 additions & 0 deletions src/tests/functional/frame_tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { FunctionalTestCase } from "../helpers/functional_test_case"

export class FrameTests extends FunctionalTestCase {
async setup() {
await this.goToLocation("/src/tests/fixtures/frames.html")
}

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
this.assert.notOk(await this.innerHTMLForSelector("#missing"))
}
}

FrameTests.registerSuite()
1 change: 1 addition & 0 deletions src/tests/functional/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./async_script_tests"
export * from "./form_submission_tests"
export * from "./frame_tests"
export * from "./loading_tests"
export * from "./navigation_tests"
export * from "./rendering_tests"
Expand Down
7 changes: 6 additions & 1 deletion src/tests/helpers/functional_test_case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,16 @@ export class FunctionalTestCase extends InternTestCase {
return this.evaluate(element => element.scrollIntoView(), element)
}

async getHTMLForSelector(selector: string): Promise<string> {
async outerHTMLForSelector(selector: string): Promise<string> {
const element = await this.remote.findByCssSelector(selector)
return this.evaluate(element => element.outerHTML, element)
}

async innerHTMLForSelector(selector: string): Promise<string> {
const element = await this.remote.findAllByCssSelector(selector)
return this.evaluate(element => element.innerHTML, element)
}

get scrollPosition(): Promise<{ x: number, y: number }> {
return this.evaluate(() => ({ x: window.scrollX, y: window.scrollY }))
}
Expand Down

0 comments on commit 3263389

Please sign in to comment.