prepareTestRun

tcmts.cli.vitest.prepareTestRun · #ycv7ejva · term · runs on the server

uses node:path

Prepare a Vitest run of these tests: the request a child tcm process runs (tcm internal-vitest <file>), and where its results will be. The debugger runs it too, under its own control.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import { provideTool, toolModule } from "#codebase/tcmts/cli/lint";
import { Placement } from "#codebase/tcmts/cli/placement";
import type { Request, TestEntry } from "#codebase/tcmts/cli/vitest";
import { exportId } from "#codebase/tcmts/core/hash";
import type { DefRecord } from "#codebase/tcmts/core/types";
import { materialize, moduleFile } from "#codebase/tcmts/loader";
import type { DefSource } from "#codebase/tcmts/loader";
import { ensureExternals } from "#codebase/tcmts/loader/packages";
import fs from "node:fs";
import path from "node:path";

export function prepareTestRun(
  cb: Codebase,
  tests: { hash: string; componentHash: string }[],
  src: DefSource,
): { requestFile: string; results: string } {
  // (Vitest installed with the packages the tests import)
  const vitestModule = toolModule(cb, "vitest");
  materialize(
    tests.map((t) => t.hash),
    src,
    cb.lay,
    vitestModule ? { also: [{ module: vitestModule, subpath: "", export: "*" }] } : {},
  );
  const vitest = provideTool(cb, "vitest");
  // what's not in the store yet (an unsaved scratch file's definitions), so the child sees it too
  const unsaved = new Map<string, DefRecord>();
  const stack = tests.map((t) => t.hash);
  while (stack.length) {
    const h = stack.pop()!;
    if (unsaved.has(h) || cb.store.hasDef(h)) continue;
    const rec = src.get(h);
    if (!rec) continue;
    unsaved.set(h, rec);
    for (const m of src.members(rec.componentHash))
      for (const s of m.spans) if (s.hash) stack.push(s.hash);
  }
  const placement = new Placement(cb, (h) => src.get(h));
  const browserWhy = new Map(tests.map((t) => [t.hash, placement.testBrowserWhy(t.hash)]));
  const entry = (t: { hash: string; componentHash: string }): TestEntry => {
    const why = browserWhy.get(t.hash);
    return {
      hash: t.hash,
      module: moduleFile(t.componentHash, src),
      export: exportId(t.hash),
      ...(why ? { browser: why } : {}),
    };
  };
  const inBrowser = tests.filter((t) => browserWhy.get(t.hash) !== undefined);
  const inNode = tests.filter((t) => !inBrowser.includes(t));
  // (the browser's tools, installed together)
  if (inBrowser.length) {
    const both = ["@vitest/browser-playwright", "playwright"]
      .map((t) => toolModule(cb, t))
      .filter((m) => m !== undefined);
    if (both.length)
      ensureExternals(
        cb.lay,
        both.map((module) => ({ module, subpath: "", export: "*" })),
      );
  }
  const provider = inBrowser.length ? provideTool(cb, "@vitest/browser-playwright") : undefined;
  if (provider) provideTool(cb, "playwright");
  const dir = path.join(cb.lay.tcmDir, "test");
  fs.mkdirSync(dir, { recursive: true });
  const request: Request = {
    root: cb.lay.root,
    vitest: vitest.dir,
    ...(provider ? { browser: { provider: provider.dir } } : {}),
    node: inNode.map(entry),
    browserTests: inBrowser.map(entry),
    unsaved: [...unsaved.values()],
    results: path.join(dir, `results-${process.pid}.json`),
  };
  const requestFile = path.join(dir, `request-${process.pid}.json`);
  fs.writeFileSync(requestFile, JSON.stringify(request));
  fs.rmSync(request.results, { force: true });
  return { requestFile, results: request.results };
}

uses

used by