// Mounting a Kit component the way an application does.
//
// Svelte 5's `mount` is the whole harness: it takes a component and a target
// and gives back the instance, which is enough to render and assert against
// real DOM. No testing-library sits in between, deliberately — the thing under
// test renders one element wearing known classes around one
, and that
// contract is read off the DOM directly. The application Kit's `test/mount.ts`
// is the same harness for the same reason; it stays a per-Kit file because a
// Kit's tests are the one thing about a Kit that never leaves it.
//
// `withColorScheme` writes the contrast attribute the Logo follows when `on`
// is omitted. The write lands on the same document the component mounts into,
// exactly as an application's Color Scheme switch does. Theme remains free to
// describe direction independently, and this helper never mutates that axis.
// The restoration therefore keeps the original harness coverage intact.
import { afterEach, expect } from "vitest";
import { flushSync, mount, unmount, type Component } from "svelte";
const mounted: { instance: Record; target: HTMLElement }[] = [];
/** Mount `component` with `props` into a fresh detached element. */
export function render>(
component: Component,
props: Props = {} as Props,
): HTMLElement {
const target = document.createElement("div");
document.body.appendChild(target);
const instance = mount(component, { target, props }) as Record;
mounted.push({ instance, target });
flushSync();
return target;
}
/** The single root element the component rendered. */
export function rendered(target: HTMLElement): HTMLElement {
const element = target.firstElementChild;
expect(element, "the component rendered no element").not.toBeNull();
return element as HTMLElement;
}
/** A class string as a set, so assertions never depend on class order. */
export function classesOf(className: string): Set {
return new Set(className.split(/\s+/).filter(Boolean));
}
/** The element's classes as a set, read off the attribute. */
export function classes(element: Element): Set {
return classesOf(element.getAttribute("class") ?? "");
}
/** One declaration out of an inline style attribute, e.g. `padding`. */
export function styleOf(element: Element, property: string): string {
return (element as HTMLElement).style.getPropertyValue(property);
}
/** The active Color Scheme on the test document — what `on` defaults from. */
export const COLOR_SCHEME_ATTRIBUTE = "data-color-scheme";
/** Run `body` with `scheme` active on the document, then put it back. */
export function withColorScheme(scheme: string | null, body: () => T): T {
const root = document.documentElement;
const before = root.getAttribute(COLOR_SCHEME_ATTRIBUTE);
if (scheme === null) root.removeAttribute(COLOR_SCHEME_ATTRIBUTE);
else root.setAttribute(COLOR_SCHEME_ATTRIBUTE, scheme);
try {
return body();
} finally {
if (before === null) root.removeAttribute(COLOR_SCHEME_ATTRIBUTE);
else root.setAttribute(COLOR_SCHEME_ATTRIBUTE, before);
}
}
afterEach(() => {
for (const { instance, target } of mounted.splice(0)) {
unmount(instance);
target.remove();
}
});