diff --git a/src/core/drive/visit.ts b/src/core/drive/visit.ts index 981671b5b..6028f579b 100644 --- a/src/core/drive/visit.ts +++ b/src/core/drive/visit.ts @@ -317,6 +317,8 @@ export class Visit implements FetchRequestDelegate { this.adapter.visitProposedToLocation(this.redirectedToLocation, { action: "replace", response: this.response, + shouldCacheSnapshot: false, + willRender: false, }) this.followedRedirect = true } diff --git a/src/tests/fixtures/navigation.html b/src/tests/fixtures/navigation.html index 48953b7d1..f2fd53d86 100644 --- a/src/tests/fixtures/navigation.html +++ b/src/tests/fixtures/navigation.html @@ -15,7 +15,7 @@ Skipped Content -
+

Navigation

Same-origin unannotated link

Same-origin unannotated link ?key=value

diff --git a/src/tests/fixtures/rendering.html b/src/tests/fixtures/rendering.html index f0376cbdd..710c2c4ee 100644 --- a/src/tests/fixtures/rendering.html +++ b/src/tests/fixtures/rendering.html @@ -45,6 +45,7 @@

Rendering

Permanent element in frame

Permanent element in frame without layout

Delayed link

+

Redirect link

diff --git a/src/tests/fixtures/test.js b/src/tests/fixtures/test.js index fe1462caa..e409e4514 100644 --- a/src/tests/fixtures/test.js +++ b/src/tests/fixtures/test.js @@ -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", diff --git a/src/tests/functional/rendering_tests.ts b/src/tests/functional/rendering_tests.ts index ba5555e25..e67e35ecd 100644 --- a/src/tests/functional/rendering_tests.ts +++ b/src/tests/functional/rendering_tests.ts @@ -6,7 +6,9 @@ import { isScrolledToTop, nextBeat, nextBody, + nextBodyMutation, nextEventNamed, + noNextBodyMutation, pathname, propertyForSelector, readEventLogs, @@ -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")) @@ -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 element once") +}) + function deepElementsEqual( page: Page, left: JSHandle[], diff --git a/src/tests/helpers/page.ts b/src/tests/helpers/page.ts index 5c25669c4..3851eb4f5 100644 --- a/src/tests/helpers/page.ts +++ b/src/tests/helpers/page.ts @@ -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 { return page.locator(selector).getAttribute(attributeName) } @@ -112,6 +115,19 @@ export async function listenForEventOnTarget(page: Page, elementId: string, even }, eventName) } +export async function nextBodyMutation(page: Page): Promise { + let record: BodyMutationLog | undefined + while (!record) { + ;[record] = await readBodyMutationLogs(page, 1) + } + return record[0] +} + +export async function noNextBodyMutation(page: Page): Promise { + const records = await readBodyMutationLogs(page, 1) + return !records.some((record) => !!record) +} + export async function nextAttributeMutationNamed( page: Page, elementId: string, @@ -185,6 +201,10 @@ async function readArray(page: Page, identifier: string, length?: number): Pr ) } +export function readBodyMutationLogs(page: Page, length?: number): Promise { + return readArray(page, "bodyMutationLogs", length) +} + export function readEventLogs(page: Page, length?: number): Promise { return readArray(page, "eventLogs", length) }