TestsPage

tcmts.studio.TestsPage · #j5imnwvi · term · runs on the server

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

used by