From 058d803ad186cf11ca21c8fe8406914982558564 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 23 Oct 2026 14:33:26 +0000 Subject: [PATCH] test(plan): unmount the view instead of emptying the document Main verify failed with Comment is not defined in Plan.svelte after every test had passed. The suite tore down by emptying document.body, which leaves $effects and askWhenLive promises running; a late {#if} patch then constructs a comment node against a jsdom that has already dropped Comment. cleanup() from testing-library destroy()s the component, the same way the other mounted views already do. Co-authored-by: Fredrik Ahlgren --- src/views/Plan.svelte.test.ts | 60 ++++++----------------------------- 1 file changed, 10 insertions(+), 50 deletions(-) diff --git a/src/views/Plan.svelte.test.ts b/src/views/Plan.svelte.test.ts index f4d6d57..7263739 100644 --- a/src/views/Plan.svelte.test.ts +++ b/src/views/Plan.svelte.test.ts @@ -10,7 +10,7 @@ */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' -import { render } from '@testing-library/svelte' +import { cleanup, render } from '@testing-library/svelte' import Plan from './Plan.svelte' import { SiteStore } from '$lib/state/site.svelte' import { LoopbackCarrier } from '$lib/carrier/loopback' @@ -27,6 +27,15 @@ function chart(): Element | null { return document.querySelector('ftw-price-chart') } +// destroy() the view. Emptying the document leaves $effects and asks +// running; a late {#if} patch then throws Comment is not defined, which +// is how CI went red with every test still green. +afterEach(() => { + cleanup() + vi.useRealTimers() + vi.restoreAllMocks() +}) + describe('the Plan view, against the simulator', () => { let fetched: ReturnType @@ -35,11 +44,6 @@ describe('the Plan view, against the simulator', () => { fetched = vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('no origin')) }) - afterEach(() => { - document.body.replaceChildren() - vi.restoreAllMocks() - }) - async function mount() { const site = new SiteStore('test') site.connect(new LoopbackCarrier(new SimBox({ now: () => MORNING }), { latencyMs: 0 })) @@ -156,11 +160,6 @@ describe('a viewer on the Plan screen', () => { vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('no origin')) }) - afterEach(() => { - document.body.replaceChildren() - vi.restoreAllMocks() - }) - it('cannot press a mode, and is told why in the right words', async () => { // Not hidden: which mode the house runs in is a reading, and a viewer is // entitled to it. What must not happen is a live button the box will @@ -224,11 +223,6 @@ describe('a viewer on the Plan screen', () => { }) describe('the Plan screen before the box has answered', () => { - afterEach(() => { - document.body.replaceChildren() - vi.restoreAllMocks() - }) - it('says nothing about what the box supports', async () => { // A cold start paints from cache with no carrier yet: `caps` is empty // because nobody has asked, not because the box said so. Both sentences @@ -248,12 +242,6 @@ describe('the Plan screen before the box has answered', () => { }) describe('a price window as the day moves under it', () => { - afterEach(() => { - document.body.replaceChildren() - vi.useRealTimers() - vi.restoreAllMocks() - }) - /** Real box, real wire, and a clock the test drives. */ async function drawn() { vi.useFakeTimers() @@ -338,12 +326,6 @@ describe('a price window as the day moves under it', () => { describe('a plan the box could not answer', () => { const rows = () => document.querySelectorAll('section.timeline li').length - afterEach(() => { - document.body.replaceChildren() - vi.useRealTimers() - vi.restoreAllMocks() - }) - it('is asked for again on its own, without the session ever moving', async () => { vi.useFakeTimers() vi.setSystemTime(MORNING) @@ -467,12 +449,6 @@ describe('a plan the box could not answer', () => { describe('a plan and a price window the wire cut short', () => { const rows = () => document.querySelectorAll('section.timeline li').length - afterEach(() => { - document.body.replaceChildren() - vi.useRealTimers() - vi.restoreAllMocks() - }) - it('arrive on their own once the carrier is back', async () => { vi.useFakeTimers() vi.setSystemTime(MORNING) @@ -530,12 +506,6 @@ describe('a plan and a price window the wire cut short', () => { * clock can be moved without a box in the way. */ describe('when the Plan view asks for prices', () => { - afterEach(() => { - document.body.replaceChildren() - vi.useRealTimers() - vi.restoreAllMocks() - }) - /** A store that believes it is streaming from a box that has prices. */ function streamingStore() { const site = new SiteStore('test') @@ -815,11 +785,6 @@ describe('when the Plan view asks for prices', () => { describe('the notice under the price chart', () => { const MIDNIGHT = new Date(2026, 6, 15, 0, 0, 0).getTime() - afterEach(() => { - document.body.replaceChildren() - vi.restoreAllMocks() - }) - /** An hourly window over `hours`, skipping any hour in `missing`. */ function window(hours: number, missing: readonly number[] = []): Prices { const slots = [] @@ -908,11 +873,6 @@ describe('the notice under the price chart', () => { * card greying out on send read as the tap having done nothing. */ describe('switching how the home is run', () => { - afterEach(() => { - document.body.replaceChildren() - vi.restoreAllMocks() - }) - function choice(label: string): HTMLButtonElement | undefined { return [...document.querySelectorAll('button.choice')].find((b) => b.textContent?.includes(label)