import { expect, test } from "vitest"; import { h } from "preact"; import { render } from "preact-render-to-string"; import { Alert, Badge, Button, Checkbox, Input, Progress, Select, Tabs, UrlPrefixInput, } from "./index.ts"; test("Button primary emits both classes and merges consumer class", () => { const html = render(h(Button, { variant: "primary", class: "x" }, "Ok")); expect(html).toContain("sb-button sb-button-primary x"); expect(html).toContain(">Ok"); expect(html).toContain('type="button"'); }); test("Button danger/icon variants", () => { expect(render(h(Button, { variant: "danger" }, "D"))).toContain( "sb-button-danger", ); expect(render(h(Button, { variant: "icon" }, "I"))).toContain( "sb-button-icon", ); }); test("Input renders sb-input with default type text", () => { const html = render(h(Input, { value: "hi" })); expect(html).toContain('class="sb-input"'); expect(html).toContain('type="text"'); }); test("Input renders cleanly when onConfirm/onExit are provided", () => { const html = render( h(Input, { value: "hi", onConfirm: () => {}, onExit: () => {} }), ); expect(html).toContain('class="sb-input"'); expect(html).toContain('value="hi"'); // Callback props must not leak as DOM attributes expect(html).not.toContain("onConfirm"); expect(html).not.toContain("onExit"); }); test("Select wraps options", () => { const html = render(h(Select, {}, h("option", {}, "A"))); expect(html).toContain('class="sb-select"'); expect(html).toContain(">A"); }); test("Checkbox is a checkbox input", () => { const html = render(h(Checkbox, { checked: true })); expect(html).toContain('type="checkbox"'); expect(html).toContain("sb-checkbox"); }); test("Tabs marks the active tab and wires per-item onSelect", () => { let picked = ""; const items = [ { label: "A", active: false, onSelect: () => (picked = "a") }, { label: "B", active: true, onSelect: () => (picked = "b") }, ]; const html = render(h(Tabs, { items })); expect(html).toContain("sb-tab sb-active"); expect(html).toContain('aria-selected="true"'); // each tab carries its own handler items[0].onSelect(); expect(picked).toBe("a"); }); test("Alert variant class", () => { expect(render(h(Alert, { variant: "error" }, "e"))).toContain( "sb-alert sb-alert-error", ); expect(render(h(Alert, { variant: "warning" }, "w"))).toContain( "sb-alert-warning", ); expect(render(h(Alert, { variant: "info" }, "i"))).toContain("sb-alert-info"); }); test("Badge", () => { expect(render(h(Badge, {}, "b"))).toContain('class="sb-badge">b<'); }); test("Progress clamps value to a width percentage", () => { expect(render(h(Progress, { value: 0.5 }))).toMatch(/width:\s*50%/); expect(render(h(Progress, { value: 2 }))).toMatch(/width:\s*100%/); expect(render(h(Progress, { value: -1 }))).toMatch(/width:\s*0%/); }); test("UrlPrefixInput shows the origin it is given, not the ambient one", () => { // The desktop app configures a *remote* sync server, so the origin cannot // be read from `location` the way the server-hosted Space Manager does. const html = render( h(UrlPrefixInput, { origin: "https://sb.example.com", value: "/notes", onInput: () => {}, }), ); expect(html).toContain("sb-url-input"); expect(html).toContain(">https://sb.example.com"); expect(html).toContain('value="/notes"'); }); test("UrlPrefixInput trims a trailing slash off the origin", () => { // Sync server URLs are commonly stored with one; without trimming the // assembled URL reads "https://h//notes". const html = render( h(UrlPrefixInput, { origin: "https://sb.example.com/", value: "/notes", onInput: () => {}, }), ); expect(html).toContain(">https://sb.example.com"); });