From 439755ab777b983a57b11fb4ffdcd67b190bb339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Fern=C3=A1ndez-Capel?= Date: Thu, 16 Nov 2023 18:41:26 +0000 Subject: [PATCH] Keep dynamically added frames on morph refreshes If some JS code adds a remote frame to the page, we want to keep it and reload its contents on morphing refreshes. This is useful, for example, for pagination, where we load pages into frames. --- src/core/drive/morph_renderer.js | 12 ++++-- src/tests/fixtures/page_refresh.html | 4 ++ src/tests/functional/page_refresh_tests.js | 45 ++++++++++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/core/drive/morph_renderer.js b/src/core/drive/morph_renderer.js index c3379be2b..8bb09bde9 100644 --- a/src/core/drive/morph_renderer.js +++ b/src/core/drive/morph_renderer.js @@ -44,10 +44,9 @@ export class MorphRenderer extends Renderer { } #shouldMorphElement = (oldNode, newNode) => { - if (!(oldNode instanceof HTMLElement) || this.isMorphingTurboFrame) { + if (this.isMorphingTurboFrame) { return true - } - else if (oldNode.hasAttribute("data-turbo-permanent")) { + } else if (!this.#morphableNode(oldNode)) { return false } else { return !this.#remoteFrameReplacement(oldNode, newNode) @@ -59,7 +58,11 @@ export class MorphRenderer extends Renderer { } #shouldRemoveElement = (node) => { - return this.#shouldMorphElement(node) + return this.#morphableNode(node) && !this.#isRemoteFrame(node) + } + + #morphableNode(node) { + return !(node instanceof HTMLElement) || !node.hasAttribute("data-turbo-permanent") } #reloadRemoteFrames() { @@ -89,6 +92,7 @@ export class MorphRenderer extends Renderer { return node instanceof HTMLElement && node.nodeName.toLowerCase() === "turbo-frame" && node.getAttribute("src") } + #remoteFrames() { return Array.from(document.querySelectorAll('turbo-frame[src]')).filter(frame => { return !frame.closest('[data-turbo-permanent]') diff --git a/src/tests/fixtures/page_refresh.html b/src/tests/fixtures/page_refresh.html index ec28092a4..b483ac96d 100644 --- a/src/tests/fixtures/page_refresh.html +++ b/src/tests/fixtures/page_refresh.html @@ -24,6 +24,10 @@

Page to be refreshed

+ +

A regular frame

+
+

Frame to be morphed

diff --git a/src/tests/functional/page_refresh_tests.js b/src/tests/functional/page_refresh_tests.js index a17cad790..931cc7b63 100644 --- a/src/tests/functional/page_refresh_tests.js +++ b/src/tests/functional/page_refresh_tests.js @@ -69,6 +69,51 @@ test("remote frames are excluded from full page morphing", async ({ page }) => { await expect(page.locator("#remote-frame")).toHaveText("Loaded morphed frame") }) +test("remote frames are reloaded if their src changes", async ({ page }) => { + await page.goto("/src/tests/fixtures/page_refresh.html") + + await page.evaluate(() => { + const frame = document.getElementById("frame") + frame.setAttribute("src", "/src/tests/fixtures/frames.html") + }) + await expect(page.locator("#frame")).toHaveText(/Frames: #frame/) + + await page.evaluate(() => { + document.getElementById("frame").innerHTML = `Modified frame!` + }) + + await page.click("#form-submit") + await nextEventNamed(page, "turbo:render", { renderMethod: "morph" }) + await nextBeat() + + await expect(page.locator("#frame")).not.toHaveText(/Frames: #frame/) + await expect(page.locator("#frame")).toHaveText(/Frame: Loaded/) +}) + +test("dynamically created remote frames are kept and reloaded", async ({ page }) => { + await page.goto("/src/tests/fixtures/page_refresh.html") + + await page.evaluate(() => { + const container = document.getElementById("container") + container.innerHTML = `` + }) + await expect(page.locator("#hello")).toHaveText(/Hello from a frame/) + + await page.evaluate(() => { + document.getElementById("hello").innerHTML = `Modified frame!` + }) + + await expect(page.locator("#hello")).not.toHaveText(/Hello from a frame/) + await expect(page.locator("#hello")).toHaveText(/Modified frame!/) + + await page.click("#form-submit") + await nextEventNamed(page, "turbo:render", { renderMethod: "morph" }) + await nextBeat() + + await expect(page.locator("#hello")).not.toHaveText(/Modified frame!/) + await expect(page.locator("#hello")).toHaveText(/Hello from a frame/) +}) + test("it preserves the scroll position when the turbo-refresh-scroll meta tag is 'preserve'", async ({ page }) => { await page.goto("/src/tests/fixtures/page_refresh.html")