Build a space page once when it opens

This commit is contained in:
Coracle-Bot 2026-09-16 16:19:43 +00:00
parent 0ba6dc864c
commit a7ca6bb9fb
3 changed files with 96 additions and 6 deletions

View file

@ -1,8 +1,34 @@
import {spec} from "@welshman/lib"
import {HOUR, spec} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {RelayMessageType} from "@welshman/net"
import {Article} from "@welshman/domain"
import type {Page} from "@playwright/test"
import {expect, getTranscript, pathPattern, roomPath, spacePath, test, users} from "../harness"
// Every page in a space renders one PageContent, so the number added to the document is the number
// of times the page was built.
const watchPageBuilds = (page: Page) =>
page.evaluate(() => {
const selector = "[data-component='PageContent']"
let builds = 0
document.documentElement.dataset.pageBuilds = "0"
new MutationObserver(records => {
for (const record of records) {
for (const node of record.addedNodes) {
if (node instanceof Element && (node.matches(selector) || node.querySelector(selector))) {
document.documentElement.dataset.pageBuilds = String(++builds)
}
}
}
}).observe(document.body, {childList: true, subtree: true})
})
const expectPageBuilds = (page: Page, builds: number) =>
expect(page.locator("html")).toHaveAttribute("data-page-builds", String(builds))
test("keeps two spaces' contents on their own relays", async ({seed, as}) => {
const scenario = await seed(({relay, user}) => {
const space = relay("space")
@ -202,3 +228,63 @@ test("enters a space on its details page whatever its relay advertises", async (
await expect(page).toHaveURL(pathPattern(spacePath(space.url) + "/about"))
await expect(page.locator('[data-component="PageBar"]')).toContainText("Space Details")
})
test("builds a page once when it opens and again when its params change", async ({seed, as}) => {
const scenario = await seed(({relay, user, at}) => {
const space = relay("space")
space.room("general", {name: "General"})
space.join(user.alice, "general")
space.join(user.bob, "general")
space.profile(user.alice, {name: "Alice Anderson"})
space.relayList(user.alice)
const article = (identifier: string, title: string, hours: number) =>
space.event(
user.alice,
() =>
space
.kind(Article)
.writer()
.setIdentifier(identifier)
.setTitle(title)
.setPublishedAt(at(hours, HOUR))
.setContent("Gardens are worth the trouble.")
.renderTemplate(),
at(hours, HOUR),
)
article("tending-the-garden", "Tending the Garden", 4)
article("repotting-in-winter", "Repotting in Winter", 3)
})
const {url} = scenario.space("space")
const page = await as(users.bob, `${spacePath(url)}/articles`)
await watchPageBuilds(page)
await page
.locator('[data-component="ArticleItem"]')
.filter({hasText: "Tending the Garden"})
.getByRole("link", {name: "Tending the Garden", exact: true})
.click({position: {x: 20, y: 20}})
await expect(
page.locator("article header").getByRole("heading", {name: "Tending the Garden"}),
).toBeVisible()
// The rebuild this guards against landed 20ms after the first build, which is before the article
// itself is on screen on a slower run.
await page.waitForTimeout(250)
await expectPageBuilds(page, 1)
// Another article is the same route with different params, which SvelteKit answers by keeping the
// page it has. The page reads the address it renders once, so this one does have to be rebuilt.
await page.getByRole("link", {name: "Repotting in Winter"}).click()
await expect(
page.locator("article header").getByRole("heading", {name: "Repotting in Winter"}),
).toBeVisible()
await expectPageBuilds(page, 2)
})

View file

@ -1,14 +1,14 @@
<script lang="ts">
import type {Snippet} from "svelte"
import {page} from "$app/stores"
type Props = {
children?: Snippet
params: {relay?: string}
}
const {children}: Props = $props()
const {children, params}: Props = $props()
</script>
{#key $page.params.relay}
{#key params.relay}
{@render children?.()}
{/key}

View file

@ -28,9 +28,10 @@
type Props = {
children?: Snippet
params: Record<string, string>
}
const {children}: Props = $props()
const {children, params}: Props = $props()
const url = decodeRelay($page.params.relay!)
@ -113,7 +114,10 @@
<SpaceMenu {url} />
</SecondaryNav>
<Page>
{#key $page.url.pathname}
<!-- SvelteKit builds a new page when the route changes and keeps the one it has when only the
params change, so this rebuilds it for the second case. The url the page store reports
arrives a tick after the page is built, so keying on that throws the new page away. -->
{#key JSON.stringify(params)}
{@render children?.()}
{/key}
</Page>