TestsPage
is an async component
The codebase's tests: each test definition, by namespace, with its last outcome as kept (passes, fails, or not run as
it is now), failures first with what they said; a run of them (Run all, a namespace's, one's), as tcm test runs
it, its output as it comes (made again every second and a half while it runs). q narrows them to names with it.
import { shownMs } from "#codebase/tcmts/cli/main"; import { ago, at, codeHref, Fold, servedCodebase, shareNow } from "#codebase/tcmts/share"; import { EditButton, listTests, Live, runsOfTests, RunTestsButton, StudioFrame, studioShare, } from "#codebase/tcmts/studio"; export async function TestsPage({ q }: { q: string }) { studioShare(); const s = shareNow(); if (!s.signedIn) return ( <StudioFrame tab="tests" title="Sign in"> {null} </StudioFrame> ); const cb = servedCodebase().cb; const own = s.cb === cb; const words = q.trim().toLowerCase(); const all = listTests(cb).filter((t) => !words || t.path.toLowerCase().includes(words)); const failed = all.filter((t) => t.ok === false); // (a suite's tests apart: they run a package's command, and their passes are kept as it ran) const defs = all.filter((t) => !t.suite); const suites = all.filter((t) => t.suite); const passed = defs.filter((t) => t.ok === true).length; const unrun = defs.filter((t) => t.ok === undefined).length; const run = runsOfTests.at(-1); const running = !!run && run.ended === undefined; const nsOf = (p: string) => p.slice(0, Math.max(0, p.lastIndexOf("."))); const groups = new Map<string, typeof all>(); for (const t of all) groups.set(nsOf(t.path), [...(groups.get(nsOf(t.path)) ?? []), t]); const mark = (t: (typeof all)[number]) => t.ok === undefined ? ( <span className="muted" title="not run as it is now"> ○ </span> ) : t.ok ? ( <span className="ok" title="passes"> ✓ </span> ) : ( <span className="bad" title="fails"> ✗ </span> ); return ( <StudioFrame tab="tests" title="Tests"> {running && <Live every={1500} />} <div className="pagehead"> <h2 className="title">Tests</h2> <span className="muted"> {`${passed} pass, `} <span className={failed.length ? "bad" : ""}>{`${failed.length} fail`}</span> {`, ${unrun} not run as they are now`} {suites.length > 0 && ` · a package's suites: ${suites.filter((t) => t.ok).length} of ${suites.length} pass as they are`} </span> <span className="right actions"> <form action={at("/tests")} method="get"> <input className="field" type="search" name="q" defaultValue={q} placeholder="Find a test" aria-label="Find a test" /> </form> {own && !running && ( <RunTestsButton names={words ? all.map((t) => t.path) : []} label={words ? "Run these" : "Run all"} primary /> )} </span> </div> {run && ( <div className="box"> <div className="boxhead"> <b className="mono">{`tcm test${run.names.length ? ` ${run.names.join(" ")}` : ""}`}</b> <span className={`muted${run.code ? " bad" : ""}`}> {running ? `running, ${Math.round((Date.now() - run.started) / 1000)}s` : run.code ? `failed, ${ago(run.ended!)}` : `passed, ${ago(run.ended!)}`} </span> </div> <pre className="code applog testlog">{run.lines.slice(-40).join("\n") || "…"}</pre> </div> )} {failed.length > 0 && ( <div className="box"> <div className="boxhead"> <b className="bad">Failing</b> <span className="count">{failed.length}</span> </div> {failed.map((t) => ( <div className="failrow" key={t.path}> <div className="lrow"> <a className="mono" href={codeHref(t.path)}> {t.path} </a> {own && ( <span className="actions"> <EditButton name={t.path} /> {!running && <RunTestsButton names={[t.path]} label="Run it" />} </span> )} </div> {t.message && ( <pre className="code errmsg">{t.message.split("\n").slice(0, 8).join("\n")}</pre> )} </div> ))} </div> )} <div className="box"> {all.length ? ( [...groups].map(([ns, tests]) => { const bad = tests.filter((t) => t.ok === false).length; const good = tests.filter((t) => t.ok === true).length; return ( <div className="relline" key={ns}> <Fold head={ <> <b className="mono">{ns || "(the root)"}</b> <span className="relsum muted"> {`${tests.length} ${tests.every((t) => t.suite) ? "suite " : ""}test${tests.length === 1 ? "" : "s"}`} {bad ? <span className="bad">{`, ${bad} failing`}</span> : null} {good < tests.length - bad ? `, ${tests.length - bad - good} not run` : ""} </span> </> } > {own && !running && ns && ( <div className="lrow" style={{ borderTop: 0 }}> <span className="muted">the tests under it</span> <RunTestsButton names={[ns]} label="Run them" /> </div> )} {tests.map((t) => ( <div className="lrow" key={t.path}> <div> {mark(t)}{" "} <a className="mono" href={codeHref(t.path)}> {t.path.slice(ns ? ns.length + 1 : 0)} </a> </div> <span className="muted">{t.ms !== undefined ? shownMs(t.ms) : ""}</span> </div> ))} </Fold> </div> ); }) ) : ( <div className="empty"> {q ? `No test is named like “${q}”.` : "No tests: a definition named test… (or under tests) is one."} </div> )} </div> </StudioFrame> ); }
uses
- tcmts.cli.main.shownMs
- tcmts.share.Fold
- tcmts.share.ago
- tcmts.share.at
- tcmts.share.codeHref
- tcmts.share.servedCodebase
- tcmts.share.shareNow
- tcmts.studio.EditButton
- tcmts.studio.Live
- tcmts.studio.RunTestsButton
- tcmts.studio.StudioFrame
- tcmts.studio.listTests
- tcmts.studio.runsOfTests
- tcmts.studio.studioShare