Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/core/drive/visit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ export class Visit implements FetchRequestDelegate {
this.adapter.visitProposedToLocation(this.redirectedToLocation, {
action: "replace",
response: this.response,
shouldCacheSnapshot: false,
willRender: false,
Comment on lines +320 to +321

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seanpdoyle I understand why we don't want to render again, but I wonder if the flow should be the opposite?

For example, now what happens is the following
-> User clicks a link
-> Link redirects to somewhere else
-> Fetch API follows the redirect
-> Turbo then renders the response
-> Turbo then follows the redirect (updating the history, for example)
-> Turbo then doesn't render the response (because it doesn't want to render it twice)

But shouldn't it be something like
-> User clicks a link
-> Link redirects to somewhere else
-> Fetch API follows the redirect
-> Turbo then SKIPS rendering the response (because it is a redirect)
-> Turbo then follows the redirect (updating the history, for example)
-> Turbo then RENDERS the response

Following this flow, it would be more akin to what browsers do, which is first visiting the redirected location and then rendering the content of the page of that location, wdyt?

NOTE: My assumption may be wrong, and I am just misunderstanding how things work, but I thought that it is worth bringing it up anyway

})
this.followedRedirect = true
}
Expand Down
2 changes: 1 addition & 1 deletion src/tests/fixtures/navigation.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

<a href="#ignored-link" id="ignored-link">Skipped Content</a>

<section id="main" style="height: 200vh">
<section id="main">
<h1>Navigation</h1>
<p><a id="same-origin-unannotated-link" href="/src/tests/fixtures/one.html">Same-origin unannotated link</a></p>
<p><a id="same-origin-unannotated-link-search-params" href="/src/tests/fixtures/one.html?key=value">Same-origin unannotated link ?key=value</a></p>
Expand Down
1 change: 1 addition & 0 deletions src/tests/fixtures/rendering.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ <h1>Rendering</h1>
<p><a id="permanent-in-frame-element-link" href="/src/tests/fixtures/permanent_element.html" data-turbo-frame="frame">Permanent element in frame</a></p>
<p><a id="permanent-in-frame-without-layout-element-link" href="/src/tests/fixtures/frames/without_layout.html" data-turbo-frame="frame">Permanent element in frame without layout</a></p>
<p><a id="delayed-link" href="/__turbo/delayed_response">Delayed link</a></p>
<p><a id="redirect-link" href="/__turbo/redirect">Redirect link</a></p>
<form>
<input type="text" id="text-input">
<input type="radio" id="radio-input">
Expand Down
11 changes: 11 additions & 0 deletions src/tests/fixtures/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@
}
}
}).observe(document, { subtree: true, childList: true, attributes: true })

window.bodyMutationLogs = []
addEventListener("turbo:load", () => {
new MutationObserver((mutations) => {
for (const { addedNodes } of mutations) {
for (const { localName, outerHTML } of addedNodes) {
if (localName == "body") bodyMutationLogs.push([outerHTML])
}
}
}).observe(document.documentElement, { childList: true })
}, { once: true })
})([
"turbo:click",
"turbo:before-stream-render",
Expand Down
10 changes: 10 additions & 0 deletions src/tests/functional/rendering_tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
isScrolledToTop,
nextBeat,
nextBody,
nextBodyMutation,
nextEventNamed,
noNextBodyMutation,
pathname,
propertyForSelector,
readEventLogs,
Expand Down Expand Up @@ -88,6 +90,7 @@ test("test reloads when tracked elements change due to failed form submission",
})

await page.click("#tracked-asset-change-form button")
await nextBeat()

const reason = await page.evaluate(() => localStorage.getItem("reason"))
const unloaded = await page.evaluate(() => localStorage.getItem("unloaded"))
Expand Down Expand Up @@ -570,6 +573,13 @@ test("test error pages", async ({ page }) => {
assert.equal(await page.textContent("body"), "404 Not Found: /nonexistent\n")
})

test("test rendering a redirect response replaces the body once and only once", async ({ page }) => {
await page.click("#redirect-link")
await nextBodyMutation(page)

assert.ok(await noNextBodyMutation(page), "replaces <body> element once")
})

function deepElementsEqual(
page: Page,
left: JSHandle<SVGElement | HTMLElement>[],
Expand Down
20 changes: 20 additions & 0 deletions src/tests/helpers/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ type MutationAttributeName = string
type MutationAttributeValue = string | null
type MutationLog = [MutationAttributeName, Target, MutationAttributeValue]

type BodyHTML = string
type BodyMutationLog = [BodyHTML]

export function attributeForSelector(page: Page, selector: string, attributeName: string): Promise<string | null> {
return page.locator(selector).getAttribute(attributeName)
}
Expand Down Expand Up @@ -112,6 +115,19 @@ export async function listenForEventOnTarget(page: Page, elementId: string, even
}, eventName)
}

export async function nextBodyMutation(page: Page): Promise<string | null> {
let record: BodyMutationLog | undefined
while (!record) {
;[record] = await readBodyMutationLogs(page, 1)
}
return record[0]
}

export async function noNextBodyMutation(page: Page): Promise<boolean> {
const records = await readBodyMutationLogs(page, 1)
return !records.some((record) => !!record)
}

export async function nextAttributeMutationNamed(
page: Page,
elementId: string,
Expand Down Expand Up @@ -185,6 +201,10 @@ async function readArray<T>(page: Page, identifier: string, length?: number): Pr
)
}

export function readBodyMutationLogs(page: Page, length?: number): Promise<BodyMutationLog[]> {
return readArray<BodyMutationLog>(page, "bodyMutationLogs", length)
}

export function readEventLogs(page: Page, length?: number): Promise<EventLog[]> {
return readArray<EventLog>(page, "eventLogs", length)
}
Expand Down