Skip to content

Commit e1b9eca

Browse files
bjohansebasclaude
andcommitted
test(e2e): drive instance.invalidate() and instance.close() against a browser
invalidate() with unchanged sources rebuilds and reaches the page as a building event followed by a no-op sync — the client neither re-renders nor reloads (a built here would 404 on the never-emitted manifest). close() ends the SSE stream under a connected page: the client falls into its reconnect loop against an endpoint that no longer speaks SSE and the page keeps running untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 742c60e commit e1b9eca

1 file changed

Lines changed: 59 additions & 0 deletions

File tree

test/e2e/client.test.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,65 @@ describe("hot client (browser)", () => {
266266
expect(normalizeConsole(console_.messages)).toMatchSnapshot();
267267
});
268268

269+
it("rebuilds on instance.invalidate() and syncs connected pages as a no-op", async () => {
270+
app = await createHotApp({ code: acceptedApp("v1") });
271+
({ page, browser } = await runBrowser());
272+
const console_ = collectConsole(page);
273+
274+
await page.goto(app.url);
275+
await waitForAppText(page, "v1");
276+
await console_.waitFor("connected");
277+
await plantReloadMarker(page);
278+
279+
// A server-side invalidate with unchanged sources: the rebuild reaches
280+
// the browser, and its unchanged hash arrives as a sync the client
281+
// applies as a no-op (a `built` here would 404 on the missing manifest).
282+
const rebuilt = app.nextBuild();
283+
app.instance.invalidate();
284+
await rebuilt;
285+
await console_.waitFor("bundle rebuilding");
286+
287+
// Give the sync a beat to land before pinning that nothing changed.
288+
await new Promise((resolve) => {
289+
setTimeout(resolve, 500);
290+
});
291+
292+
expect(
293+
await page.evaluate(() => document.getElementById("app").textContent),
294+
).toBe("v1");
295+
expect(await readReloadMarker(page)).toBe(true);
296+
});
297+
298+
it("keeps the page alive after instance.close()", async () => {
299+
app = await createHotApp({
300+
query: "?timeout=1000",
301+
code: acceptedApp("v1"),
302+
});
303+
({ page, browser } = await runBrowser());
304+
const console_ = collectConsole(page);
305+
306+
await page.goto(app.url);
307+
await waitForAppText(page, "v1");
308+
await console_.waitFor("connected");
309+
await plantReloadMarker(page);
310+
311+
// Closing the middleware ends the SSE stream; the client falls into its
312+
// reconnect loop against an endpoint that no longer speaks SSE.
313+
await new Promise((resolve) => {
314+
app.instance.close(resolve);
315+
});
316+
317+
// A few reconnect windows later the page is still running untouched.
318+
await new Promise((resolve) => {
319+
setTimeout(resolve, 2500);
320+
});
321+
322+
expect(
323+
await page.evaluate(() => document.getElementById("app").textContent),
324+
).toBe("v1");
325+
expect(await readReloadMarker(page)).toBe(true);
326+
});
327+
269328
it("routes server publish() payloads to subscribe handlers", async () => {
270329
app = await createHotApp({
271330
code: `

0 commit comments

Comments
 (0)